From c8d7c664e2418ae09dd9fb1eea3f6e6f47c42404 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Tue, 30 Mar 2021 16:19:37 +0200 Subject: [PATCH] files: replace ttl with expires_at --- src/App.ts | 4 +- .../ReplaceTtlWithExpiresAtFilesTable.ts | 46 +++++++++++++++++++ src/models/FileModel.ts | 21 ++++----- src/models/URLRedirect.ts | 4 +- views/file-uploader.njk | 3 +- 5 files changed, 63 insertions(+), 15 deletions(-) create mode 100644 src/migrations/ReplaceTtlWithExpiresAtFilesTable.ts diff --git a/src/App.ts b/src/App.ts index 080315d..85674c4 100644 --- a/src/App.ts +++ b/src/App.ts @@ -44,6 +44,7 @@ import MakeMagicLinksSessionNotUniqueMigration from "swaf/auth/magic_link/MakeMa import AddPasswordToUsersMigration from "swaf/auth/password/AddPasswordToUsersMigration"; import DropNameFromUsers from "swaf/auth/migrations/DropNameFromUsers"; import packageJson = require('./package.json'); +import ReplaceTtlWithExpiresAtFilesTable from "./migrations/ReplaceTtlWithExpiresAtFilesTable"; export default class App extends Application { public constructor( @@ -69,7 +70,8 @@ export default class App extends Application { AddUsedToMagicLinksMigration, MakeMagicLinksSessionNotUniqueMigration, AddPasswordToUsersMigration, - DropNameFromUsers, + DummyMigration, + ReplaceTtlWithExpiresAtFilesTable, ]; } diff --git a/src/migrations/ReplaceTtlWithExpiresAtFilesTable.ts b/src/migrations/ReplaceTtlWithExpiresAtFilesTable.ts new file mode 100644 index 0000000..fd0be5e --- /dev/null +++ b/src/migrations/ReplaceTtlWithExpiresAtFilesTable.ts @@ -0,0 +1,46 @@ +import Migration from "swaf/db/Migration"; +import FileModel from "../models/FileModel"; +import {logger} from "swaf/Logger"; + +export default class ReplaceTtlWithExpiresAtFilesTable extends Migration { + public async install(): Promise { + await this.query(`ALTER TABLE files + ADD COLUMN expires_at DATETIME NULL`); + + const files = await FileModel.select().get(); + for (const file of files) { + logger.debug(file); + if (file.created_at && typeof file.ttl === 'number' && file.ttl > 0) { + file.expires_at = new Date(file.created_at.getTime() + file.ttl * 1000); + } + const callbacks: (() => Promise)[] = []; + await file.save(this.getCurrentConnection(), callback => { + callbacks.push(callback); + }); + for (const c of callbacks) await c(); + } + + await this.query(`ALTER TABLE files + DROP COLUMN ttl`); + } + + public async rollback(): Promise { + await this.query(`ALTER TABLE files + ADD COLUMN ttl INT UNSIGNED NOT NULL DEFAULT 0`); + + const files = await FileModel.select().get(); + for (const file of files) { + if (file.created_at && file.expires_at) { + file.ttl = Math.ceil((file.expires_at.getTime() - file.created_at.getTime()) / 1000); + } + const callbacks: (() => Promise)[] = []; + await file.save(this.getCurrentConnection(), callback => { + callbacks.push(callback); + }); + for (const c of callbacks) await c(); + } + + await this.query(`ALTER TABLE files + DROP COLUMN expires_at`); + } +} diff --git a/src/models/FileModel.ts b/src/models/FileModel.ts index 19c4652..2b9ee65 100644 --- a/src/models/FileModel.ts +++ b/src/models/FileModel.ts @@ -28,16 +28,22 @@ export default class FileModel extends Model { public readonly storage_path?: string = undefined; public readonly size?: number = undefined; public created_at?: Date = undefined; - public readonly ttl?: number = undefined; + public expires_at?: Date | null = undefined; + + /** + * @deprecated + */ + public ttl?: number = undefined; protected init(): void { this.setValidation('user_id').defined().exists(User, 'id'); - this.setValidation('slug').defined().minLength(1).maxLength(259).unique(FileModel, 'slug').unique(URLRedirect, 'slug'); + this.setValidation('slug').defined().minLength(1).maxLength(259) + .unique(this, 'slug') + .unique(URLRedirect, 'slug'); this.setValidation('real_name').defined().minLength(1).maxLength(259); this.setValidation('storage_type').defined().maxLength(64); this.setValidation('storage_path').defined().maxLength(1745); this.setValidation('size').defined().min(0); - this.setValidation('ttl').defined().min(0).max(4294967295); } public getURL(domain: string = config.get('public_url')): string { @@ -46,15 +52,8 @@ export default class FileModel extends Model { }); } - public getExpirationDate(): Date | null { - if (!this.created_at) return new Date(); - if (this.ttl === 0) return null; - - return new Date(this.created_at.getTime() + this.getOrFail('ttl') * 1000); - } - public shouldBeDeleted(): boolean { - const expirationDate = this.getExpirationDate(); + const expirationDate = this.expires_at; if (!expirationDate) return false; return new Date().getTime() >= expirationDate.getTime(); } diff --git a/src/models/URLRedirect.ts b/src/models/URLRedirect.ts index 1ed2e12..279fa5f 100644 --- a/src/models/URLRedirect.ts +++ b/src/models/URLRedirect.ts @@ -28,7 +28,9 @@ export default class URLRedirect extends Model { protected init(): void { this.setValidation('user_id').defined().exists(User, 'id'); - this.setValidation('slug').defined().minLength(1).maxLength(259).unique(URLRedirect, 'slug').unique(FileModel, 'slug'); + this.setValidation('slug').defined().minLength(1).maxLength(259) + .unique(this, 'slug') + .unique(FileModel, 'slug'); this.setValidation('target_url').defined().maxLength(1745).regexp(/^https?:\/\/.{3,259}?\/?/i); } diff --git a/views/file-uploader.njk b/views/file-uploader.njk index fead61d..c2f4a6f 100644 --- a/views/file-uploader.njk +++ b/views/file-uploader.njk @@ -81,8 +81,7 @@
{{ file.real_name }}
{{ (file.size / (1024 * 1024)).toFixed(2) }}MB - {% set expires_at = file.getExpirationDate() %} - {% if expires_at %}{{ expires_at.toISOString() }}{% else %}Never{% endif %} + {% if file.expires_at %}{{ file.expires_at.toISOString() }}{% else %}Never{% endif %} {% if file.shouldBeDeleted() %} Pending deletion