Skip to content

Commit

Permalink
Merge pull request #56 from Alanimdeo/development
Browse files Browse the repository at this point in the history
v0.12.0
  • Loading branch information
Alanimdeo authored Sep 27, 2024
2 parents 41cd83a + 2fa17f9 commit 3371da0
Show file tree
Hide file tree
Showing 19 changed files with 468 additions and 147 deletions.
3 changes: 2 additions & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@conveyor/backend",
"version": "0.11.1",
"version": "0.12.0",
"author": "Alanimdeo <alan@imdeo.kr>",
"license": "MIT",
"private": false,
Expand All @@ -15,6 +15,7 @@
"@conveyor/types": "*",
"argon2": "^0.41.1",
"better-sqlite3": "^11.3.0",
"better-sqlite3-session-store": "^0.1.0",
"chokidar": "^4.0.1",
"cookie-parser": "~1.4.6",
"debug": "~4.3.7",
Expand Down
18 changes: 18 additions & 0 deletions backend/src/alteration/scripts/0.6.0.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import crypto from "crypto";
import { Database } from "../../modules/db";

export function upgrade(db: Database) {
db.run("PRAGMA journal_mode = DELETE");

db.run("INSERT INTO info (key, value) VALUES (?, ?)", [
"sessionSecret",
crypto.randomBytes(32).toString(),
]);

db.run(
"ALTER TABLE watch_directory_presets ADD COLUMN customParameters TEXT NOT NULL DEFAULT '[]'"
);
db.run(
"ALTER TABLE watch_condition_presets ADD COLUMN customParameters TEXT NOT NULL DEFAULT '[]'"
);
}
20 changes: 16 additions & 4 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import session from "express-session";
import rateLimit from "express-rate-limit";
import { csrf } from "lusca";
import createError from "http-errors";
import createMemoryStore from "memorystore";
import SqliteDatabase from "better-sqlite3";
import createSqliteStore from "better-sqlite3-session-store";
import logger from "morgan";
import { loadDatabase } from "./modules/db";
import { initializeWatchers } from "./modules/watcher";
Expand All @@ -17,7 +18,7 @@ import { alterDatabase } from "./alteration";

dotenv.config();

const MemoryStore = createMemoryStore(session);
const SqliteStore = createSqliteStore(session);

async function main() {
const server = express();
Expand All @@ -40,12 +41,23 @@ async function main() {

const cookieExpiration =
Number(process.env.COOKIE_EXPIRATION) || 30 * 60 * 1000;
const sessionStore = new SqliteStore({
client: db.sql,
expired: {
clear: true,
intervalMs: cookieExpiration,
},
});
const sessionSecret = db.getSettings(["sessionSecret"]).sessionSecret;
server.use(
session({
secret: process.env.SESSION_SECRET || crypto.randomBytes(32).toString(),
secret:
process.env.SESSION_SECRET ||
sessionSecret ||
crypto.randomBytes(32).toString(),
resave: false,
saveUninitialized: false,
store: new MemoryStore({ checkPeriod: cookieExpiration }),
store: sessionStore,
cookie: { maxAge: cookieExpiration, sameSite: "strict" },
})
);
Expand Down
89 changes: 61 additions & 28 deletions backend/src/modules/db.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { existsSync, readdirSync } from "fs";
import path from "path";
import crypto from "crypto";
import SqliteDatabase from "better-sqlite3";
import type { Database as SqliteDatabaseType } from "better-sqlite3";
import semver from "semver";
Expand All @@ -14,6 +15,11 @@ import type {
} from "@conveyor/types";
import { getVersion } from "../alteration";

export type Settings = {
dateFormat: string;
sessionSecret: string;
};

export class Database {
version: string;
sql: SqliteDatabaseType;
Expand All @@ -22,7 +28,6 @@ export class Database {
this.version = getLatestDatabaseVersion();
if (existsSync(filename)) {
this.sql = new SqliteDatabase(filename);
this.sql.pragma("journal_mode = WAL");
return;
}

Expand All @@ -43,24 +48,24 @@ export class Database {

getWatchDirectories() {
const query = this.all<WatchDirectory[]>("SELECT * FROM watch_directories");
return query.map(this.booleanizeWatchDirectory);
return query.map(this.parseWatchDirectory);
}
getWatchDirectoryById(id: number) {
return this.booleanizeWatchDirectory(
return this.parseWatchDirectory(
this.get<WatchDirectory>("SELECT * FROM watch_directories WHERE id=?", [
id,
])
);
}
getWatchDirectoryByPath(path: string) {
return this.booleanizeWatchDirectory(
return this.parseWatchDirectory(
this.get<WatchDirectory>("SELECT * FROM watch_directories WHERE path=?", [
path,
])
);
}

booleanizeWatchDirectory(directory: WatchDirectory) {
parseWatchDirectory(directory: WatchDirectory) {
if (!directory) {
return directory;
}
Expand All @@ -75,25 +80,28 @@ export class Database {
const query = this.all<WatchDirectoryPreset[]>(
"SELECT id, name FROM watch_directory_presets"
);
return query.map(this.booleanizeWatchDirectoryPreset);
return query.map(this.parseWatchDirectoryPreset);
}
getWatchDirectoryPreset(id: number) {
return this.booleanizeWatchDirectoryPreset(
return this.parseWatchDirectoryPreset(
this.get<WatchDirectoryPreset>(
"SELECT * FROM watch_directory_presets WHERE id=?",
[id]
)
);
}

booleanizeWatchDirectoryPreset(preset: WatchDirectoryPreset) {
parseWatchDirectoryPreset(preset: WatchDirectoryPreset) {
if (!preset) {
return preset;
}
preset.enabled = Boolean(preset.enabled);
preset.recursive = Boolean(preset.recursive);
preset.usePolling = Boolean(preset.usePolling);
preset.ignoreDotFiles = Boolean(preset.ignoreDotFiles);
if (typeof preset.customParameters === "string") {
preset.customParameters = JSON.parse(preset.customParameters);
}
return preset;
}

Expand Down Expand Up @@ -123,17 +131,17 @@ export class Database {
}
const params = directoryId ? [directoryId] : [];
const result = this.all<WatchCondition[]>(sql, params);
return result.map(this.booleanizeWatchCondition);
return result.map(this.parseWatchCondition);
}
getWatchCondition(id: number) {
return this.booleanizeWatchCondition(
return this.parseWatchCondition(
this.get<WatchCondition>("SELECT * FROM watch_conditions WHERE id=?", [
id,
])
);
}

booleanizeWatchCondition(condition: WatchCondition) {
parseWatchCondition(condition: WatchCondition) {
if (!condition) {
return condition;
}
Expand All @@ -152,18 +160,18 @@ export class Database {
const query = this.all<WatchConditionPreset[]>(
"SELECT id, name FROM watch_condition_presets"
);
return query.map(this.booleanizeWatchConditionPreset);
return query.map(this.parseWatchConditionPreset);
}
getWatchConditionPreset(id: number) {
return this.booleanizeWatchConditionPreset(
return this.parseWatchConditionPreset(
this.get<WatchConditionPreset>(
"SELECT * FROM watch_condition_presets WHERE id=?",
[id]
)
);
}

booleanizeWatchConditionPreset(preset: WatchConditionPreset) {
parseWatchConditionPreset(preset: WatchConditionPreset) {
if (!preset) {
return preset;
}
Expand All @@ -172,6 +180,9 @@ export class Database {
if (preset.renamePattern && typeof preset.renamePattern === "string") {
preset.renamePattern = JSON.parse(preset.renamePattern);
}
if (typeof preset.customParameters === "string") {
preset.customParameters = JSON.parse(preset.customParameters);
}
return preset;
}

Expand Down Expand Up @@ -217,7 +228,7 @@ export class Database {
}

addWatchDirectoryPreset(preset: Omit<WatchDirectoryPreset, "id">) {
const sql = `INSERT INTO watch_directory_presets (name, enabled, path, recursive, usePolling, interval, ignoreDotFiles) VALUES (?, ?, ?, ?, ?, ?, ?)`;
const sql = `INSERT INTO watch_directory_presets (name, enabled, path, recursive, usePolling, interval, ignoreDotFiles, customParameters) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`;
this.run(sql, [
preset.name,
preset.enabled,
Expand All @@ -226,13 +237,14 @@ export class Database {
preset.usePolling,
preset.interval || null,
preset.ignoreDotFiles,
preset.customParameters ? JSON.stringify(preset.customParameters) : null,
]);
}
updateWatchDirectoryPreset(
id: number,
preset: Omit<WatchDirectoryPreset, "id"> | WatchDirectoryPreset
) {
const sql = `UPDATE watch_directory_presets SET name=?, enabled=?, path=?, recursive=?, usePolling=?, interval=?, ignoreDotFiles=? WHERE id=?`;
const sql = `UPDATE watch_directory_presets SET name=?, enabled=?, path=?, recursive=?, usePolling=?, interval=?, ignoreDotFiles=?, customParameters=? WHERE id=?`;
this.run(sql, [
preset.name,
preset.enabled,
Expand All @@ -241,6 +253,7 @@ export class Database {
preset.usePolling,
preset.interval || null,
preset.ignoreDotFiles,
preset.customParameters ? JSON.stringify(preset.customParameters) : null,
id,
]);
}
Expand Down Expand Up @@ -287,7 +300,7 @@ export class Database {
}

addWatchConditionPreset(preset: Omit<WatchConditionPreset, "id">) {
const sql = `INSERT INTO watch_condition_presets (name, enabled, priority, type, useRegExp, pattern, destination, delay, renamePattern) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const sql = `INSERT INTO watch_condition_presets (name, enabled, priority, type, useRegExp, pattern, destination, delay, renamePattern, customParameters) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
this.run(sql, [
preset.name,
preset.enabled,
Expand All @@ -298,13 +311,14 @@ export class Database {
preset.destination,
preset.delay,
preset.renamePattern ? JSON.stringify(preset.renamePattern) : null,
preset.customParameters ? JSON.stringify(preset.customParameters) : null,
]);
}
updateWatchConditionPreset(
id: number,
preset: Omit<WatchConditionPreset, "id"> | WatchConditionPreset
) {
const sql = `UPDATE watch_condition_presets SET name=?, enabled=?, priority=?, type=?, useRegExp=?, pattern=?, destination=?, delay=?, renamePattern=? WHERE id=?`;
const sql = `UPDATE watch_condition_presets SET name=?, enabled=?, priority=?, type=?, useRegExp=?, pattern=?, destination=?, delay=?, renamePattern=?, customParameters=? WHERE id=?`;
this.run(sql, [
preset.name,
preset.enabled,
Expand All @@ -315,6 +329,7 @@ export class Database {
preset.destination,
preset.delay,
preset.renamePattern ? JSON.stringify(preset.renamePattern) : null,
preset.customParameters ? JSON.stringify(preset.customParameters) : null,
id,
]);
}
Expand Down Expand Up @@ -439,15 +454,29 @@ export class Database {
this.run(sql, [hash]);
}

getSettings() {
const sql = "SELECT value AS dateFormat FROM info WHERE key = 'dateFormat'";
const result = this.get<{ dateFormat: string }>(sql);
return result;
}
updateSettings(settings: { dateFormat?: string }) {
getSettings(keys: (keyof Settings)[] = ["dateFormat", "sessionSecret"]) {
const sql = `SELECT * FROM info WHERE key IN ('${keys.join("', '")}')`;
const result = this.all<{ key: keyof Settings; value: string }[]>(sql);
const settings: Partial<Settings> = {};
for (const { key, value } of result) {
settings[key] = value;
}
return settings;
}
updateSettings(settings: Partial<Settings>) {
const sql: ([key, value]: [string, string]) => [string, string[]] = ([
key,
value,
]) => [`UPDATE info SET value = ? WHERE key = '${key}'`, [value]];
const updates = [];
if (settings.dateFormat) {
const sql = "UPDATE info SET value = ? WHERE key = 'dateFormat'";
this.run(sql, [settings.dateFormat]);
updates.push(sql(["dateFormat", settings.dateFormat]));
}
if (settings.sessionSecret) {
updates.push(sql(["sessionSecret", settings.sessionSecret]));
}
for (const [query, params] of updates) {
this.run(query, params);
}
}

Expand Down Expand Up @@ -534,22 +563,26 @@ const createTable: {
"dateFormat",
"yyyy-MM-dd hh:mm:ss a",
]);
db.run("INSERT INTO info (key, value) VALUES (?, ?)", [
"sessionSecret",
crypto.randomBytes(32).toString(),
]);
},
watch_directories: async (db: Database) =>
db.run(
"CREATE TABLE watch_directories (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT '', enabled INTEGER NOT NULL DEFAULT 1, path TEXT NOT NULL, recursive INTEGER NOT NULL DEFAULT 1, usePolling INTEGER NOT NULL DEFAULT 0, interval INTEGER, ignoreDotFiles INTEGER NOT NULL DEFAULT 1)"
),
watch_directory_presets: async (db: Database) =>
db.run(
"CREATE TABLE watch_directory_presets (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT '', enabled INTEGER NOT NULL DEFAULT 1, path TEXT NOT NULL, recursive INTEGER NOT NULL DEFAULT 1, usePolling INTEGER NOT NULL DEFAULT 0, interval INTEGER, ignoreDotFiles INTEGER NOT NULL DEFAULT 1)"
"CREATE TABLE watch_directory_presets (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT '', enabled INTEGER NOT NULL DEFAULT 1, path TEXT NOT NULL, recursive INTEGER NOT NULL DEFAULT 1, usePolling INTEGER NOT NULL DEFAULT 0, interval INTEGER, ignoreDotFiles INTEGER NOT NULL DEFAULT 1, customParameters TEXT NOT NULL DEFAULT '[]')"
),
watch_conditions: async (db: Database) =>
db.run(
"CREATE TABLE watch_conditions (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT '', directoryId INTEGER NOT NULL, enabled INTEGER NOT NULL DEFAULT 1, priority INTEGER NOT NULL DEFAULT 0, type TEXT NOT NULL DEFAULT 'all', useRegExp INTEGER NOT NULL DEFAULT 0, pattern TEXT NOT NULL, destination TEXT NOT NULL, delay INTEGER NOT NULL DEFAULT 0, renamePattern TEXT, FOREIGN KEY(directoryId) REFERENCES watch_directories(id))"
),
watch_condition_presets: async (db: Database) =>
db.run(
"CREATE TABLE watch_condition_presets (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT '', enabled INTEGER NOT NULL DEFAULT 1, priority INTEGER NOT NULL DEFAULT 0, type TEXT NOT NULL DEFAULT 'all', useRegExp INTEGER NOT NULL DEFAULT 0, pattern TEXT NOT NULL, destination TEXT NOT NULL, delay INTEGER NOT NULL DEFAULT 0, renamePattern TEXT)"
"CREATE TABLE watch_condition_presets (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL DEFAULT '', enabled INTEGER NOT NULL DEFAULT 1, priority INTEGER NOT NULL DEFAULT 0, type TEXT NOT NULL DEFAULT 'all', useRegExp INTEGER NOT NULL DEFAULT 0, pattern TEXT NOT NULL, destination TEXT NOT NULL, delay INTEGER NOT NULL DEFAULT 0, renamePattern TEXT, customParameters TEXT NOT NULL DEFAULT '[]')"
),
logs: async (db: Database) =>
db.run(
Expand Down
Loading

0 comments on commit 3371da0

Please sign in to comment.