Compare commits
No commits in common. "d530dd8cb6dcb96cd745a866782fe2e3a7eb58c3" and "25343a22c5e70ddc917d33e2009600c81dab3613" have entirely different histories.
d530dd8cb6
...
25343a22c5
15 changed files with 479 additions and 664 deletions
|
@ -53,12 +53,6 @@ export const UserSchema = z.object({
|
||||||
|
|
||||||
export type User = z.infer<typeof UserSchema>;
|
export type User = z.infer<typeof UserSchema>;
|
||||||
|
|
||||||
export const UserSettingSchema = z.object({
|
|
||||||
fileDeepLinkRegex: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type UserSetting = z.infer<typeof UserSettingSchema>;
|
|
||||||
|
|
||||||
export const SchemaMigrationSchema = z.object({
|
export const SchemaMigrationSchema = z.object({
|
||||||
version: z.string().nullable(),
|
version: z.string().nullable(),
|
||||||
dirty: z.boolean(),
|
dirty: z.boolean(),
|
||||||
|
|
|
@ -45,11 +45,6 @@ export interface Users {
|
||||||
username: string | null;
|
username: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserSettings {
|
|
||||||
username: string;
|
|
||||||
settings: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DB {
|
export interface DB {
|
||||||
doc_tag_relation: DocTagRelation;
|
doc_tag_relation: DocTagRelation;
|
||||||
document: Document;
|
document: Document;
|
||||||
|
@ -57,5 +52,4 @@ export interface DB {
|
||||||
schema_migration: SchemaMigration;
|
schema_migration: SchemaMigration;
|
||||||
tags: Tags;
|
tags: Tags;
|
||||||
users: Users;
|
users: Users;
|
||||||
user_settings: UserSettings;
|
|
||||||
}
|
}
|
||||||
|
|
50
packages/server/gen_conf_schema.ts
Normal file
50
packages/server/gen_conf_schema.ts
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// import { promises } from "fs";
|
||||||
|
// const { readdir, writeFile } = promises;
|
||||||
|
// import { dirname, join } from "path";
|
||||||
|
// import { createGenerator } from "ts-json-schema-generator";
|
||||||
|
|
||||||
|
// async function genSchema(path: string, typename: string) {
|
||||||
|
// const gen = createGenerator({
|
||||||
|
// path: path,
|
||||||
|
// type: typename,
|
||||||
|
// tsconfig: "tsconfig.json",
|
||||||
|
// });
|
||||||
|
// const schema = gen.createSchema(typename);
|
||||||
|
// if (schema.definitions != undefined) {
|
||||||
|
// const definitions = schema.definitions;
|
||||||
|
// const definition = definitions[typename];
|
||||||
|
// if (typeof definition == "object") {
|
||||||
|
// let property = definition.properties;
|
||||||
|
// if (property) {
|
||||||
|
// property["$schema"] = {
|
||||||
|
// type: "string",
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// const text = JSON.stringify(schema);
|
||||||
|
// await writeFile(join(dirname(path), `${typename}.schema.json`), text);
|
||||||
|
// }
|
||||||
|
// function capitalize(s: string) {
|
||||||
|
// return s.charAt(0).toUpperCase() + s.slice(1);
|
||||||
|
// }
|
||||||
|
// async function setToALL(path: string) {
|
||||||
|
// console.log(`scan ${path}`);
|
||||||
|
// const direntry = await readdir(path, { withFileTypes: true });
|
||||||
|
// const works = direntry
|
||||||
|
// .filter((x) => x.isFile() && x.name.endsWith("Config.ts"))
|
||||||
|
// .map((x) => {
|
||||||
|
// const name = x.name;
|
||||||
|
// const m = /(.+)\.ts/.exec(name);
|
||||||
|
// if (m !== null) {
|
||||||
|
// const typename = m[1];
|
||||||
|
// return genSchema(join(path, typename), capitalize(typename));
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// await Promise.all(works);
|
||||||
|
// const subdir = direntry.filter((x) => x.isDirectory()).map((x) => x.name);
|
||||||
|
// for (const x of subdir) {
|
||||||
|
// await setToALL(join(path, x));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// setToALL("src");
|
|
@ -1,18 +0,0 @@
|
||||||
import { Kysely, sql } from 'kysely';
|
|
||||||
|
|
||||||
export async function up(db: Kysely<any>) {
|
|
||||||
await db.schema
|
|
||||||
.createTable("user_settings")
|
|
||||||
.addColumn("username", "varchar(256)", col => col.notNull().primaryKey())
|
|
||||||
.addColumn("settings", "json", col => col.notNull())
|
|
||||||
.addForeignKeyConstraint("user_settings_username_fk", ["username"], "users", ["username"])
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
await db.updateTable("schema_migration")
|
|
||||||
.set({ version: "2025-06-26", dirty: 0 })
|
|
||||||
.execute();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function down(db: Kysely<any>) {
|
|
||||||
throw new Error('Downward migrations are not supported. Restore from backup.');
|
|
||||||
}
|
|
|
@ -65,8 +65,8 @@ export async function up(db: Kysely<any>) {
|
||||||
await db
|
await db
|
||||||
.insertInto('schema_migration')
|
.insertInto('schema_migration')
|
||||||
.values({
|
.values({
|
||||||
version: '2024-12-27',
|
version: '0.0.1',
|
||||||
dirty: 0,
|
dirty: false,
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
|
|
|
@ -6,37 +6,37 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/app.ts",
|
"dev": "tsx watch src/app.ts",
|
||||||
"start": "tsx src/app.ts",
|
"start": "tsx src/app.ts"
|
||||||
"migrate": "tsx tools/migration.ts"
|
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@std/async": "npm:@jsr/std__async@^1.0.13",
|
"@std/async": "npm:@jsr/std__async@^1.0.12",
|
||||||
"@zip.js/zip.js": "^2.7.62",
|
"@zip.js/zip.js": "^2.7.60",
|
||||||
"better-sqlite3": "^9.6.0",
|
"better-sqlite3": "^9.6.0",
|
||||||
"chokidar": "^3.6.0",
|
"chokidar": "^3.6.0",
|
||||||
"dbtype": "workspace:dbtype",
|
"dbtype": "workspace:dbtype",
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.4.5",
|
||||||
"jose": "^5.10.0",
|
"jose": "^5.9.3",
|
||||||
"koa": "^2.16.1",
|
"koa": "^2.15.3",
|
||||||
"koa-bodyparser": "^4.4.1",
|
"koa-bodyparser": "^4.4.1",
|
||||||
"koa-compose": "^4.1.0",
|
"koa-compose": "^4.1.0",
|
||||||
"koa-router": "^12.0.1",
|
"koa-router": "^12.0.1",
|
||||||
"kysely": "^0.27.6",
|
"kysely": "^0.27.4",
|
||||||
"natural-orderby": "^2.0.3",
|
"natural-orderby": "^2.0.3",
|
||||||
"tiny-async-pool": "^1.3.0"
|
"tiny-async-pool": "^1.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/better-sqlite3": "^7.6.13",
|
"@types/better-sqlite3": "^7.6.11",
|
||||||
"@types/jsonwebtoken": "^8.5.9",
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
"@types/koa": "^2.15.0",
|
"@types/koa": "^2.15.0",
|
||||||
"@types/koa-bodyparser": "^4.3.12",
|
"@types/koa-bodyparser": "^4.3.12",
|
||||||
"@types/koa-compose": "^3.2.8",
|
"@types/koa-compose": "^3.2.8",
|
||||||
"@types/koa-router": "^7.4.8",
|
"@types/koa-router": "^7.4.8",
|
||||||
"@types/node": "^22.15.33",
|
"@types/node": "^22.15.3",
|
||||||
"@types/tiny-async-pool": "^1.0.5",
|
"@types/tiny-async-pool": "^1.0.5",
|
||||||
"tsx": "^4.20.3",
|
"nodemon": "^3.1.7",
|
||||||
"typescript": "^5.8.3"
|
"tsx": "^4.19.1",
|
||||||
|
"typescript": "^5.6.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
packages/server/preload.ts
Normal file
7
packages/server/preload.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// import { contextBridge, ipcRenderer } from "electron";
|
||||||
|
|
||||||
|
// contextBridge.exposeInMainWorld("electron", {
|
||||||
|
// passwordReset: async (username: string, toPw: string) => {
|
||||||
|
// return await ipcRenderer.invoke("reset_password", username, toPw);
|
||||||
|
// },
|
||||||
|
// });
|
|
@ -1,16 +1,14 @@
|
||||||
import { Kysely } from "kysely";
|
import { Kysely } from "kysely";
|
||||||
import { getKysely } from "./db/kysely.ts";
|
import { getKysely } from "./db/kysely.ts";
|
||||||
import fs from "node:fs/promises";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
export async function connectDB() {
|
export async function connectDB() {
|
||||||
const kysely = getKysely();
|
const kysely = getKysely();
|
||||||
|
|
||||||
let tries = 0;
|
let tries = 0;
|
||||||
for (; ;) {
|
for (;;) {
|
||||||
try {
|
try {
|
||||||
console.log("try to connect db");
|
console.log("try to connect db");
|
||||||
await kysely.selectNoFrom(eb => eb.val(1).as("dummy")).execute();
|
await kysely.selectNoFrom(eb=> eb.val(1).as("dummy")).execute();
|
||||||
console.log("connect success");
|
console.log("connect success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (tries < 3) {
|
if (tries < 3) {
|
||||||
|
@ -27,72 +25,20 @@ export async function connectDB() {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkTableExists(kysely: Kysely<any>, table: string) {
|
async function checkTableExists(kysely: Kysely<any>, table: string) {
|
||||||
const result = await kysely.selectFrom("sqlite_master")
|
const result = await kysely.selectFrom("sqlite_master").where("type", "=", "table").where("name", "=", table).executeTakeFirst();
|
||||||
.selectAll()
|
|
||||||
.where("type", "=", "table")
|
|
||||||
.where("name", "=", table)
|
|
||||||
.executeTakeFirst();
|
|
||||||
return result !== undefined;
|
return result !== undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function migrateDB() {
|
export async function migrateDB() {
|
||||||
const kysely = getKysely();
|
const kysely = getKysely();
|
||||||
|
let version_number = 0;
|
||||||
// is schema_migration exists?
|
// is schema_migration exists?
|
||||||
const hasTable = await checkTableExists(kysely, "schema_migration");
|
const hasTable = await checkTableExists(kysely, "schema_migration");
|
||||||
if (!hasTable) {
|
if (!hasTable) {
|
||||||
// 2. 마이그레이션 실행 (최초 마이그레이션)
|
// migrate from 0
|
||||||
const migration = await import("../migrations/2024-12-27.ts");
|
// create schema_migration
|
||||||
await migration.up(kysely);
|
|
||||||
console.log("최초 마이그레이션 완료");
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 현재 버전 확인
|
const version = await kysely.selectFrom("schema_migration").executeTakeFirst();
|
||||||
const row = await kysely.selectFrom("schema_migration").selectAll().executeTakeFirst();
|
|
||||||
const currentVersion = row?.version ?? "0001-01-01"; // 기본값 설정
|
|
||||||
|
|
||||||
// 마이그레이션 목록 정의 (버전순 정렬 필수)
|
|
||||||
const migrations = await readMigrations();
|
|
||||||
|
|
||||||
// 현재 버전보다 높은 migration만 실행
|
|
||||||
let lastestVersion = currentVersion;
|
|
||||||
console.log(`현재 DB 버전: ${currentVersion}`);
|
|
||||||
for (const m of migrations) {
|
|
||||||
if (compareVersion(m.version, currentVersion) > 0) {
|
|
||||||
console.log(`마이그레이션 실행: ${m.version}`);
|
|
||||||
const migration = await import(m.file);
|
|
||||||
await migration.up(kysely);
|
|
||||||
await kysely.updateTable("schema_migration")
|
|
||||||
.set({ version: m.version, dirty: 0 })
|
|
||||||
.execute();
|
|
||||||
lastestVersion = m.version;
|
|
||||||
console.log(`마이그레이션 완료: ${m.version}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lastestVersion !== currentVersion) {
|
|
||||||
console.log(`마이그레이션 완료. ${currentVersion} -> ${lastestVersion}`);
|
|
||||||
} else {
|
|
||||||
console.log("마이그레이션 필요 없음");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readMigrations(): Promise<{ version: string; file: string }[]> {
|
|
||||||
const migrationsDir = path.join(import.meta.dirname, "../migrations");
|
|
||||||
const files = (await fs.readdir(migrationsDir))
|
|
||||||
.filter(file => file.endsWith(".ts"))
|
|
||||||
.map(file => {
|
|
||||||
const version = file.match(/(\d{4}-\d{2}-\d{2})/)?.[0] || "0001-01-01";
|
|
||||||
return { version, file: `../migrations/${file}` };
|
|
||||||
});
|
|
||||||
return files.sort((a, b) => compareVersion(a.version, b.version));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Date 기반 버전 비교 함수.
|
|
||||||
function compareVersion(a: string, b: string): number {
|
|
||||||
const dateA = new Date(a);
|
|
||||||
const dateB = new Date(b);
|
|
||||||
if (dateA < dateB) return -1;
|
|
||||||
if (dateA > dateB) return 1;
|
|
||||||
return 0; // 같을 경우
|
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { getKysely } from "./kysely.ts";
|
import { getKysely } from "./kysely.ts";
|
||||||
import { type IUser, IUserSettings, Password, type UserAccessor, type UserCreateInput } from "../model/user.ts";
|
import { type IUser, Password, type UserAccessor, type UserCreateInput } from "../model/user.ts";
|
||||||
|
|
||||||
class SqliteUser implements IUser {
|
class SqliteUser implements IUser {
|
||||||
readonly username: string;
|
readonly username: string;
|
||||||
|
@ -41,24 +41,6 @@ class SqliteUser implements IUser {
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
return (result.numDeletedRows ?? 0n) > 0;
|
return (result.numDeletedRows ?? 0n) > 0;
|
||||||
}
|
}
|
||||||
async get_settings(): Promise<IUserSettings | undefined> {
|
|
||||||
const settings = await this.kysely
|
|
||||||
.selectFrom("user_settings")
|
|
||||||
.select("settings")
|
|
||||||
.where("username", "=", this.username)
|
|
||||||
.executeTakeFirst();
|
|
||||||
if (!settings) return undefined;
|
|
||||||
return settings.settings ? JSON.parse(settings.settings) as IUserSettings : undefined;
|
|
||||||
}
|
|
||||||
async set_settings(settings: IUserSettings) {
|
|
||||||
const settingsJson = JSON.stringify(settings);
|
|
||||||
const result = await this.kysely
|
|
||||||
.insertInto("user_settings")
|
|
||||||
.values({ username: this.username, settings: settingsJson })
|
|
||||||
.onConflict((oc) => oc.doUpdateSet({ settings: settingsJson }))
|
|
||||||
.executeTakeFirst();
|
|
||||||
return (result.numInsertedOrUpdatedRows ?? 0n) > 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createSqliteUserController = (kysely = getKysely()): UserAccessor => {
|
export const createSqliteUserController = (kysely = getKysely()): UserAccessor => {
|
||||||
|
|
|
@ -76,7 +76,7 @@ function setToken(ctx: Koa.Context, token_name: string, token_payload: string |
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createLoginHandler = (userController: UserAccessor) => async (ctx: Koa.Context, _next: Koa.Next) => {
|
export const createLoginMiddleware = (userController: UserAccessor) => async (ctx: Koa.Context, _next: Koa.Next) => {
|
||||||
const setting = get_setting();
|
const setting = get_setting();
|
||||||
const secretKey = setting.jwt_secretkey;
|
const secretKey = setting.jwt_secretkey;
|
||||||
const body = ctx.request.body;
|
const body = ctx.request.body;
|
||||||
|
@ -115,7 +115,7 @@ export const createLoginHandler = (userController: UserAccessor) => async (ctx:
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const LogoutHandler = (ctx: Koa.Context, _next: Koa.Next) => {
|
export const LogoutMiddleware = (ctx: Koa.Context, next: Koa.Next) => {
|
||||||
const setting = get_setting();
|
const setting = get_setting();
|
||||||
ctx.cookies.set(accessTokenName, null);
|
ctx.cookies.set(accessTokenName, null);
|
||||||
ctx.cookies.set(refreshTokenName, null);
|
ctx.cookies.set(refreshTokenName, null);
|
||||||
|
@ -127,9 +127,9 @@ export const LogoutHandler = (ctx: Koa.Context, _next: Koa.Next) => {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createUserHandler =
|
export const createUserMiddleWare =
|
||||||
(userController: UserAccessor) => async (ctx: Koa.ParameterizedContext<UserState>, next: Koa.Next) => {
|
(userController: UserAccessor) => async (ctx: Koa.ParameterizedContext<UserState>, next: Koa.Next) => {
|
||||||
const refreshToken = makeRefreshToken(userController);
|
const refreshToken = refreshTokenHandler(userController);
|
||||||
const setting = get_setting();
|
const setting = get_setting();
|
||||||
const setGuest = async () => {
|
const setGuest = async () => {
|
||||||
setToken(ctx, accessTokenName, null, 0);
|
setToken(ctx, accessTokenName, null, 0);
|
||||||
|
@ -140,7 +140,7 @@ export const createUserHandler =
|
||||||
return await refreshToken(ctx, setGuest, next);
|
return await refreshToken(ctx, setGuest, next);
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeRefreshToken = (cntr: UserAccessor) => async (ctx: Koa.Context, fail: Koa.Next, next: Koa.Next) => {
|
const refreshTokenHandler = (cntr: UserAccessor) => async (ctx: Koa.Context, fail: Koa.Next, next: Koa.Next) => {
|
||||||
const accessPayload = ctx.cookies.get(accessTokenName);
|
const accessPayload = ctx.cookies.get(accessTokenName);
|
||||||
const setting = get_setting();
|
const setting = get_setting();
|
||||||
const secretKey = setting.jwt_secretkey;
|
const secretKey = setting.jwt_secretkey;
|
||||||
|
@ -200,7 +200,7 @@ const makeRefreshToken = (cntr: UserAccessor) => async (ctx: Koa.Context, fail:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
export const createRefreshTokenMiddleware = (cntr: UserAccessor) => async (ctx: Koa.Context, next: Koa.Next) => {
|
export const createRefreshTokenMiddleware = (cntr: UserAccessor) => async (ctx: Koa.Context, next: Koa.Next) => {
|
||||||
const handler = makeRefreshToken(cntr);
|
const handler = refreshTokenHandler(cntr);
|
||||||
await handler(ctx, fail, success);
|
await handler(ctx, fail, success);
|
||||||
async function fail() {
|
async function fail() {
|
||||||
const user = ctx.state.user as PayloadInfo;
|
const user = ctx.state.user as PayloadInfo;
|
||||||
|
@ -242,54 +242,12 @@ export const resetPasswordMiddleware = (cntr: UserAccessor) => async (ctx: Koa.C
|
||||||
ctx.type = "json";
|
ctx.type = "json";
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getUserSettingHandler(userController: UserAccessor) {
|
|
||||||
return async (ctx: Koa.ParameterizedContext<UserState>, next: Koa.Next) => {
|
|
||||||
const username = ctx.state.user.username;
|
|
||||||
if (!username) {
|
|
||||||
return sendError(403, "not authorized");
|
|
||||||
}
|
|
||||||
const user = await userController.findUser(username);
|
|
||||||
if (user === undefined) {
|
|
||||||
return sendError(403, "not authorized");
|
|
||||||
}
|
|
||||||
const settings = await user.get_settings();
|
|
||||||
if (settings === undefined) {
|
|
||||||
ctx.body = {};
|
|
||||||
ctx.type = "json";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
ctx.body = settings;
|
|
||||||
ctx.type = "json";
|
|
||||||
await next();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
export function setUserSettingHandler(userController: UserAccessor) {
|
|
||||||
return async (ctx: Koa.ParameterizedContext<UserState>, next: Koa.Next) => {
|
|
||||||
const username = ctx.state.user.username;
|
|
||||||
if (!username) {
|
|
||||||
return sendError(403, "not authorized");
|
|
||||||
}
|
|
||||||
const user = await userController.findUser(username);
|
|
||||||
if (user === undefined) {
|
|
||||||
return sendError(403, "not authorized");
|
|
||||||
}
|
|
||||||
const body = ctx.request.body;
|
|
||||||
const settings = body as Record<string, unknown>;
|
|
||||||
await user.set_settings(settings);
|
|
||||||
ctx.body = { ok: true };
|
|
||||||
ctx.type = "json";
|
|
||||||
await next();
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createLoginRouter(userController: UserAccessor) {
|
export function createLoginRouter(userController: UserAccessor) {
|
||||||
const router = new Router();
|
const router = new Router();
|
||||||
router.post("/login", createLoginHandler(userController));
|
router.post("/login", createLoginMiddleware(userController));
|
||||||
router.post("/logout", LogoutHandler);
|
router.post("/logout", LogoutMiddleware);
|
||||||
router.post("/refresh", createRefreshTokenMiddleware(userController));
|
router.post("/refresh", createRefreshTokenMiddleware(userController));
|
||||||
router.post("/reset", resetPasswordMiddleware(userController));
|
router.post("/reset", resetPasswordMiddleware(userController));
|
||||||
router.get("/settings", getUserSettingHandler(userController));
|
|
||||||
router.post("/settings", setUserSettingHandler(userController));
|
|
||||||
return router;
|
return router;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { UserSetting } from "dbtype";
|
|
||||||
import { createHmac, randomBytes } from "node:crypto";
|
import { createHmac, randomBytes } from "node:crypto";
|
||||||
|
|
||||||
function hashForPassword(salt: string, password: string) {
|
function hashForPassword(salt: string, password: string) {
|
||||||
|
@ -42,8 +41,6 @@ export interface UserCreateInput {
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IUserSettings = UserSetting;
|
|
||||||
|
|
||||||
export interface IUser {
|
export interface IUser {
|
||||||
readonly username: string;
|
readonly username: string;
|
||||||
readonly password: Password;
|
readonly password: Password;
|
||||||
|
@ -68,18 +65,6 @@ export interface IUser {
|
||||||
* @param password password to set
|
* @param password password to set
|
||||||
*/
|
*/
|
||||||
reset_password(password: string): Promise<void>;
|
reset_password(password: string): Promise<void>;
|
||||||
|
|
||||||
/**
|
|
||||||
* get user settings
|
|
||||||
* @returns user settings, or undefined if not set
|
|
||||||
*/
|
|
||||||
get_settings(): Promise<IUserSettings | undefined>;
|
|
||||||
/**
|
|
||||||
* set user settings
|
|
||||||
* @param settings user settings to set
|
|
||||||
* @returns if settings updated, return true
|
|
||||||
*/
|
|
||||||
set_settings(settings: IUserSettings): Promise<boolean>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UserAccessor {
|
export interface UserAccessor {
|
||||||
|
|
|
@ -46,11 +46,10 @@ export const createPermissionCheckMiddleware =
|
||||||
if (!permissions.map((p) => user_permission.includes(p)).every((x) => x)) {
|
if (!permissions.map((p) => user_permission.includes(p)).every((x) => x)) {
|
||||||
if (user.username === "") {
|
if (user.username === "") {
|
||||||
return sendError(401, "you are guest. login needed.");
|
return sendError(401, "you are guest. login needed.");
|
||||||
} return sendError(403, "do not have permission");
|
}return sendError(403, "do not have permission");
|
||||||
}
|
}
|
||||||
await next();
|
await next();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AdminOnlyMiddleware = async (ctx: Koa.ParameterizedContext<UserState>, next: Koa.Next) => {
|
export const AdminOnlyMiddleware = async (ctx: Koa.ParameterizedContext<UserState>, next: Koa.Next) => {
|
||||||
const user = ctx.state.user;
|
const user = ctx.state.user;
|
||||||
if (user.username !== "admin") {
|
if (user.username !== "admin") {
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { get_setting, SettingConfig } from "./SettingConfig.ts";
|
||||||
import { createReadStream, readFileSync } from "node:fs";
|
import { createReadStream, readFileSync } from "node:fs";
|
||||||
import bodyparser from "koa-bodyparser";
|
import bodyparser from "koa-bodyparser";
|
||||||
import { createSqliteDocumentAccessor, createSqliteTagController, createSqliteUserController } from "./db/mod.ts";
|
import { createSqliteDocumentAccessor, createSqliteTagController, createSqliteUserController } from "./db/mod.ts";
|
||||||
import { createLoginRouter, createUserHandler, getAdmin, isAdminFirst } from "./login.ts";
|
import { createLoginRouter, createUserMiddleWare, getAdmin, isAdminFirst } from "./login.ts";
|
||||||
import getContentRouter from "./route/contents.ts";
|
import getContentRouter from "./route/contents.ts";
|
||||||
import { error_handler } from "./route/error_handler.ts";
|
import { error_handler } from "./route/error_handler.ts";
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ class ServerApplication {
|
||||||
}
|
}
|
||||||
app.use(bodyparser());
|
app.use(bodyparser());
|
||||||
app.use(error_handler);
|
app.use(error_handler);
|
||||||
app.use(createUserHandler(this.userController));
|
app.use(createUserMiddleWare(this.userController));
|
||||||
|
|
||||||
const diff_router = createDiffRouter(this.diffManger);
|
const diff_router = createDiffRouter(this.diffManger);
|
||||||
this.diffManger.register("comic", createComicWatcher());
|
this.diffManger.register("comic", createComicWatcher());
|
||||||
|
@ -238,6 +238,8 @@ class ServerApplication {
|
||||||
static async createServer() {
|
static async createServer() {
|
||||||
const db = await connectDB();
|
const db = await connectDB();
|
||||||
|
|
||||||
|
// todo : db migration
|
||||||
|
|
||||||
const app = new ServerApplication({
|
const app = new ServerApplication({
|
||||||
userController: createSqliteUserController(db),
|
userController: createSqliteUserController(db),
|
||||||
documentController: createSqliteDocumentAccessor(db),
|
documentController: createSqliteDocumentAccessor(db),
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
import { migrateDB } from "../src/database.ts";
|
|
||||||
import { config } from "dotenv";
|
|
||||||
|
|
||||||
config(); // Load environment variables from .env file
|
|
||||||
|
|
||||||
export async function runMigration() {
|
|
||||||
try {
|
|
||||||
await migrateDB();
|
|
||||||
console.log("Database migration completed successfully.");
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Database migration failed:", error);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await runMigration();
|
|
814
pnpm-lock.yaml
generated
814
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
Loading…
Add table
Reference in a new issue