rewrite website
This commit is contained in:
93
src/db/database.sql
Normal file
93
src/db/database.sql
Normal file
@@ -0,0 +1,93 @@
|
||||
-- admins
|
||||
CREATE TABLE IF NOT EXISTS admin (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(255) NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
permissions INT NOT NULL
|
||||
);
|
||||
|
||||
-- user
|
||||
CREATE TABLE IF NOT EXISTS user (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
firstname VARCHAR(255) NOT NULL,
|
||||
lastname VARCHAR(255) NOT NULL,
|
||||
birthday DATE NOT NULL,
|
||||
telephone VARCHAR(255),
|
||||
username VARCHAR(255) NOT NULL,
|
||||
edition ENUM('java', 'bedrock'),
|
||||
uuid VARCHAR(36)
|
||||
);
|
||||
|
||||
-- blocked user
|
||||
CREATE TABLE IF NOT EXISTS blocked_user (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
uuid VARCHAR(255) UNIQUE NOT NULL,
|
||||
comment TINYTEXT
|
||||
);
|
||||
|
||||
-- report
|
||||
CREATE TABLE IF NOT EXISTS report (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
reason VARCHAR(255) NOT NULL,
|
||||
body TEXT,
|
||||
url_hash VARCHAR(255) NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP,
|
||||
reporter_id INT,
|
||||
reported_id INT,
|
||||
FOREIGN KEY (reporter_id) REFERENCES user(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (reported_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- report attachment
|
||||
CREATE TABLE IF NOT EXISTS report_attachment (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
type ENUM('image', 'video') NOT NULL,
|
||||
hash CHAR(32) NOT NULL,
|
||||
report_id INT NOT NULL,
|
||||
FOREIGN KEY (report_id) REFERENCES report(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- report status
|
||||
CREATE TABLE IF NOT EXISTS report_status (
|
||||
status ENUM('open', 'closed'),
|
||||
notice TEXT,
|
||||
statement TEXT,
|
||||
report_id INT NOT NULL UNIQUE,
|
||||
reviewer_id INT,
|
||||
FOREIGN KEY (report_id) REFERENCES report(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (reviewer_id) REFERENCES admin(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- strike reason
|
||||
CREATE TABLE IF NOT EXISTS strike_reason (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
weight TINYINT NOT NULL
|
||||
);
|
||||
|
||||
-- strike
|
||||
CREATE TABLE IF NOT EXISTS strike (
|
||||
at TIMESTAMP NOT NULL,
|
||||
report_id INT NOT NULL UNIQUE,
|
||||
strike_reason_id INT NOT NULL,
|
||||
FOREIGN KEY (report_id) REFERENCES report(id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (strike_reason_id) REFERENCES strike_reason(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- feedback
|
||||
CREATE TABLE IF NOT EXISTS feedback (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
event VARCHAR(255) NOT NULL,
|
||||
title VARCHAR(255),
|
||||
content TEXT,
|
||||
url_hash VARCHAR(255) NOT NULL UNIQUE,
|
||||
last_changed TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
user_id INT,
|
||||
FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- settings
|
||||
CREATE TABLE IF NOT EXISTS settings (
|
||||
name VARCHAR(255) UNIQUE NOT NULL,
|
||||
value TEXT NOT NULL
|
||||
);
|
||||
235
src/db/database.ts
Normal file
235
src/db/database.ts
Normal file
@@ -0,0 +1,235 @@
|
||||
import { drizzle, type MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import mysql from 'mysql2/promise';
|
||||
import {
|
||||
existsAdmin,
|
||||
admin,
|
||||
type ExistsAdminReq,
|
||||
type GetAdminReq,
|
||||
getAdmins,
|
||||
type AddAdminReq,
|
||||
addAdmin,
|
||||
type EditAdminReq,
|
||||
editAdmin,
|
||||
type DeleteAdminReq,
|
||||
deleteAdmin
|
||||
} from './schema/admin';
|
||||
import {
|
||||
type AddUserReq,
|
||||
type EditUserReq,
|
||||
type DeleteUserReq,
|
||||
type GetUsersReq,
|
||||
type GetUserByUsernameReq,
|
||||
user,
|
||||
addUser,
|
||||
editUser,
|
||||
deleteUser,
|
||||
getUsers,
|
||||
getUserByUsername,
|
||||
existsUser,
|
||||
type ExistsUserReq,
|
||||
type GetUserByUuidReq,
|
||||
getUserByUuid,
|
||||
type GetUserByIdReq,
|
||||
getUserById
|
||||
} from './schema/user';
|
||||
import {
|
||||
type GetSettingReq,
|
||||
settings,
|
||||
getSetting,
|
||||
type GetSettingsReq,
|
||||
getSettings,
|
||||
type SetSettingsReq,
|
||||
setSettings
|
||||
} from './schema/settings';
|
||||
import {
|
||||
addFeedback,
|
||||
type AddFeedbackReq,
|
||||
addUserFeedbacks,
|
||||
type AddUserFeedbacksReq,
|
||||
feedback,
|
||||
getFeedbackByUrlHash,
|
||||
type GetFeedbackByUrlHash,
|
||||
getFeedbacks,
|
||||
type GetFeedbacksReq,
|
||||
submitFeedback,
|
||||
type SubmitFeedbackReq
|
||||
} from './schema/feedback.ts';
|
||||
import {
|
||||
addReport,
|
||||
type AddReportReq,
|
||||
editReport,
|
||||
type EditReportReq,
|
||||
getReportById,
|
||||
type GetReportById,
|
||||
getReportByUrlHash,
|
||||
type GetReportByUrlHash,
|
||||
getReports,
|
||||
type GetReportsReq,
|
||||
report,
|
||||
submitReport,
|
||||
type SubmitReportReq
|
||||
} from './schema/report.ts';
|
||||
import { DATABASE_URI } from 'astro:env/server';
|
||||
import {
|
||||
type GetStrikeReasonsReq,
|
||||
getStrikeReasons,
|
||||
strikeReason,
|
||||
type AddStrikeReasonReq,
|
||||
addStrikeReason,
|
||||
type EditStrikeReasonReq,
|
||||
editStrikeReason,
|
||||
type DeleteStrikeReasonReq,
|
||||
deleteStrikeReason
|
||||
} from '@db/schema/strikeReason.ts';
|
||||
import {
|
||||
deleteStrike,
|
||||
type DeleteStrikeReq,
|
||||
editStrike,
|
||||
type EditStrikeReq,
|
||||
getStrikeByReportId,
|
||||
type GetStrikeByReportIdReq,
|
||||
getStrikesByUserId,
|
||||
type GetStrikesByUserIdReq,
|
||||
strike
|
||||
} from '@db/schema/strike.ts';
|
||||
import {
|
||||
editReportStatus,
|
||||
type EditReportStatusReq,
|
||||
getReportStatus,
|
||||
type GetReportStatusReq,
|
||||
reportStatus
|
||||
} from '@db/schema/reportStatus.ts';
|
||||
import {
|
||||
addBlockedUser,
|
||||
type AddBlockedUserReq,
|
||||
getBlockedUsers,
|
||||
type GetBlockedUsersReq,
|
||||
blockedUser,
|
||||
type GetBlockedUserByUuidReq,
|
||||
getBlockedUserByUuid,
|
||||
type EditBlockedUserReq,
|
||||
editBlockedUser,
|
||||
type DeleteBlockedUserReq,
|
||||
deleteBlockedUser
|
||||
} from '@db/schema/blockedUser.ts';
|
||||
import {
|
||||
addReportAttachment,
|
||||
type AddReportAttachmentReq,
|
||||
getReportAttachments,
|
||||
type GetReportAttachmentsReq,
|
||||
reportAttachment
|
||||
} from '@db/schema/reportAttachment.ts';
|
||||
|
||||
export class Database {
|
||||
protected readonly db: MySql2Database<{
|
||||
admin: typeof admin;
|
||||
user: typeof user;
|
||||
blockedUser: typeof blockedUser;
|
||||
report: typeof report;
|
||||
reportAttachment: typeof reportAttachment;
|
||||
reportStatus: typeof reportStatus;
|
||||
strike: typeof strike;
|
||||
strikeReason: typeof strikeReason;
|
||||
feedback: typeof feedback;
|
||||
settings: typeof settings;
|
||||
}>;
|
||||
|
||||
private constructor(db: typeof this.db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
static async init(databaseUri: string) {
|
||||
const connectionPool = mysql.createPool({
|
||||
uri: databaseUri
|
||||
});
|
||||
|
||||
const db = drizzle({
|
||||
client: connectionPool,
|
||||
schema: {
|
||||
admin,
|
||||
user,
|
||||
blockedUser,
|
||||
report,
|
||||
reportAttachment,
|
||||
reportStatus,
|
||||
strike,
|
||||
strikeReason,
|
||||
feedback,
|
||||
settings
|
||||
},
|
||||
mode: 'default'
|
||||
});
|
||||
|
||||
return new Database(db);
|
||||
}
|
||||
|
||||
async transaction<T>(fn: (tx: Database & { rollback: () => never }) => Promise<T>): Promise<T> {
|
||||
return this.db.transaction((tx) => fn(new Database(tx) as Database & { rollback: () => never }));
|
||||
}
|
||||
|
||||
/* admins */
|
||||
addAdmin = (values: AddAdminReq) => addAdmin(this.db, values);
|
||||
editAdmin = (values: EditAdminReq) => editAdmin(this.db, values);
|
||||
deleteAdmin = (values: DeleteAdminReq) => deleteAdmin(this.db, values);
|
||||
getAdmins = (values: GetAdminReq) => getAdmins(this.db, values);
|
||||
existsAdmin = (values: ExistsAdminReq) => existsAdmin(this.db, values);
|
||||
|
||||
/* user */
|
||||
addUser = (values: AddUserReq) => addUser(this.db, values);
|
||||
editUser = (values: EditUserReq) => editUser(this.db, values);
|
||||
deleteUser = (values: DeleteUserReq) => deleteUser(this.db, values);
|
||||
existsUser = (values: ExistsUserReq) => existsUser(this.db, values);
|
||||
getUsers = (values: GetUsersReq) => getUsers(this.db, values);
|
||||
getUserById = (values: GetUserByIdReq) => getUserById(this.db, values);
|
||||
getUserByUsername = (values: GetUserByUsernameReq) => getUserByUsername(this.db, values);
|
||||
getUserByUuid = (values: GetUserByUuidReq) => getUserByUuid(this.db, values);
|
||||
|
||||
/* user blocks */
|
||||
addBlockedUser = (values: AddBlockedUserReq) => addBlockedUser(this.db, values);
|
||||
editBlockedUser = (values: EditBlockedUserReq) => editBlockedUser(this.db, values);
|
||||
deleteBlockedUser = (values: DeleteBlockedUserReq) => deleteBlockedUser(this.db, values);
|
||||
getBlockedUserByUuid = (values: GetBlockedUserByUuidReq) => getBlockedUserByUuid(this.db, values);
|
||||
getBlockedUsers = (values: GetBlockedUsersReq) => getBlockedUsers(this.db, values);
|
||||
|
||||
/* report */
|
||||
addReport = (values: AddReportReq) => addReport(this.db, values);
|
||||
editReport = (values: EditReportReq) => editReport(this.db, values);
|
||||
submitReport = (values: SubmitReportReq) => submitReport(this.db, values);
|
||||
getReports = (values: GetReportsReq) => getReports(this.db, values);
|
||||
getReportById = (values: GetReportById) => getReportById(this.db, values);
|
||||
getReportByUrlHash = (values: GetReportByUrlHash) => getReportByUrlHash(this.db, values);
|
||||
|
||||
/* report attachment */
|
||||
addReportAttachment = (values: AddReportAttachmentReq) => addReportAttachment(this.db, values);
|
||||
getReportAttachments = (values: GetReportAttachmentsReq) => getReportAttachments(this.db, values);
|
||||
|
||||
/* report status */
|
||||
getReportStatus = (values: GetReportStatusReq) => getReportStatus(this.db, values);
|
||||
editReportStatus = (values: EditReportStatusReq) => editReportStatus(this.db, values);
|
||||
|
||||
/* strike reason */
|
||||
addStrikeReason = (values: AddStrikeReasonReq) => addStrikeReason(this.db, values);
|
||||
editStrikeReason = (values: EditStrikeReasonReq) => editStrikeReason(this.db, values);
|
||||
deleteStrikeReason = (values: DeleteStrikeReasonReq) => deleteStrikeReason(this.db, values);
|
||||
getStrikeReasons = (values: GetStrikeReasonsReq) => getStrikeReasons(this.db, values);
|
||||
|
||||
/* strikes */
|
||||
editStrike = (values: EditStrikeReq) => editStrike(this.db, values);
|
||||
deleteStrike = (values: DeleteStrikeReq) => deleteStrike(this.db, values);
|
||||
getStrikeByReportId = (values: GetStrikeByReportIdReq) => getStrikeByReportId(this.db, values);
|
||||
getStrikesByUserId = (values: GetStrikesByUserIdReq) => getStrikesByUserId(this.db, values);
|
||||
|
||||
/* feedback */
|
||||
addFeedback = (values: AddFeedbackReq) => addFeedback(this.db, values);
|
||||
addUserFeedbacks = (values: AddUserFeedbacksReq) => addUserFeedbacks(this.db, values);
|
||||
submitFeedback = (values: SubmitFeedbackReq) => submitFeedback(this.db, values);
|
||||
getFeedbacks = (values: GetFeedbacksReq) => getFeedbacks(this.db, values);
|
||||
getFeedbackByUrlHash = (values: GetFeedbackByUrlHash) => getFeedbackByUrlHash(this.db, values);
|
||||
|
||||
/* settings */
|
||||
getSettings = (values: GetSettingsReq) => getSettings(this.db, values);
|
||||
setSettings = (values: SetSettingsReq) => setSettings(this.db, values);
|
||||
getSetting = (values: GetSettingReq) => getSetting(this.db, values);
|
||||
}
|
||||
|
||||
export const db = await Database.init(DATABASE_URI);
|
||||
82
src/db/schema/admin.ts
Normal file
82
src/db/schema/admin.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { int, mysqlTable, varchar } from 'drizzle-orm/mysql-core';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { Permissions } from '@util/permissions.ts';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
type Database = MySql2Database<{ admin: typeof admin }>;
|
||||
|
||||
export const admin = mysqlTable('admin', {
|
||||
id: int('id').primaryKey().autoincrement(),
|
||||
username: varchar('username', { length: 255 }).notNull(),
|
||||
password: varchar('password', { length: 255 }).notNull(),
|
||||
permissions: int('permissions').notNull()
|
||||
});
|
||||
|
||||
export type AddAdminReq = Omit<typeof admin.$inferInsert, 'id'>;
|
||||
|
||||
export type EditAdminReq = {
|
||||
id: number;
|
||||
username: string;
|
||||
password: string | null;
|
||||
permissions: number;
|
||||
};
|
||||
|
||||
export type DeleteAdminReq = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type GetAdminReq = {};
|
||||
export type GetAdminRes = Omit<typeof admin.$inferSelect, 'password'>[];
|
||||
|
||||
export type ExistsAdminReq = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
export type ExistsAdminRes = {
|
||||
id: number;
|
||||
username: string;
|
||||
permissions: Permissions;
|
||||
} | null;
|
||||
|
||||
export async function addAdmin(db: Database, values: AddAdminReq) {
|
||||
values.password = bcrypt.hashSync(values.password, 10);
|
||||
|
||||
const adminIds = await db.insert(admin).values(values).$returningId();
|
||||
|
||||
return adminIds[0];
|
||||
}
|
||||
|
||||
export async function editAdmin(db: Database, values: EditAdminReq) {
|
||||
return db
|
||||
.update(admin)
|
||||
.set({
|
||||
id: values.id,
|
||||
username: values.username,
|
||||
password: values.password != null ? bcrypt.hashSync(values.password, 10) : undefined,
|
||||
permissions: values.permissions
|
||||
})
|
||||
.where(eq(admin.id, values.id));
|
||||
}
|
||||
|
||||
export async function deleteAdmin(db: Database, values: DeleteAdminReq) {
|
||||
return db.delete(admin).where(eq(admin.id, values.id));
|
||||
}
|
||||
|
||||
export async function getAdmins(db: Database, _values: GetAdminReq): Promise<GetAdminRes> {
|
||||
return db.select({ id: admin.id, username: admin.username, permissions: admin.permissions }).from(admin);
|
||||
}
|
||||
|
||||
export async function existsAdmin(db: Database, values: ExistsAdminReq): Promise<ExistsAdminRes> {
|
||||
const a = await db.query.admin.findFirst({
|
||||
where: eq(admin.username, values.username)
|
||||
});
|
||||
|
||||
if (!a || !bcrypt.compareSync(values.password, a.password)) return null;
|
||||
|
||||
return {
|
||||
id: a.id,
|
||||
username: a.username,
|
||||
permissions: new Permissions(a.permissions)
|
||||
};
|
||||
}
|
||||
58
src/db/schema/blockedUser.ts
Normal file
58
src/db/schema/blockedUser.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { int, mysqlTable, varchar } from 'drizzle-orm/mysql-core';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
type Database = MySql2Database<{ blockedUser: typeof blockedUser }>;
|
||||
|
||||
export const blockedUser = mysqlTable('blocked_user', {
|
||||
id: int('id').primaryKey().autoincrement(),
|
||||
uuid: varchar('uuid', { length: 255 }).unique().notNull(),
|
||||
comment: varchar('comment', { length: 255 })
|
||||
});
|
||||
|
||||
export type AddBlockedUserReq = {
|
||||
uuid: string;
|
||||
comment?: string | null;
|
||||
};
|
||||
|
||||
export type EditBlockedUserReq = {
|
||||
id: number;
|
||||
uuid: string;
|
||||
comment?: string | null;
|
||||
};
|
||||
|
||||
export type DeleteBlockedUserReq = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type GetBlockedUserByUuidReq = {
|
||||
uuid: string;
|
||||
};
|
||||
|
||||
export type GetBlockedUsersReq = {};
|
||||
|
||||
export async function addBlockedUser(db: Database, values: AddBlockedUserReq) {
|
||||
const bu = await db.insert(blockedUser).values(values).$returningId();
|
||||
|
||||
return bu[0];
|
||||
}
|
||||
|
||||
export async function editBlockedUser(db: Database, values: EditBlockedUserReq) {
|
||||
await db.update(blockedUser).set(values).where(eq(blockedUser.id, values.id));
|
||||
}
|
||||
|
||||
export async function deleteBlockedUser(db: Database, values: DeleteBlockedUserReq) {
|
||||
return db.delete(blockedUser).where(eq(blockedUser.id, values.id));
|
||||
}
|
||||
|
||||
export async function getBlockedUserByUuid(db: Database, values: GetBlockedUserByUuidReq) {
|
||||
const bu = await db.query.blockedUser.findFirst({
|
||||
where: eq(blockedUser.uuid, values.uuid)
|
||||
});
|
||||
|
||||
return bu ?? null;
|
||||
}
|
||||
|
||||
export async function getBlockedUsers(db: Database, _values: GetBlockedUsersReq) {
|
||||
return db.select().from(blockedUser);
|
||||
}
|
||||
98
src/db/schema/feedback.ts
Normal file
98
src/db/schema/feedback.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core';
|
||||
import { user } from './user.ts';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { eq, inArray } from 'drizzle-orm';
|
||||
import { generateRandomString } from '@util/random.ts';
|
||||
|
||||
type Database = MySql2Database<{ feedback: typeof feedback }>;
|
||||
|
||||
export const feedback = mysqlTable('feedback', {
|
||||
id: int('id').primaryKey().autoincrement(),
|
||||
event: varchar('event', { length: 255 }).notNull(),
|
||||
title: varchar('title', { length: 255 }),
|
||||
content: text('content'),
|
||||
urlHash: varchar('url_hash', { length: 255 }).unique().notNull(),
|
||||
lastChanged: timestamp('last_changed', { mode: 'date' }).notNull().defaultNow().onUpdateNow(),
|
||||
userId: int('user_id').references(() => user.id)
|
||||
});
|
||||
|
||||
export type AddFeedbackReq = {
|
||||
event: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type AddUserFeedbacksReq = {
|
||||
event: string;
|
||||
title: string;
|
||||
uuids: string[];
|
||||
};
|
||||
|
||||
export type SubmitFeedbackReq = {
|
||||
urlHash: string;
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type GetFeedbacksReq = {};
|
||||
|
||||
export type GetFeedbackByUrlHash = {
|
||||
urlHash: string;
|
||||
};
|
||||
|
||||
export async function addFeedback(db: Database, values: AddFeedbackReq) {
|
||||
return db.insert(feedback).values({
|
||||
event: values.event,
|
||||
content: values.content,
|
||||
urlHash: generateRandomString(16)
|
||||
});
|
||||
}
|
||||
|
||||
export async function addUserFeedbacks(db: Database, values: AddUserFeedbacksReq) {
|
||||
const users = await db.select({ id: user.id, uuid: user.uuid }).from(user).where(inArray(user.uuid, values.uuids));
|
||||
|
||||
const userFeedbacks = users.map((user) => ({
|
||||
id: user.id,
|
||||
uuid: user.uuid!,
|
||||
urlHash: generateRandomString(16)
|
||||
}));
|
||||
|
||||
await db.insert(feedback).values(
|
||||
userFeedbacks.map((feedback) => ({
|
||||
event: values.event,
|
||||
title: values.title,
|
||||
urlHash: feedback.urlHash,
|
||||
userId: feedback.id
|
||||
}))
|
||||
);
|
||||
|
||||
return userFeedbacks;
|
||||
}
|
||||
|
||||
export async function submitFeedback(db: Database, values: SubmitFeedbackReq) {
|
||||
return db
|
||||
.update(feedback)
|
||||
.set({
|
||||
content: values.content
|
||||
})
|
||||
.where(eq(feedback.urlHash, values.urlHash));
|
||||
}
|
||||
|
||||
export async function getFeedbacks(db: Database, _values: GetFeedbacksReq) {
|
||||
return db
|
||||
.select({
|
||||
id: feedback.id,
|
||||
event: feedback.event,
|
||||
title: feedback.title,
|
||||
content: feedback.content,
|
||||
urlHash: feedback.urlHash,
|
||||
lastChanged: feedback.lastChanged,
|
||||
username: user.username
|
||||
})
|
||||
.from(feedback)
|
||||
.leftJoin(user, eq(feedback.userId, user.id));
|
||||
}
|
||||
|
||||
export async function getFeedbackByUrlHash(db: Database, values: GetFeedbackByUrlHash) {
|
||||
return db.query.feedback.findFirst({
|
||||
where: eq(feedback.urlHash, values.urlHash)
|
||||
});
|
||||
}
|
||||
218
src/db/schema/report.ts
Normal file
218
src/db/schema/report.ts
Normal file
@@ -0,0 +1,218 @@
|
||||
import { alias, int, mysqlTable, text, timestamp, varchar } from 'drizzle-orm/mysql-core';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { and, eq, isNotNull } from 'drizzle-orm';
|
||||
import { reportStatus } from './reportStatus.ts';
|
||||
import { generateRandomString } from '@util/random.ts';
|
||||
import { BASE_PATH } from 'astro:env/server';
|
||||
import { strikeReason } from '@db/schema/strikeReason.ts';
|
||||
import { strike } from '@db/schema/strike.ts';
|
||||
import { user } from '@db/schema/user.ts';
|
||||
|
||||
type Database = MySql2Database<{ report: typeof report }>;
|
||||
|
||||
export const report = mysqlTable('report', {
|
||||
id: int('id').primaryKey().autoincrement(),
|
||||
reason: varchar('reason', { length: 255 }).notNull(),
|
||||
body: text('body'),
|
||||
urlHash: varchar('url_hash', { length: 255 }).notNull(),
|
||||
createdAt: timestamp('created_at', { mode: 'date' }),
|
||||
reporterId: int('reporter_id').references(() => user.id),
|
||||
reportedId: int('reported_id').references(() => user.id)
|
||||
});
|
||||
|
||||
export type AddReportReq = {
|
||||
reason: string;
|
||||
body: string | null;
|
||||
createdAt?: Date | null;
|
||||
reporterId?: number;
|
||||
reportedId?: number | null;
|
||||
};
|
||||
|
||||
export type EditReportReq = {
|
||||
id: number;
|
||||
reportedId: number | null;
|
||||
};
|
||||
|
||||
export type SubmitReportReq = {
|
||||
urlHash: string;
|
||||
reportedId: number | null;
|
||||
reason: string;
|
||||
body: string;
|
||||
};
|
||||
|
||||
export type GetReportsReq = {
|
||||
reporter?: string | null;
|
||||
reported?: string | null;
|
||||
includeDrafts?: boolean | null;
|
||||
};
|
||||
|
||||
export type GetReportById = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type GetReportByUrlHash = {
|
||||
urlHash: string;
|
||||
};
|
||||
|
||||
export async function addReport(db: Database, values: AddReportReq) {
|
||||
const urlHash = generateRandomString(16);
|
||||
|
||||
const r = await db
|
||||
.insert(report)
|
||||
.values({
|
||||
reason: values.reason,
|
||||
body: values.body,
|
||||
urlHash: urlHash,
|
||||
createdAt: values.createdAt,
|
||||
reporterId: values.reporterId,
|
||||
reportedId: values.reportedId
|
||||
})
|
||||
.$returningId();
|
||||
|
||||
return Object.assign(r[0], { url: `${BASE_PATH}/report/${urlHash}` });
|
||||
}
|
||||
|
||||
export async function editReport(db: Database, values: EditReportReq) {
|
||||
return db.update(report).set({
|
||||
reportedId: values.reportedId
|
||||
});
|
||||
}
|
||||
|
||||
export async function submitReport(db: Database, values: SubmitReportReq) {
|
||||
return db
|
||||
.update(report)
|
||||
.set({
|
||||
reportedId: values.reportedId,
|
||||
reason: values.reason,
|
||||
body: values.body,
|
||||
createdAt: new Date()
|
||||
})
|
||||
.where(eq(report.urlHash, values.urlHash));
|
||||
}
|
||||
|
||||
export async function getReports(db: Database, values: GetReportsReq) {
|
||||
const reporter = alias(user, 'reporter');
|
||||
const reported = alias(user, 'reported');
|
||||
|
||||
let reporterIdSubquery;
|
||||
if (values.reporter != null) {
|
||||
reporterIdSubquery = db
|
||||
.select({ id: reporter.id })
|
||||
.from(reporter)
|
||||
.where(eq(reporter.username, values.reporter))
|
||||
.as('reporter_id_subquery');
|
||||
}
|
||||
let reportedIdSubquery;
|
||||
if (values.reported != null) {
|
||||
reportedIdSubquery = db
|
||||
.select({ id: reported.id })
|
||||
.from(reported)
|
||||
.where(eq(reported.username, values.reported))
|
||||
.as('reported_id_subquery');
|
||||
}
|
||||
|
||||
return db
|
||||
.select({
|
||||
id: report.id,
|
||||
reason: report.reason,
|
||||
body: report.body,
|
||||
urlHash: report.urlHash,
|
||||
createdAt: report.createdAt,
|
||||
reporter: {
|
||||
id: reporter.id,
|
||||
username: reporter.username
|
||||
},
|
||||
reported: {
|
||||
id: reported.id,
|
||||
username: reported.username
|
||||
},
|
||||
status: {
|
||||
status: reportStatus.status,
|
||||
notice: reportStatus.notice,
|
||||
statement: reportStatus.statement
|
||||
},
|
||||
strike: {
|
||||
strikeReasonId: strikeReason.id
|
||||
}
|
||||
})
|
||||
.from(report)
|
||||
.innerJoin(reporter, eq(report.reporterId, reporter.id))
|
||||
.leftJoin(reported, eq(report.reportedId, reported.id))
|
||||
.leftJoin(reportStatus, eq(report.id, reportStatus.reportId))
|
||||
.leftJoin(strike, eq(report.id, strike.reportId))
|
||||
.leftJoin(strikeReason, eq(strike.strikeReasonId, strikeReason.id))
|
||||
.where(
|
||||
and(
|
||||
values.reporter != null ? eq(report.reporterId, reporterIdSubquery!.id) : undefined,
|
||||
values.reported != null ? eq(report.reportedId, reportedIdSubquery!.id) : undefined,
|
||||
values.includeDrafts == false ? isNotNull(report.createdAt) : undefined
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
export async function getReportById(db: Database, values: GetReportById) {
|
||||
const reporter = alias(user, 'reporter');
|
||||
const reported = alias(user, 'reported');
|
||||
|
||||
const reports = await db
|
||||
.select({
|
||||
id: report.id,
|
||||
reason: report.reason,
|
||||
body: report.body,
|
||||
createdAt: report.createdAt,
|
||||
reporter: {
|
||||
id: reporter.id,
|
||||
username: reporter.username
|
||||
},
|
||||
reported: {
|
||||
id: reported.id,
|
||||
username: reported.username
|
||||
},
|
||||
status: {
|
||||
status: reportStatus.status,
|
||||
notice: reportStatus.notice,
|
||||
statement: reportStatus.statement
|
||||
}
|
||||
})
|
||||
.from(report)
|
||||
.innerJoin(reporter, eq(report.reporterId, reporter.id))
|
||||
.leftJoin(reported, eq(report.reportedId, reported.id))
|
||||
.leftJoin(reportStatus, eq(report.id, reportStatus.reportId))
|
||||
.where(eq(report.id, values.id));
|
||||
|
||||
return reports[0] ?? null;
|
||||
}
|
||||
|
||||
export async function getReportByUrlHash(db: Database, values: GetReportByUrlHash) {
|
||||
const reporter = alias(user, 'reporter');
|
||||
const reported = alias(user, 'reported');
|
||||
|
||||
const reports = await db
|
||||
.select({
|
||||
id: report.id,
|
||||
reason: report.reason,
|
||||
body: report.body,
|
||||
createdAt: report.createdAt,
|
||||
urlHash: report.urlHash,
|
||||
reporter: {
|
||||
id: reporter.id,
|
||||
username: reporter.username
|
||||
},
|
||||
reported: {
|
||||
id: reported.id,
|
||||
username: reported.username
|
||||
},
|
||||
status: {
|
||||
status: reportStatus.status,
|
||||
notice: reportStatus.notice,
|
||||
statement: reportStatus.statement
|
||||
}
|
||||
})
|
||||
.from(report)
|
||||
.innerJoin(reporter, eq(report.reporterId, reporter.id))
|
||||
.leftJoin(reported, eq(report.reportedId, reported.id))
|
||||
.leftJoin(reportStatus, eq(report.id, reportStatus.reportId))
|
||||
.where(eq(report.urlHash, values.urlHash));
|
||||
|
||||
return reports[0] ?? null;
|
||||
}
|
||||
38
src/db/schema/reportAttachment.ts
Normal file
38
src/db/schema/reportAttachment.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { char, int, mysqlEnum, mysqlTable } from 'drizzle-orm/mysql-core';
|
||||
import { report } from '@db/schema/report.ts';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
type Database = MySql2Database<{ reportAttachment: typeof reportAttachment }>;
|
||||
|
||||
export const reportAttachment = mysqlTable('report_attachment', {
|
||||
type: mysqlEnum('type', ['image', 'video']),
|
||||
hash: char('hash', { length: 32 }),
|
||||
reportId: int('report_id')
|
||||
.notNull()
|
||||
.references(() => report.id)
|
||||
});
|
||||
|
||||
export type AddReportAttachmentReq = {
|
||||
type: 'image' | 'video';
|
||||
hash: string;
|
||||
reportId: number;
|
||||
};
|
||||
|
||||
export type GetReportAttachmentsReq = {
|
||||
reportId: number;
|
||||
};
|
||||
|
||||
export async function addReportAttachment(db: Database, values: AddReportAttachmentReq) {
|
||||
await db.insert(reportAttachment).values(values);
|
||||
}
|
||||
|
||||
export async function getReportAttachments(db: Database, values: GetReportAttachmentsReq) {
|
||||
return db
|
||||
.select({
|
||||
type: reportAttachment.type,
|
||||
hash: reportAttachment.hash
|
||||
})
|
||||
.from(reportAttachment)
|
||||
.where(eq(reportAttachment.reportId, values.reportId));
|
||||
}
|
||||
50
src/db/schema/reportStatus.ts
Normal file
50
src/db/schema/reportStatus.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { int, mysqlEnum, mysqlTable, text } from 'drizzle-orm/mysql-core';
|
||||
import { admin } from './admin.ts';
|
||||
import { report } from './report.ts';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
type Database = MySql2Database<{ reportStatus: typeof reportStatus }>;
|
||||
|
||||
export const reportStatus = mysqlTable('report_status', {
|
||||
status: mysqlEnum('status', ['open', 'closed']),
|
||||
notice: text('notice'),
|
||||
statement: text('statement'),
|
||||
reportId: int('report_id')
|
||||
.notNull()
|
||||
.unique()
|
||||
.references(() => report.id),
|
||||
reviewerId: int('reviewer_id').references(() => admin.id)
|
||||
});
|
||||
|
||||
export type GetReportStatusReq = {
|
||||
reportId: number;
|
||||
};
|
||||
|
||||
export type EditReportStatusReq = {
|
||||
reportId: number;
|
||||
status: 'open' | 'closed' | null;
|
||||
notice: string | null;
|
||||
statement: string | null;
|
||||
};
|
||||
|
||||
export async function getReportStatus(db: Database, values: GetReportStatusReq) {
|
||||
const rs = await db.query.reportStatus.findFirst({
|
||||
where: eq(reportStatus.reportId, values.reportId)
|
||||
});
|
||||
|
||||
return rs ?? null;
|
||||
}
|
||||
|
||||
export async function editReportStatus(db: Database, values: EditReportStatusReq) {
|
||||
return db
|
||||
.insert(reportStatus)
|
||||
.values(values)
|
||||
.onDuplicateKeyUpdate({
|
||||
set: {
|
||||
status: values.status,
|
||||
notice: values.notice,
|
||||
statement: values.statement
|
||||
}
|
||||
});
|
||||
}
|
||||
52
src/db/schema/settings.ts
Normal file
52
src/db/schema/settings.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
import { mysqlTable, text, varchar } from 'drizzle-orm/mysql-core';
|
||||
import { eq, inArray } from 'drizzle-orm';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
|
||||
type Database = MySql2Database<{ settings: typeof settings }>;
|
||||
|
||||
export const settings = mysqlTable('settings', {
|
||||
name: varchar('name', { length: 255 }).unique().notNull(),
|
||||
value: text()
|
||||
});
|
||||
|
||||
export type GetSettingsReq = {
|
||||
names?: string[];
|
||||
};
|
||||
|
||||
export type SetSettingsReq = {
|
||||
settings: {
|
||||
name: string;
|
||||
value: string | null;
|
||||
}[];
|
||||
};
|
||||
|
||||
export type GetSettingReq = {
|
||||
name: string;
|
||||
};
|
||||
|
||||
export async function getSettings(db: Database, values: GetSettingsReq) {
|
||||
return db
|
||||
.select({ name: settings.name, value: settings.value })
|
||||
.from(settings)
|
||||
.where(values.names ? inArray(settings.name, values.names) : undefined);
|
||||
}
|
||||
|
||||
export async function setSettings(db: Database, values: SetSettingsReq) {
|
||||
return db.transaction(async (tx) => {
|
||||
for (const setting of values.settings) {
|
||||
await tx
|
||||
.insert(settings)
|
||||
.values(setting)
|
||||
.onDuplicateKeyUpdate({
|
||||
set: {
|
||||
value: setting.value
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSetting(db: Database, values: GetSettingReq): Promise<string | null> {
|
||||
const value = await db.select({ value: settings.value }).from(settings).where(eq(settings.name, values.name));
|
||||
return value.length > 0 ? value[0]?.value : null;
|
||||
}
|
||||
81
src/db/schema/strike.ts
Normal file
81
src/db/schema/strike.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { int, mysqlTable, timestamp } from 'drizzle-orm/mysql-core';
|
||||
import { strikeReason } from '@db/schema/strikeReason.ts';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { report } from '@db/schema/report.ts';
|
||||
|
||||
type Database = MySql2Database<{ strike: typeof strike }>;
|
||||
|
||||
export const strike = mysqlTable('strike', {
|
||||
at: timestamp('at', { mode: 'date' }).notNull(),
|
||||
reportId: int('report_id')
|
||||
.notNull()
|
||||
.references(() => report.id),
|
||||
strikeReasonId: int('strike_reason_id')
|
||||
.notNull()
|
||||
.references(() => strikeReason.id)
|
||||
});
|
||||
|
||||
export type EditStrikeReq = {
|
||||
reportId: number;
|
||||
at?: Date;
|
||||
strikeReasonId: number;
|
||||
};
|
||||
|
||||
export type DeleteStrikeReq = {
|
||||
reportId: number;
|
||||
};
|
||||
|
||||
export type GetStrikeByReportIdReq = {
|
||||
reportId: number;
|
||||
};
|
||||
|
||||
export type GetStrikesByUserIdReq = {
|
||||
userId: number;
|
||||
};
|
||||
|
||||
export async function editStrike(db: Database, values: EditStrikeReq) {
|
||||
return db
|
||||
.insert(strike)
|
||||
.values({
|
||||
at: values.at ?? new Date(),
|
||||
reportId: values.reportId,
|
||||
strikeReasonId: values.strikeReasonId
|
||||
})
|
||||
.onDuplicateKeyUpdate({
|
||||
set: {
|
||||
at: values.at ?? new Date(),
|
||||
strikeReasonId: values.strikeReasonId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export async function deleteStrike(db: Database, values: DeleteStrikeReq) {
|
||||
return db.delete(strike).where(eq(strike.reportId, values.reportId)).limit(1);
|
||||
}
|
||||
|
||||
export async function getStrikeByReportId(db: Database, values: GetStrikeByReportIdReq) {
|
||||
const strikes = await db
|
||||
.select({
|
||||
strike,
|
||||
strikeReason
|
||||
})
|
||||
.from(strike)
|
||||
.where(eq(strike.reportId, values.reportId))
|
||||
.leftJoin(strikeReason, eq(strike.strikeReasonId, strikeReason.id));
|
||||
|
||||
return strikes[0] ?? null;
|
||||
}
|
||||
|
||||
export async function getStrikesByUserId(db: Database, values: GetStrikesByUserIdReq) {
|
||||
return db
|
||||
.select({
|
||||
at: strike.at,
|
||||
report: report,
|
||||
reason: strikeReason
|
||||
})
|
||||
.from(strike)
|
||||
.innerJoin(strikeReason, eq(strike.strikeReasonId, strikeReason.id))
|
||||
.innerJoin(report, eq(strike.reportId, report.id))
|
||||
.where(eq(report.reportedId, values.userId));
|
||||
}
|
||||
42
src/db/schema/strikeReason.ts
Normal file
42
src/db/schema/strikeReason.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { int, mysqlTable, tinyint, varchar } from 'drizzle-orm/mysql-core';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { asc, eq } from 'drizzle-orm';
|
||||
|
||||
type Database = MySql2Database<{ strikeReason: typeof strikeReason }>;
|
||||
|
||||
export const strikeReason = mysqlTable('strike_reason', {
|
||||
id: int('id').primaryKey().autoincrement(),
|
||||
name: varchar('name', { length: 255 }).notNull(),
|
||||
weight: tinyint('weight').notNull()
|
||||
});
|
||||
|
||||
export type AddStrikeReasonReq = {
|
||||
name: string;
|
||||
weight: number;
|
||||
};
|
||||
|
||||
export type EditStrikeReasonReq = typeof strikeReason.$inferSelect;
|
||||
|
||||
export type DeleteStrikeReasonReq = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type GetStrikeReasonsReq = {};
|
||||
|
||||
export async function addStrikeReason(db: Database, values: AddStrikeReasonReq) {
|
||||
const sr = await db.insert(strikeReason).values(values).$returningId();
|
||||
|
||||
return sr[0];
|
||||
}
|
||||
|
||||
export async function editStrikeReason(db: Database, values: EditStrikeReasonReq) {
|
||||
await db.update(strikeReason).set(values).where(eq(strikeReason.id, values.id));
|
||||
}
|
||||
|
||||
export async function deleteStrikeReason(db: Database, values: DeleteStrikeReasonReq) {
|
||||
await db.delete(strikeReason).where(eq(strikeReason.id, values.id));
|
||||
}
|
||||
|
||||
export async function getStrikeReasons(db: Database, _values: GetStrikeReasonsReq) {
|
||||
return db.select().from(strikeReason).orderBy(asc(strikeReason.weight));
|
||||
}
|
||||
106
src/db/schema/user.ts
Normal file
106
src/db/schema/user.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { date, int, mysqlTable, varchar } from 'drizzle-orm/mysql-core';
|
||||
import type { MySql2Database } from 'drizzle-orm/mysql2';
|
||||
import { eq, like, or } from 'drizzle-orm';
|
||||
import { mysqlEnum } from 'drizzle-orm/mysql-core/columns/enum';
|
||||
|
||||
type Database = MySql2Database<{ user: typeof user }>;
|
||||
|
||||
export const user = mysqlTable('user', {
|
||||
id: int('id').primaryKey().autoincrement(),
|
||||
firstname: varchar('firstname', { length: 255 }).notNull(),
|
||||
lastname: varchar('lastname', { length: 255 }).notNull(),
|
||||
birthday: date('birthday', { mode: 'string' }).notNull(),
|
||||
telephone: varchar('telephone', { length: 255 }),
|
||||
username: varchar('username', { length: 255 }).notNull(),
|
||||
edition: mysqlEnum(['java', 'bedrock']).notNull(),
|
||||
uuid: varchar('uuid', { length: 36 })
|
||||
});
|
||||
|
||||
export type AddUserReq = Omit<typeof user.$inferInsert, 'id'>;
|
||||
|
||||
export type EditUserReq = Partial<Omit<typeof user.$inferInsert, 'id'>> & { id: number };
|
||||
|
||||
export type DeleteUserReq = {
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type ExistsUserReq = {
|
||||
username?: string;
|
||||
uuid?: string;
|
||||
};
|
||||
|
||||
export type GetUsersReq = {
|
||||
username?: string | null;
|
||||
limit?: number;
|
||||
};
|
||||
export type GetUsersRes = (typeof user.$inferSelect)[];
|
||||
|
||||
export type GetUserByIdReq = {
|
||||
id: number;
|
||||
};
|
||||
export type GetUserByIdRes = typeof user.$inferSelect | null;
|
||||
|
||||
export type GetUserByUsernameReq = {
|
||||
username: string;
|
||||
};
|
||||
export type GetUserByUsernameRes = typeof user.$inferSelect | null;
|
||||
|
||||
export type GetUserByUuidReq = {
|
||||
uuid: string;
|
||||
};
|
||||
|
||||
export async function addUser(db: Database, values: AddUserReq) {
|
||||
const userIds = await db.insert(user).values(values).$returningId();
|
||||
|
||||
return userIds[0];
|
||||
}
|
||||
|
||||
export async function editUser(db: Database, values: EditUserReq) {
|
||||
await db.update(user).set(values).where(eq(user.id, values.id));
|
||||
}
|
||||
|
||||
export async function deleteUser(db: Database, values: DeleteUserReq) {
|
||||
await db.delete(user).where(eq(user.id, values.id));
|
||||
}
|
||||
|
||||
export async function existsUser(db: Database, values: ExistsUserReq) {
|
||||
const u = db.query.user.findFirst({
|
||||
where: or(
|
||||
values.username != null ? eq(user.username, values.username) : undefined,
|
||||
values.uuid != null ? eq(user.uuid, values.uuid) : undefined
|
||||
)
|
||||
});
|
||||
|
||||
return u ?? null;
|
||||
}
|
||||
|
||||
export async function getUsers(db: Database, values: GetUsersReq): Promise<GetUsersRes> {
|
||||
return db.query.user.findMany({
|
||||
where: values.username != null ? like(user.username, `%${values.username}%`) : undefined,
|
||||
limit: values.limit
|
||||
});
|
||||
}
|
||||
|
||||
export async function getUserById(db: Database, values: GetUserByIdReq): Promise<GetUserByIdRes> {
|
||||
const u = await db.query.user.findFirst({
|
||||
where: eq(user.id, values.id)
|
||||
});
|
||||
|
||||
return u ?? null;
|
||||
}
|
||||
|
||||
export async function getUserByUsername(db: Database, values: GetUserByUsernameReq): Promise<GetUserByUsernameRes> {
|
||||
const u = await db.query.user.findFirst({
|
||||
where: eq(user.username, values.username)
|
||||
});
|
||||
|
||||
return u ?? null;
|
||||
}
|
||||
|
||||
export async function getUserByUuid(db: Database, values: GetUserByUuidReq) {
|
||||
const u = await db.query.user.findFirst({
|
||||
where: eq(user.uuid, values.uuid)
|
||||
});
|
||||
|
||||
return u ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user