diff --git a/new/backend/.gitignore b/new/backend/.gitignore new file mode 100644 index 000000000..3b56c7a6e --- /dev/null +++ b/new/backend/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +# Keep environment variables out of version control +.env + +# Remove the database file +db.sqlite \ No newline at end of file diff --git a/new/backend/example.env b/new/backend/example.env index b42a8a733..ec6df51f2 100644 --- a/new/backend/example.env +++ b/new/backend/example.env @@ -21,5 +21,10 @@ HTTP_BASE_URL=http://localhost:4000 # HTTP_HOST=0.0.0.0 # HTTP_PORT=4000 +# -- This variable determines the database to connect to. +# -- Note that this has to be of the same database type as specified in the Prisma schema. +# -- Right now it's SQLite since we already use it. +DATABASE_URL=file:./db.sqlite + # -- Do change this variable, lest you wish to anger the computer. SECURITY_SECRET=changeme \ No newline at end of file diff --git a/new/backend/package.json b/new/backend/package.json index 763e14911..4504741a9 100644 --- a/new/backend/package.json +++ b/new/backend/package.json @@ -24,6 +24,7 @@ "author": "", "dependencies": { "@fastify/type-provider-typebox": "^3.5.0", + "@prisma/client": "5.3.1", "@pronounspage/common": "workspace:*", "@sinclair/typebox": "^0.31.14", "csv-parse": "^5.5.0", @@ -36,6 +37,7 @@ "nodemon": "^3.0.1", "npm-run-all": "^4.1.5", "pino-pretty": "^10.2.0", + "prisma": "^5.3.1", "rimraf": "^5.0.1", "typescript": "^5.2.2" }, diff --git a/new/backend/prisma/schema.prisma b/new/backend/prisma/schema.prisma new file mode 100644 index 000000000..5c378ade9 --- /dev/null +++ b/new/backend/prisma/schema.prisma @@ -0,0 +1,333 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = env("DATABASE_URL") +} + +model OldAuditLog { + id String @id + userId String? + username String? + event String + payload String? + + @@map("audit_log") +} + +model NewAuditLog { + id Int @id @default(autoincrement()) + userId String + username String + email String + loggedAt Int + locale String + actionType String + actionData String + + @@map("audit_logs_new") +} + +model Authenticator { + id String @id + userId String? + type String + payload String + validUntil Int? + users User? @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) + + @@index([userId], map: "authenticators_userId") + @@index([type], map: "authenticators_type") + @@map("authenticators") +} + +// Some manual changes here: +// 1. `bannedBy` is a nullable +model BanProposal { + id String @id + userId String + bannedBy String? + bannedTerms String + bannedReason String + bannedByUser User? @relation("ban_proposals_bannedByTousers", fields: [bannedBy], references: [id], onDelete: SetNull, onUpdate: NoAction) + user User @relation("ban_proposals_userIdTousers", fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) + + @@index([userId], map: "ban_proposals_userId") + @@map("ban_proposals") +} + +model Ban { + type String + value String + + @@id([type, value]) + @@map("ban") +} + +model Inclusive { + id String @id + insteadOf String + say String + because String + locale String + approved Int + base_id String? + author_id String? + categories String? + links String? + deleted Int @default(0) + clarification String? + users User? @relation(fields: [author_id], references: [id], onUpdate: NoAction) + + @@index([insteadOf], map: "inclusive_insteadOf") + @@index([locale], map: "inclusive_locale") + @@map("inclusive") +} + +/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client. +model Link { + url String? @id + expiresAt Int? + favicon String? + relMe String? + nodeinfo String? + + @@map("links") + @@ignore +} + +model Name { + id String @id + name String + locale String + origin String? + meaning String? + usage String? + legally String? + pros String? + cons String? + notablePeople String? + links String? + namedays String? + namedaysComment String? + deleted Int @default(0) + approved Int @default(0) + base_id String? + author_id String? + + @@index([locale], map: "names_locale") + @@index([name], map: "names_name") + @@index([locale, name], map: "names_name_locale") + @@map("names") +} + +model Noun { + id String @id + masc String + fem String + neutr String + mascPl String + femPl String + neutrPl String + approved Int + base_id String? + locale String @default("pl") + author_id String? + deleted Int @default(0) + sources String? + users User? @relation(fields: [author_id], references: [id], onUpdate: NoAction) + + @@index([masc], map: "nouns_masc") + @@index([locale], map: "nouns_locale") + @@map("nouns") +} + +model Profile { + id String @id + userId String + locale String + names String + pronouns String + description String + birthday String? + links String + flags String + words String + active Int + teamName String? + footerName String? + footerAreas String? + customFlags String @default("{}") + card String? + credentials String? + credentialsLevel Int? + credentialsName Int? + cardDark String? + opinions String @default("{}") + timezone String? + sensitive String @default("[]") + users User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) + user_connections UserConnection[] + + @@unique([userId, locale], map: "sqlite_autoindex_profiles_2") + @@index([footerAreas], map: "profiles_footerAreas") + @@index([footerName], map: "profiles_footerName") + @@index([teamName], map: "profiles_teamName") + @@index([locale, userId], map: "profiles_locale_userId") + @@index([userId], map: "profiles_userId") + @@index([locale], map: "profiles_locale") + @@map("profiles") +} + +model Report { + id String @id + userId String? + reporterId String? + comment String + isAutomatic Int? + isHandled Int? + snapshot String? + users_reports_reporterIdTousers User? @relation("reports_reporterIdTousers", fields: [reporterId], references: [id], onUpdate: NoAction) + users_reports_userIdTousers User? @relation("reports_userIdTousers", fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) + + @@index([userId], map: "reports_userId") + @@index([isHandled], map: "reports_isHandled") + @@index([isAutomatic], map: "reports_isAutomatic") + @@map("reports") +} + +/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client. +model SocialLookup { + userId String + provider String + identifier String + users User @relation(fields: [userId], references: [id], onDelete: Cascade, onUpdate: NoAction) + + @@index([provider, identifier], map: "social_lookup_provider_identifier") + @@map("social_lookup") + @@ignore +} + +model Source { + id String @id + locale String + pronouns String + type String + author String? + title String + extra String? + year Int? + fragments String + comment String? + link String? + submitter_id String? + approved Int? @default(0) + deleted Int? @default(0) + base_id String? + key String? + images String? + spoiler Int @default(0) + users User? @relation(fields: [submitter_id], references: [id], onUpdate: NoAction) + + @@index([locale], map: "sources_locale") + @@map("sources") +} + +model Stat { + id String + locale String + users Int + data String + + @@id([id, locale]) + @@map("stats") +} + +/// The underlying table does not contain a valid unique identifier and can therefore currently not be handled by Prisma Client. +model SubscriptionMessage { + id String? @id + subscription_id String + campaign String + + @@map("subscription_messages") + @@ignore +} + +model Term { + id String @id + term String + original String? + definition String + locale String + approved Int + base_id String? + author_id String? + deleted Int @default(0) + flags String @default("[]") + category String? + images String @default("") + key String? + users User? @relation(fields: [author_id], references: [id], onUpdate: NoAction) + + @@index([term], map: "terms_term") + @@index([locale], map: "terms_locale") + @@map("terms") +} + +model UserConnection { + id String @id + from_profileId String + to_userId String + relationship String + users User @relation(fields: [to_userId], references: [id], onDelete: Cascade, onUpdate: NoAction) + profiles Profile @relation(fields: [from_profileId], references: [id], onDelete: Cascade, onUpdate: NoAction) + + @@index([to_userId], map: "user_connections_to_userId") + @@index([from_profileId], map: "user_connections_from_profileId") + @@map("user_connections") +} + +model UserMessage { + id String @id + userId String + adminId String + message String + + @@map("user_messages") +} + +model User { + id String @id + username String @unique(map: "users_username") + email String @unique(map: "users_email") + roles String + avatarSource String? + bannedReason String? + suspiciousChecked Unsupported("tinyint") @default(dbgenerated("0")) + usernameNorm String? @unique(map: "users_usernameNorm") + bannedTerms String? + bannedBy String? + lastActive Int? + banSnapshot String? + inactiveWarning Int? + adminNotifications Int @default(7) + loginAttempts String? + timesheets String? + socialLookup Int @default(0) + authenticators Authenticator[] + ban_proposals_ban_proposals_bannedByTousers BanProposal[] @relation("ban_proposals_bannedByTousers") + ban_proposals_ban_proposals_userIdTousers BanProposal[] @relation("ban_proposals_userIdTousers") + inclusive Inclusive[] + nouns Noun[] + profiles Profile[] + reports_reports_reporterIdTousers Report[] @relation("reports_reporterIdTousers") + reports_reports_userIdTousers Report[] @relation("reports_userIdTousers") + social_lookup SocialLookup[] @ignore + sources Source[] + terms Term[] + user_connections UserConnection[] + + @@map("users") +} diff --git a/new/backend/src/config.ts b/new/backend/src/config.ts index ae48a43b6..cabd5ab76 100644 --- a/new/backend/src/config.ts +++ b/new/backend/src/config.ts @@ -22,6 +22,9 @@ export interface Config { host: string; port: number; }; + database: { + url: string; + } security: { secret: string; }; @@ -86,6 +89,9 @@ export function loadConfigFromEnv(): Config { host: envVarOrDefault("HTTP_HOST", identity, "0.0.0.0"), port: envVarOrDefault("HTTP_PORT", parseInteger, 4000), }, + database: { + url: envVarNotNull("DATABASE_URL", identity), + }, security: { secret: envVarNotNull("SECURITY_SECRET", identity), }, diff --git a/new/pnpm-lock.yaml b/new/pnpm-lock.yaml index 52938c8de..c59556349 100644 --- a/new/pnpm-lock.yaml +++ b/new/pnpm-lock.yaml @@ -32,6 +32,9 @@ importers: '@fastify/type-provider-typebox': specifier: ^3.5.0 version: 3.5.0(@sinclair/typebox@0.31.14) + '@prisma/client': + specifier: 5.3.1 + version: 5.3.1(prisma@5.3.1) '@pronounspage/common': specifier: workspace:* version: link:../common @@ -63,6 +66,9 @@ importers: pino-pretty: specifier: ^10.2.0 version: 10.2.0 + prisma: + specifier: ^5.3.1 + version: 5.3.1 rimraf: specifier: ^5.0.1 version: 5.0.1 @@ -220,6 +226,28 @@ packages: dev: true optional: true + /@prisma/client@5.3.1(prisma@5.3.1): + resolution: {integrity: sha512-ArOKjHwdFZIe1cGU56oIfy7wRuTn0FfZjGuU/AjgEBOQh+4rDkB6nF+AGHP8KaVpkBIiHGPQh3IpwQ3xDMdO0Q==} + engines: {node: '>=16.13'} + requiresBuild: true + peerDependencies: + prisma: '*' + peerDependenciesMeta: + prisma: + optional: true + dependencies: + '@prisma/engines-version': 5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59 + prisma: 5.3.1 + dev: false + + /@prisma/engines-version@5.3.1-2.61e140623197a131c2a6189271ffee05a7aa9a59: + resolution: {integrity: sha512-y5qbUi3ql2Xg7XraqcXEdMHh0MocBfnBzDn5GbV1xk23S3Mq8MGs+VjacTNiBh3dtEdUERCrUUG7Z3QaJ+h79w==} + dev: false + + /@prisma/engines@5.3.1: + resolution: {integrity: sha512-6QkILNyfeeN67BNEPEtkgh3Xo2tm6D7V+UhrkBbRHqKw9CTaz/vvTP/ROwYSP/3JT2MtIutZm/EnhxUiuOPVDA==} + requiresBuild: true + /@sinclair/typebox@0.31.14: resolution: {integrity: sha512-2spk0ie6J/4r+nwb55OtBXUn5cZLF9S98fopIjuutBVoq8yLRNh+h8QvMkCjMu5gWBMnnZ/PUSXeHE3xGBPKLQ==} dev: false @@ -1844,6 +1872,14 @@ packages: hasBin: true dev: true + /prisma@5.3.1: + resolution: {integrity: sha512-Wp2msQIlMPHe+5k5Od6xnsI/WNG7UJGgFUJgqv/ygc7kOECZapcSz/iU4NIEzISs3H1W9sFLjAPbg/gOqqtB7A==} + engines: {node: '>=16.13'} + hasBin: true + requiresBuild: true + dependencies: + '@prisma/engines': 5.3.1 + /process-warning@2.2.0: resolution: {integrity: sha512-/1WZ8+VQjR6avWOgHeEPd7SDQmFQ1B5mC1eRXsCm5TarlNmx/wCsa5GEaxGm05BORRtyG/Ex/3xq3TuRvq57qg==} dev: false