Improved albums public page cache and more

Removed its dependency towards albums' editedAt property.
Editing album's metas (name, description, etc) will no longer update
its editedAt property.
Instead it will now ONLY be updated when adding/removing files to/from
it. Just like how it was meant to be, which was to be used to check
whether it's necessary to re-generate their downloadable ZIPs.

Albums public page cache will still be properly invalidated when
adding/removing files to/from it, as well as after editing their metas.

Added views/album-notice.njk to be used to render okay-ish notice when
an album's public page is still being generated.
I was originally thinking of using it for disabled albums as well, but
I refrained from it to reduce the possibility of disabled album IDs from
being easily scanned (as it just returns 404 now).

Removed invalidatedAt property from stats cache. Instead their caches
will immediately be nullified as they should (thus frees up memory
slightly as well).

Stats cache for albums will now only be cleared when truly necessary.
As in, adding/removing files to/from albums will no longer clear them.

Updated Nunjucks files to properly use h1, h2, h3 tags in actual
hierarchical orders.
Elements that don't need to use hX tags will now use P instead.
Nothing changes visually, only structurally.

Fixed some elements in Nunjucks using single quotes instead of
double quotes. They'd have worked the same, but consistency.

Added h1 title in FAQ page.

Make text for no JS warning a bit bigger, and improved the phrasing
a little bit.
This commit is contained in:
Bobby Wibowo 2020-06-03 10:44:24 +07:00
parent f305b08077
commit 8b4b0e79c5
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
11 changed files with 106 additions and 78 deletions

View File

@ -262,6 +262,7 @@ self.disable = async (req, res, next) => {
})
.update('enabled', 0)
utils.invalidateAlbumsCache([id])
utils.invalidateStatsCache('albums')
const identifier = await db.table('albums')
.select('identifier')
@ -325,7 +326,6 @@ self.edit = async (req, res, next) => {
const update = {
name,
editedAt: Math.floor(Date.now() / 1000),
download: Boolean(req.body.download),
public: Boolean(req.body.public),
description: typeof req.body.description === 'string'
@ -343,6 +343,7 @@ self.edit = async (req, res, next) => {
.where(filter)
.update(update)
utils.invalidateAlbumsCache([id])
utils.invalidateStatsCache('albums')
if (req.body.requestLink) {
self.onHold.delete(update.identifier)
@ -459,10 +460,6 @@ self.generateZip = async (req, res, next) => {
if ((isNaN(versionString) || versionString <= 0) && album.editedAt)
return res.redirect(`${album.identifier}?v=${album.editedAt}`)
// TODO: editedAt column will now be updated whenever
// a user is simply editing the album's name/description.
// Perhaps add a new timestamp column that will only be updated
// when the files in the album are actually modified?
if (album.zipGeneratedAt > album.editedAt)
try {
const filePath = path.join(paths.zips, `${identifier}.zip`)
@ -607,6 +604,7 @@ self.addFiles = async (req, res, next) => {
await db.table('albums')
.whereIn('id', albumids)
.update('editedAt', Math.floor(Date.now() / 1000))
utils.invalidateAlbumsCache(albumids)
return res.json({ success: true, failed })
} catch (error) {

View File

@ -49,20 +49,17 @@ const statsCache = {
albums: {
cache: null,
generating: false,
generatedAt: 0,
invalidatedAt: 0
generatedAt: 0
},
users: {
cache: null,
generating: false,
generatedAt: 0,
invalidatedAt: 0
generatedAt: 0
},
uploads: {
cache: null,
generating: false,
generatedAt: 0,
invalidatedAt: 0
generatedAt: 0
}
}
@ -460,11 +457,13 @@ self.bulkDeleteFromDb = async (field, values, user) => {
if (unlinkeds.length) {
// Update albums if necessary, but do not wait
if (albumids.length)
if (albumids.length) {
db.table('albums')
.whereIn('id', albumids)
.update('editedAt', Math.floor(Date.now() / 1000))
.catch(logger.error)
self.invalidateAlbumsCache(albumids)
}
// Purge Cloudflare's cache if necessary, but do not wait
if (config.cloudflare.purgeCache)
@ -575,12 +574,11 @@ self.invalidateAlbumsCache = albumids => {
delete self.albumsCache[albumid]
delete self.albumsCache[`${albumid}-nojs`]
}
self.invalidateStatsCache('albums')
}
self.invalidateStatsCache = type => {
if (!['albums', 'users', 'uploads'].includes(type)) return
statsCache[type].invalidatedAt = Date.now()
statsCache[type].cache = null
}
self.stats = async (req, res, next) => {
@ -777,7 +775,7 @@ self.stats = async (req, res, next) => {
// Uploads
if (!statsCache.uploads.cache && statsCache.uploads.generating) {
stats.uploads = false
} else if ((statsCache.uploads.invalidatedAt < statsCache.uploads.generatedAt) || statsCache.uploads.generating) {
} else if (statsCache.uploads.cache) {
stats.uploads = statsCache.uploads.cache
} else {
statsCache.uploads.generating = true
@ -834,7 +832,7 @@ self.stats = async (req, res, next) => {
// Users
if (!statsCache.users.cache && statsCache.users.generating) {
stats.users = false
} else if ((statsCache.users.invalidatedAt < statsCache.users.generatedAt) || statsCache.users.generating) {
} else if (statsCache.users.cache) {
stats.users = statsCache.users.cache
} else {
statsCache.users.generating = true
@ -877,7 +875,7 @@ self.stats = async (req, res, next) => {
// Albums
if (!statsCache.albums.cache && statsCache.albums.generating) {
stats.albums = false
} else if ((statsCache.albums.invalidatedAt < statsCache.albums.generatedAt) || statsCache.albums.generating) {
} else if (statsCache.albums.cache) {
stats.albums = statsCache.albums.cache
} else {
statsCache.albums.generating = true

View File

@ -31,24 +31,20 @@ routes.get('/a/:identifier', async (req, res, next) => {
if (!utils.albumsCache[cacheid])
utils.albumsCache[cacheid] = {
cache: null,
generating: false,
// Cache will actually be deleted after the album has been updated,
// so storing this timestamp may be redundant, but just in case.
generatedAt: 0
generating: false
}
if (!utils.albumsCache[cacheid].cache && utils.albumsCache[cacheid].generating)
return res.json({
success: false,
description: 'This album is still generating its public page.'
return res.render('album-notice', {
config,
versions: utils.versionStrings,
album,
notice: 'This album\'s public page is still being generated. Please try again later.'
})
else if ((album.editedAt < utils.albumsCache[cacheid].generatedAt) || utils.albumsCache[cacheid].generating)
else if (utils.albumsCache[cacheid].cache)
return res.send(utils.albumsCache[cacheid].cache)
// Use current timestamp to make sure cache is invalidated
// when an album is edited during this generation process.
utils.albumsCache[cacheid].generating = true
utils.albumsCache[cacheid].generatedAt = Math.floor(Date.now() / 1000)
}
const files = await db.table('files')

View File

@ -1,13 +1,15 @@
<noscript>
<section id="noscript" class="hero is-fullheight">
<div class="hero-body">
<div class="container has-text-centered">
<p>This page requires JavaScript to function.</p>
<div class="container has-text-centered is-size-5">
<p>
This page requires JavaScript to function.
</p>
<p>
{%- if noscriptMessage -%}
{{ noscriptMessage | safe }}
{%- else -%}
If you are not redirected to the No-JS uploader, please <a href="nojs">click here</a>.
If you are not automatically redirected to our No-JS uploader, <a href="nojs">click here</a>.
{%- endif -%}
</p>
</div>

29
views/album-notice.njk Normal file
View File

@ -0,0 +1,29 @@
{% set metaTitle %}
{{- album.name | truncate(60, true, '…') -}}
{% endset %}
{% set metaDesc = album.description | striptags | truncate(200, true, '…') %}
{% set metaUrl = '/' + album.url %}
{% extends "_layout.njk" %}
{% block stylesheets %}
<!-- Stylesheets -->
<link rel="stylesheet" href="../libs/bulma/bulma.min.css{{ versions[3] }}">
<link rel="stylesheet" href="../libs/fontello/fontello.css{{ versions[1] }}">
<link rel="stylesheet" href="../css/style.css{{ versions[1] }}">
{% endblock %}
{% block content %}
{{ super() }}
<section class="hero is-fullheight">
<div class="hero-body">
<div class="container has-text-centered is-size-5">
<p>
{{ notice }}
</p>
</div>
</div>
</section>
{% include "_partial/floating-home.njk" %}
{% endblock %}

View File

@ -50,9 +50,9 @@
</h1>
</div>
<div class="level-item">
<h1 id="count" class="subtitle">
<p id="count" class="subtitle">
{{ files.length }} file{{ 's' if files.length !== 1 }} (<span class="file-size">{{ album.totalSize }} B</span>)
</h1>
</p>
</div>
</div>
@ -126,7 +126,7 @@
{% set floatingHomeHref = '..' %}
{% include "_partial/floating-home.njk" %}
{% if not nojs -%}
{% set noscriptMessage = 'If you are not redirected to its No-JS version, please <a href="' + noJsUrl + '">click here</a>.' %}
{% set noscriptMessage = 'If you are not automatically redirected to its No-JS version, <a href="' + noJsUrl + '">click here</a>.' %}
{% include "_partial/noscript.njk" %}
{%- endif %}
{% endblock %}

View File

@ -13,12 +13,12 @@
<section class="section has-extra-bottom-padding">
<div class="container has-text-left">
<h1 class='title'>Cookie Policy</h1>
<h1 class="subtitle">
<p class="subtitle">
{{ globals.whole_cookie }} may use cookies. This page describe how and why we use cookies.
</h1>
</p>
<hr>
<h2 class='subtitle is-brighter'>What are cookies? -or- How to disable cookies?</h2>
<h3 class="subtitle is-brighter">What are cookies? -or- How to disable cookies?</h3>
<article class="message">
<div class="message-body">
<strong>Simply put, a cookie is a technology for remembering something about you.</strong><br>
@ -26,7 +26,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>How We Use Cookies</h2>
<h3 class="subtitle is-brighter">How We Use Cookies</h3>
<article class="message">
<div class="message-body">
We use cookies for a variety of reasons detailed below.<br>
@ -35,14 +35,14 @@
</div>
</article>
<h2 class='subtitle is-brighter'>Account related cookies</h2>
<h3 class="subtitle is-brighter">Account related cookies</h3>
<article class="message">
<div class="message-body">
If you create an account with us, then we will use cookies for the management of the signup process and general administration.
</div>
</article>
<h2 class='subtitle is-brighter'>Login related cookies</h2>
<h3 class="subtitle is-brighter">Login related cookies</h3>
<article class="message">
<div class="message-body">
We use cookies when you are logged in so that we can remember that you are logged in.<br>
@ -51,7 +51,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>What information do we know about you?</h2>
<h3 class="subtitle is-brighter">What information do we know about you?</h3>
<article class="message">
<div class="message-body">
We dont request or require you to provide personal information to access our website.<br>
@ -59,14 +59,14 @@
</div>
</article>
<h2 class='subtitle is-brighter'>What about cookies?</h2>
<h3 class="subtitle is-brighter">What about cookies?</h3>
<article class="message">
<div class="message-body">
{{ globals.whole_cookie }} may place cookies on your browser in order to identify you when you return to our website.
</div>
</article>
<h2 class='subtitle is-brighter'>Cookies We May Use</h2>
<h3 class="subtitle is-brighter">Cookies We May Use</h3>
<article class="message">
<div class="message-body">
We may use the following cookies:<br>
@ -205,7 +205,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>Questions?</h2>
<h3 class="subtitle is-brighter">Questions?</h3>
<article class="message">
<div class="message-body">
For more information, email <a href="mailto:{{ globals.email }}">{{ globals.email }}</a>.

View File

@ -35,9 +35,9 @@
<h1 class="title">
Dashboard
</h1>
<h1 class="subtitle">
<p class="subtitle">
A simple <strong>dashboard</strong>, to sort your uploaded stuff
</h1>
</p>
<hr>
<div class="columns">

View File

@ -17,9 +17,14 @@
{{ super() }}
<section class="section">
<div class="container has-text-left">
<h1 id="general" class='title is-spaced'>General</h1>
<h1 class="title">
Frequently Asked Questions
</h1>
<hr>
<h2 class='subtitle is-brighter'>What is {{ globals.name }}?</h2>
<h2 id="general" class='title is-spaced'>General</h2>
<h3 class="subtitle is-brighter">What is {{ globals.name }}?</h3>
<article class="message">
<div class="message-body">
This is a fork of <a href="https://github.com/WeebDev/lolisafe" target="_blank" rel="noopener">lolisafe</a>.<br>
@ -28,7 +33,7 @@
</article>
{% if globals.enable_faq_banned_categories -%}
<h2 class='subtitle is-brighter'>Are there any <strong>banned categories</strong>?</h2>
<h3 class="subtitle is-brighter">Are there any <strong>banned categories</strong>?</h3>
<article class="message">
<div class="message-body">
Banned categories are the following, <i>but not limited to</i>:<br>
@ -47,7 +52,7 @@
</article>
{%- endif %}
<h2 class='subtitle is-brighter'>Will you keep my uploads forever?</h2>
<h3 class="subtitle is-brighter">Will you keep my uploads forever?</h3>
<article class="message">
<div class="message-body">
Unless the uploads are included within the banned categories above, or some other bullshit, I will.<br>
@ -60,7 +65,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>How can I keep track of my uploads?</h2>
<h3 class="subtitle is-brighter">How can I keep track of my uploads?</h3>
<article class="message">
<div class="message-body">
Simply create a user on the site and every uploads will be associated with your account, granting you access to your uploads through our Dashboard.<br>
@ -68,7 +73,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>What are albums?</h2>
<h3 class="subtitle is-brighter">What are albums?</h3>
<article class="message">
<div class="message-body">
Albums are a simple way of sorting/categorizing uploads together.<br>
@ -78,14 +83,14 @@
</div>
</article>
<h2 class='subtitle is-brighter'>Why should I use this?</h2>
<h3 class="subtitle is-brighter">Why should I use this?</h3>
<article class="message">
<div class="message-body">
I don't know.
</div>
</article>
<h2 class='subtitle is-brighter'>I saw something too illegal for my tastes here, what should I do?</h2>
<h3 class="subtitle is-brighter">I saw something too illegal for my tastes here, what should I do?</h3>
<article class="message">
<div class="message-body">
Send a strongly worded email to <a href="mailto:{{ globals.email }}">{{ globals.email }}</a> and I will try to get back to you within <strong>48 hours</strong>.<br>
@ -94,7 +99,7 @@
</article>
{% if globals.support -%}
<h2 class='subtitle is-brighter'>How can I support {{ globals.name }}?</h2>
<h3 class="subtitle is-brighter">How can I support {{ globals.name }}?</h3>
<article class="message">
<div class="message-body">
{{ globals.support | safe }}
@ -106,9 +111,9 @@
<section class="section">
<div class="container has-text-left">
<h1 id="technical" class='title is-spaced'>Technical</h1>
<h2 id="technical" class='title is-spaced'>Technical</h2>
<h2 class='subtitle is-brighter'>What are the allowed extensions here?</h2>
<h3 class="subtitle is-brighter">What are the allowed extensions here?</h3>
<article class="message">
<div class="message-body">
{% if extensionsFilter.length -%}
@ -127,7 +132,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>Why are my <strong>.htm/.html</strong> uploads being served as plain text?</h2>
<h3 class="subtitle is-brighter">Why are my <strong>.htm/.html</strong> uploads being served as plain text?</h3>
<article class="message">
<div class="message-body">
There had been too many phishing pages being uploaded in the past.
@ -135,7 +140,7 @@
</article>
{% if globals.server_location -%}
<h2 class='subtitle is-brighter'>Where is the server located?</h2>
<h3 class="subtitle is-brighter">Where is the server located?</h3>
<article class="message">
<div class="message-body">
{{ globals.server_location | safe }}.
@ -148,7 +153,7 @@
{%- endif %}
{% if config.cloudflare.purgeCache -%}
<h2 class='subtitle is-brighter'>Since my uploads are cached, what about after I delete them from the dashboard?</h2>
<h3 class="subtitle is-brighter">Since my uploads are cached, what about after I delete them from the dashboard?</h3>
<article class="message">
<div class="message-body">
We will send API requests to Cloudflare to purge their cache immediately after you delete your uploads from the dashboard.<br>
@ -158,7 +163,7 @@
{%- endif %}
{% if globals.enable_faq_tor -%}
<h2 class='subtitle is-brighter'>I cannot access this website with Tor and/or VPNs!?</h2>
<h3 class="subtitle is-brighter">I cannot access this website with Tor and/or VPNs!?</h3>
<article class="message">
<div class="message-body">
My server is actively refreshing and blacklisting Tor exit nodes.<br>
@ -169,7 +174,7 @@
</article>
{%- endif %}
<h2 class='subtitle is-brighter'>Are there any Desktop clients?</h2>
<h3 class="subtitle is-brighter">Are there any Desktop clients?</h3>
<article class="message">
<div class="message-body">
We do have some browser extensions:<br>
@ -187,7 +192,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>Do you have a No-JS uploader form?</h2>
<h3 class="subtitle is-brighter">Do you have a No-JS uploader form?</h3>
<article class="message">
<div class="message-body">
<a href="nojs" target="_blank"><strong>Yes!</strong></a>
@ -195,7 +200,7 @@
</article>
{% if noJsMaxSizeInt and chunkSizeInt -%}
<h2 class='subtitle is-brighter'>Why is the maximum file size in the No-JS uploader form smaller?</h2>
<h3 class="subtitle is-brighter">Why is the maximum file size in the No-JS uploader form smaller?</h3>
<article class="message">
<div class="message-body">
This site is using Cloudflare, which limits the maximum upload size.<br>
@ -205,7 +210,7 @@
{%- endif %}
{% if chunkSizeInt -%}
<h2 class='subtitle is-brighter'>So your API supports chunked uploads?</h2>
<h3 class="subtitle is-brighter">So your API supports chunked uploads?</h3>
<article class="message">
<div class="message-body">
<strong>Yes.</strong> The homepage uploader was coded to chunk uploads into {{ chunkSizeInt }} MB pieces by default. However, this is configurable through its Config tab.<br>
@ -218,7 +223,7 @@
{%- endif %}
{% if globals.fork_repo -%}
<h2 class='subtitle is-brighter'>I found a bug! -or- I want to request a feature!</h2>
<h3 class="subtitle is-brighter">I found a bug! -or- I want to request a feature!</h3>
<article class="message">
<div class="message-body">
Feel free to create a {{ globals.fork_host }} issue <a href="{{ globals.fork_issues }}" target="_blank" rel="noopener">here</a>.</br>
@ -227,7 +232,7 @@
</article>
{%- endif %}
<h2 class='subtitle is-brighter'>How do I delete my own account <strong>and</strong> all the uploads associated with it?</h2>
<h3 class="subtitle is-brighter">How do I delete my own account <strong>and</strong> all the uploads associated with it?</h3>
<article class="message">
<div class="message-body">
For now, you will also have to contact me through my email above.<br>
@ -240,16 +245,16 @@
<section class="section has-extra-bottom-padding">
<div class="container has-text-left">
<h1 id="privacy" class='title is-spaced'>Privacy</h1>
<h2 id="privacy" class='title is-spaced'>Privacy</h2>
<h2 class='subtitle is-brighter'>What information is kept with uploads?</h2>
<h3 class="subtitle is-brighter">What information is kept with uploads?</h3>
<article class="message">
<div class="message-body">
The uploader's <strong>IP address</strong>.
</div>
</article>
<h2 class='subtitle is-brighter'>What information is kept with users?</h2>
<h3 class="subtitle is-brighter">What information is kept with users?</h3>
<article class="message">
<div class="message-body">
Technically, <strong>none</strong>.<br>
@ -259,7 +264,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>Why do you need to keep my IP address?</h2>
<h3 class="subtitle is-brighter">Why do you need to keep my IP address?</h3>
<article class="message">
<div class="message-body">
<strong>Security purposes.</strong><br>
@ -268,7 +273,7 @@
</div>
</article>
<h2 class='subtitle is-brighter'>Does the server have some sort of access logs?</h2>
<h3 class="subtitle is-brighter">Does the server have some sort of access logs?</h3>
<article class="message">
<div class="message-body">
<strong>Yes.</strong><br>
@ -280,7 +285,7 @@
</article>
{% if config.cookiePolicy -%}
<h2 class='subtitle is-brighter'>Cookies</h2>
<h3 class="subtitle is-brighter">Cookies</h3>
<article class="message">
<div class="message-body">
We use cookies to offer you a better browsing experience and to analyze our traffic.<br>
@ -290,7 +295,7 @@
</article>
{%- endif %}
<h2 class='subtitle is-brighter'>I still have more unanswered questions!</h2>
<h3 class="subtitle is-brighter">I still have more unanswered questions!</h3>
<article class="message">
<div class="message-body">
Feel free to email <a href="mailto:{{ globals.email }}">{{ globals.email }}</a>.

View File

@ -52,9 +52,9 @@
<h1 class="title">{{ globals.name }}</h1>
<h2 class="subtitle">{{ globals.home_subtitle | safe }}</h2>
<h3 id="maxSize" class="subtitle">
<p id="maxSize" class="subtitle">
Maximum upload size per file is <span>{{ maxSizeInt }} MB</span>
</h3>
</p>
<div class="columns is-gapless">
<div class="column is-hidden-mobile"></div>

View File

@ -30,9 +30,9 @@
<h1 class="title">{{ globals.name }}</h1>
<h2 class="subtitle">{{ globals.home_subtitle | safe }}</h2>
<h3 class="subtitle" id="maxSize">
<p class="subtitle" id="maxSize">
Maximum total size per upload attempt is {{ noJsMaxSizeInt or maxSizeInt }} MB
</h3>
</p>
<div class="columns is-gapless">
<div class="column is-hidden-mobile"></div>