From f9199b4f2d41c0b25667a232c51cd4bce1175c78 Mon Sep 17 00:00:00 2001 From: Bianca Date: Thu, 9 Jul 2026 15:02:53 +0200 Subject: [PATCH 1/4] feat: add guides matricole schema and router --- src/db/schema/web/index.ts | 3 +- src/db/schema/web/matricole.ts | 16 +++++ src/routers/web/index.ts | 3 +- src/routers/web/matricole.ts | 114 +++++++++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 src/db/schema/web/matricole.ts create mode 100644 src/routers/web/matricole.ts diff --git a/src/db/schema/web/index.ts b/src/db/schema/web/index.ts index a460a87..3e31132 100644 --- a/src/db/schema/web/index.ts +++ b/src/db/schema/web/index.ts @@ -1,6 +1,7 @@ import * as associations from "./associations" import * as faqs from "./faqs" +import * as matricole from "./matricole" import * as projects from "./projects" import * as test from "./test" -export const schema = { ...associations, ...test, ...faqs, ...projects } +export const schema = { ...associations, ...test, ...faqs, ...projects, ...matricole } diff --git a/src/db/schema/web/matricole.ts b/src/db/schema/web/matricole.ts new file mode 100644 index 0000000..a11a37c --- /dev/null +++ b/src/db/schema/web/matricole.ts @@ -0,0 +1,16 @@ +import { bigint, integer, text } from "drizzle-orm/pg-core" +import { timeColumns } from "@/db/columns" +import { createTable } from "../create-table" +import { permissions } from "../tg/permissions" + +export const guidesMatricole = createTable.web("guides_matricole", { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + version: text("version").notNull(), + date: text("date").notNull(), + file: text("file").notNull(), + createdBy: bigint("created_by_id", { mode: "number" }) + .references(() => permissions.userId) + .notNull(), + modifiedBy: bigint("modified_by_id", { mode: "number" }).references(() => permissions.userId), + ...timeColumns, +}) diff --git a/src/routers/web/index.ts b/src/routers/web/index.ts index 5b050c8..b031329 100644 --- a/src/routers/web/index.ts +++ b/src/routers/web/index.ts @@ -1,6 +1,7 @@ import { createTRPCRouter } from "@/trpc" import associations from "./associations" import faqs from "./faqs" +import matricole from "./matricole" import projects from "./projects" -export const webRouter = createTRPCRouter({ associations, faqs, projects }) +export const webRouter = createTRPCRouter({ associations, faqs, projects, matricole }) diff --git a/src/routers/web/matricole.ts b/src/routers/web/matricole.ts new file mode 100644 index 0000000..2bb7d1c --- /dev/null +++ b/src/routers/web/matricole.ts @@ -0,0 +1,114 @@ +import { desc, eq } from "drizzle-orm" +import z from "zod" +import { uploadBlob } from "@/azure/blob" +import { DB, SCHEMA } from "@/db" +import { createTRPCRouter, publicProcedure } from "@/trpc" + +const GUIDES_MATRICOLE = SCHEMA.WEB.guidesMatricole + +const guideFileSchema = z + .file() + .mime(["application/pdf"]) + .min(1) + .max(10 * 1024 * 1024) + +const guideFormSchema = z.object({ + version: z.string(), + date: z.string(), +}) + +const guideSchema = z.object({ + id: z.number(), + version: z.string(), + date: z.string(), + file: z.string(), +}) + +export default createTRPCRouter({ + getAllGuides: publicProcedure.output(z.array(guideSchema)).query(async () => { + return await DB.select().from(GUIDES_MATRICOLE).orderBy(desc(GUIDES_MATRICOLE.date)) + }), + + getLatestGuide: publicProcedure.output(guideSchema.nullable()).query(async () => { + const [latestGuide] = await DB.select().from(GUIDES_MATRICOLE).orderBy(desc(GUIDES_MATRICOLE.date)).limit(1) + + return latestGuide || null + }), + + addGuide: publicProcedure + .input( + z + .instanceof(FormData) + .transform((fd): Record => Object.fromEntries(fd.entries())) + .pipe( + guideFormSchema.extend({ + file: guideFileSchema, + createdBy: z.coerce.number(), + }) + ) + ) + .mutation(async ({ input }) => { + const { version, date, file, createdBy } = input + + const uploadedFile = await uploadBlob(Buffer.from(await file.arrayBuffer()), "pdf", file.type) + + const [res] = await DB.insert(GUIDES_MATRICOLE) + .values({ + version, + date, + file: uploadedFile.url, + createdBy, + }) + .returning() + + return res + }), + + editGuide: publicProcedure + .input( + z + .instanceof(FormData) + .transform((fd): Record => Object.fromEntries(fd.entries())) + .pipe( + guideFormSchema.extend({ + id: z.coerce.number(), + file: guideFileSchema.optional(), + modifiedBy: z.coerce.number(), + }) + ) + ) + .mutation(async ({ input }) => { + const { id, version, date, file, modifiedBy } = input + + const uploadedFile = file + ? await uploadBlob(Buffer.from(await file.arrayBuffer()), "pdf", file.type) + : undefined + + const [res] = await DB.update(GUIDES_MATRICOLE) + .set({ + version, + date, + ...(uploadedFile ? { file: uploadedFile.url } : {}), + modifiedBy, + }) + .where(eq(GUIDES_MATRICOLE.id, id)) + .returning() + + if (!res) return { error: "NOT_FOUND" } + return res + }), + + deleteGuide: publicProcedure + .input( + z.object({ + id: z.number(), + }) + ) + .mutation(async ({ input }) => { + const { id } = input + const deleted = await DB.delete(GUIDES_MATRICOLE).where(eq(GUIDES_MATRICOLE.id, id)).returning() + + if (deleted.length === 0) return { error: "NOT_FOUND" } + return { error: null } + }), +}) From 3296d10b07fb1506d9effc2ed51609318036a912 Mon Sep 17 00:00:00 2001 From: Bianca Date: Thu, 9 Jul 2026 16:08:09 +0200 Subject: [PATCH 2/4] feat: enforce unique version constraint and prevent duplicate entries in guides matricole --- .../web/{matricole.ts => guides_matricole.ts} | 2 +- src/db/schema/web/index.ts | 4 +- .../web/{matricole.ts => guides_matricole.ts} | 39 +++---------------- src/routers/web/index.ts | 4 +- 4 files changed, 10 insertions(+), 39 deletions(-) rename src/db/schema/web/{matricole.ts => guides_matricole.ts} (92%) rename src/routers/web/{matricole.ts => guides_matricole.ts} (68%) diff --git a/src/db/schema/web/matricole.ts b/src/db/schema/web/guides_matricole.ts similarity index 92% rename from src/db/schema/web/matricole.ts rename to src/db/schema/web/guides_matricole.ts index a11a37c..4cf1c03 100644 --- a/src/db/schema/web/matricole.ts +++ b/src/db/schema/web/guides_matricole.ts @@ -5,7 +5,7 @@ import { permissions } from "../tg/permissions" export const guidesMatricole = createTable.web("guides_matricole", { id: integer().primaryKey().generatedAlwaysAsIdentity(), - version: text("version").notNull(), + version: text("version").notNull().unique(), date: text("date").notNull(), file: text("file").notNull(), createdBy: bigint("created_by_id", { mode: "number" }) diff --git a/src/db/schema/web/index.ts b/src/db/schema/web/index.ts index 3e31132..d8d43e6 100644 --- a/src/db/schema/web/index.ts +++ b/src/db/schema/web/index.ts @@ -1,7 +1,7 @@ import * as associations from "./associations" import * as faqs from "./faqs" -import * as matricole from "./matricole" +import * as guides_matricole from "./guides_matricole" import * as projects from "./projects" import * as test from "./test" -export const schema = { ...associations, ...test, ...faqs, ...projects, ...matricole } +export const schema = { ...associations, ...test, ...faqs, ...projects, ...guides_matricole } diff --git a/src/routers/web/matricole.ts b/src/routers/web/guides_matricole.ts similarity index 68% rename from src/routers/web/matricole.ts rename to src/routers/web/guides_matricole.ts index 2bb7d1c..b2f9d6b 100644 --- a/src/routers/web/matricole.ts +++ b/src/routers/web/guides_matricole.ts @@ -50,6 +50,11 @@ export default createTRPCRouter({ .mutation(async ({ input }) => { const { version, date, file, createdBy } = input + const [existing] = await DB.select({ id: GUIDES_MATRICOLE.id }) + .from(GUIDES_MATRICOLE) + .where(eq(GUIDES_MATRICOLE.version, version)) + if (existing) return { error: "DUPLICATE_VERSION" } + const uploadedFile = await uploadBlob(Buffer.from(await file.arrayBuffer()), "pdf", file.type) const [res] = await DB.insert(GUIDES_MATRICOLE) @@ -64,40 +69,6 @@ export default createTRPCRouter({ return res }), - editGuide: publicProcedure - .input( - z - .instanceof(FormData) - .transform((fd): Record => Object.fromEntries(fd.entries())) - .pipe( - guideFormSchema.extend({ - id: z.coerce.number(), - file: guideFileSchema.optional(), - modifiedBy: z.coerce.number(), - }) - ) - ) - .mutation(async ({ input }) => { - const { id, version, date, file, modifiedBy } = input - - const uploadedFile = file - ? await uploadBlob(Buffer.from(await file.arrayBuffer()), "pdf", file.type) - : undefined - - const [res] = await DB.update(GUIDES_MATRICOLE) - .set({ - version, - date, - ...(uploadedFile ? { file: uploadedFile.url } : {}), - modifiedBy, - }) - .where(eq(GUIDES_MATRICOLE.id, id)) - .returning() - - if (!res) return { error: "NOT_FOUND" } - return res - }), - deleteGuide: publicProcedure .input( z.object({ diff --git a/src/routers/web/index.ts b/src/routers/web/index.ts index b031329..295165e 100644 --- a/src/routers/web/index.ts +++ b/src/routers/web/index.ts @@ -1,7 +1,7 @@ import { createTRPCRouter } from "@/trpc" import associations from "./associations" import faqs from "./faqs" -import matricole from "./matricole" +import guides_matricole from "./guides_matricole" import projects from "./projects" -export const webRouter = createTRPCRouter({ associations, faqs, projects, matricole }) +export const webRouter = createTRPCRouter({ associations, faqs, projects, guides_matricole }) From 06912aff79a5a6bb03980fe80a4c7a2c9ec2cc18 Mon Sep 17 00:00:00 2001 From: Bianca Date: Fri, 10 Jul 2026 09:19:59 +0200 Subject: [PATCH 3/4] feat: add migration --- drizzle/0015_equal_the_spike.sql | 14 + drizzle/meta/0015_snapshot.json | 1799 ++++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + 3 files changed, 1820 insertions(+) create mode 100644 drizzle/0015_equal_the_spike.sql create mode 100644 drizzle/meta/0015_snapshot.json diff --git a/drizzle/0015_equal_the_spike.sql b/drizzle/0015_equal_the_spike.sql new file mode 100644 index 0000000..ca95f18 --- /dev/null +++ b/drizzle/0015_equal_the_spike.sql @@ -0,0 +1,14 @@ +CREATE TABLE "web_guides_matricole" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "web_guides_matricole_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "version" text NOT NULL, + "date" text NOT NULL, + "file" text NOT NULL, + "created_by_id" bigint NOT NULL, + "modified_by_id" bigint, + "updated_at" timestamp (0) with time zone, + "created_at" timestamp (0) with time zone DEFAULT now() NOT NULL, + CONSTRAINT "web_guides_matricole_version_unique" UNIQUE("version") +); +--> statement-breakpoint +ALTER TABLE "web_guides_matricole" ADD CONSTRAINT "web_guides_matricole_created_by_id_tg_permissions_user_id_fk" FOREIGN KEY ("created_by_id") REFERENCES "public"."tg_permissions"("user_id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "web_guides_matricole" ADD CONSTRAINT "web_guides_matricole_modified_by_id_tg_permissions_user_id_fk" FOREIGN KEY ("modified_by_id") REFERENCES "public"."tg_permissions"("user_id") ON DELETE no action ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0015_snapshot.json b/drizzle/meta/0015_snapshot.json new file mode 100644 index 0000000..c2d2549 --- /dev/null +++ b/drizzle/meta/0015_snapshot.json @@ -0,0 +1,1799 @@ +{ + "id": "11c6c7db-919f-47b7-bca7-a1614891aad1", + "prevId": "89bf3898-1916-4fec-986f-938f3fc9e1a2", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.auth_accounts": { + "name": "auth_accounts", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "account_userId_idx": { + "name": "account_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_accounts_user_id_auth_users_id_fk": { + "name": "auth_accounts_user_id_auth_users_id_fk", + "tableFrom": "auth_accounts", + "tableTo": "auth_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_passkey": { + "name": "auth_passkey", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "credential_id": { + "name": "credential_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "counter": { + "name": "counter", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "device_type": { + "name": "device_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "backed_up": { + "name": "backed_up", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "transports": { + "name": "transports", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "aaguid": { + "name": "aaguid", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "passkey_userId_idx": { + "name": "passkey_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "passkey_credentialID_idx": { + "name": "passkey_credentialID_idx", + "columns": [ + { + "expression": "credential_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_passkey_user_id_auth_users_id_fk": { + "name": "auth_passkey_user_id_auth_users_id_fk", + "tableFrom": "auth_passkey", + "tableTo": "auth_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_sessions": { + "name": "auth_sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "session_userId_idx": { + "name": "session_userId_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "auth_sessions_user_id_auth_users_id_fk": { + "name": "auth_sessions_user_id_auth_users_id_fk", + "tableFrom": "auth_sessions", + "tableTo": "auth_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_sessions_token_unique": { + "name": "auth_sessions_token_unique", + "nullsNotDistinct": false, + "columns": [ + "token" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_users": { + "name": "auth_users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "name": "email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tg_id": { + "name": "tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "tg_username": { + "name": "tg_username", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "auth_users_email_unique": { + "name": "auth_users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.auth_verifications": { + "name": "auth_verifications", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "verification_identifier_idx": { + "name": "verification_identifier_idx", + "columns": [ + { + "expression": "identifier", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_audit_log": { + "name": "tg_audit_log", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tg_audit_log_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "admin_id": { + "name": "admin_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "type": { + "name": "type", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "until": { + "name": "until", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "varchar(256)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "auditlog_adminid_idx": { + "name": "auditlog_adminid_idx", + "columns": [ + { + "expression": "admin_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_grants": { + "name": "tg_grants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tg_grants_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "granted_by_id": { + "name": "granted_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "valid_since": { + "name": "valid_since", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true + }, + "valid_until": { + "name": "valid_until", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true + }, + "interrupted_by_id": { + "name": "interrupted_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "tg_grants_user_id_idx": { + "name": "tg_grants_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "tg_grants_granted_by_id_tg_permissions_user_id_fk": { + "name": "tg_grants_granted_by_id_tg_permissions_user_id_fk", + "tableFrom": "tg_grants", + "tableTo": "tg_permissions", + "columnsFrom": [ + "granted_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "tg_grants_interrupted_by_id_tg_permissions_user_id_fk": { + "name": "tg_grants_interrupted_by_id_tg_permissions_user_id_fk", + "tableFrom": "tg_grants", + "tableTo": "tg_permissions", + "columnsFrom": [ + "interrupted_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_groups": { + "name": "tg_groups", + "schema": "", + "columns": { + "telegram_id": { + "name": "telegram_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar", + "primaryKey": false, + "notNull": true + }, + "tag": { + "name": "tag", + "type": "varchar", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "hide": { + "name": "hide", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tg_groups_link_unique": { + "name": "tg_groups_link_unique", + "nullsNotDistinct": false, + "columns": [ + "link" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_link": { + "name": "tg_link", + "schema": "", + "columns": { + "code": { + "name": "code", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "ttl": { + "name": "ttl", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tg_username": { + "name": "tg_username", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "tg_id": { + "name": "tg_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "tg_link_user_id_auth_users_id_fk": { + "name": "tg_link_user_id_auth_users_id_fk", + "tableFrom": "tg_link", + "tableTo": "auth_users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "tg_link_tg_id_unique": { + "name": "tg_link_tg_id_unique", + "nullsNotDistinct": false, + "columns": [ + "tg_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_messages": { + "name": "tg_messages", + "schema": "", + "columns": { + "chat_id": { + "name": "chat_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "message_id": { + "name": "message_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "author_id": { + "name": "author_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true + }, + "message": { + "name": "message", + "type": "varchar(8704)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tg_messages_chat_id_message_id_pk": { + "name": "tg_messages_chat_id_message_id_pk", + "columns": [ + "chat_id", + "message_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_group_admins": { + "name": "tg_group_admins", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "group_id": { + "name": "group_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "added_by_id": { + "name": "added_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "user_id_idx": { + "name": "user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": { + "tg_group_admins_user_id_group_id_pk": { + "name": "tg_group_admins_user_id_group_id_pk", + "columns": [ + "user_id", + "group_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_permissions": { + "name": "tg_permissions", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "roles": { + "name": "roles", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{\"admin\"}'" + }, + "added_by_id": { + "name": "added_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "modified_by_id": { + "name": "modified_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_test": { + "name": "tg_test", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "tg_test_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "text": { + "name": "text", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.tg_users": { + "name": "tg_users", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "bigint", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(192)", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "varchar(192)", + "primaryKey": false, + "notNull": false + }, + "username": { + "name": "username", + "type": "varchar(128)", + "primaryKey": false, + "notNull": false + }, + "is_bot": { + "name": "is_bot", + "type": "boolean", + "primaryKey": false, + "notNull": true + }, + "lang_code": { + "name": "lang_code", + "type": "varchar(35)", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_associations": { + "name": "web_associations", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_associations_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_it": { + "name": "description_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_en": { + "name": "description_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "website": { + "name": "website", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "facebook": { + "name": "facebook", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "instagram": { + "name": "instagram", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tiktok": { + "name": "tiktok", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "x": { + "name": "x", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "youtube": { + "name": "youtube", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "telegram": { + "name": "telegram", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "linkedin": { + "name": "linkedin", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "spotify": { + "name": "spotify", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "modified_by_id": { + "name": "modified_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "web_associations_created_by_id_tg_permissions_user_id_fk": { + "name": "web_associations_created_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_associations", + "tableTo": "tg_permissions", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "web_associations_modified_by_id_tg_permissions_user_id_fk": { + "name": "web_associations_modified_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_associations", + "tableTo": "tg_permissions", + "columnsFrom": [ + "modified_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_faq_categories": { + "name": "web_faq_categories", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_faq_categories_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "title_it": { + "name": "title_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_en": { + "name": "title_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "modified_by_id": { + "name": "modified_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "web_faq_categories_created_by_id_tg_permissions_user_id_fk": { + "name": "web_faq_categories_created_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_faq_categories", + "tableTo": "tg_permissions", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "web_faq_categories_modified_by_id_tg_permissions_user_id_fk": { + "name": "web_faq_categories_modified_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_faq_categories", + "tableTo": "tg_permissions", + "columnsFrom": [ + "modified_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_faqs": { + "name": "web_faqs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_faqs_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "title_it": { + "name": "title_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "title_en": { + "name": "title_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_it": { + "name": "description_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_en": { + "name": "description_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category_id": { + "name": "category_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "modified_by_id": { + "name": "modified_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "web_faqs_category_id_web_faq_categories_id_fk": { + "name": "web_faqs_category_id_web_faq_categories_id_fk", + "tableFrom": "web_faqs", + "tableTo": "web_faq_categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "web_faqs_created_by_id_tg_permissions_user_id_fk": { + "name": "web_faqs_created_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_faqs", + "tableTo": "tg_permissions", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "web_faqs_modified_by_id_tg_permissions_user_id_fk": { + "name": "web_faqs_modified_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_faqs", + "tableTo": "tg_permissions", + "columnsFrom": [ + "modified_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_guides_matricole": { + "name": "web_guides_matricole", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_guides_matricole_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "file": { + "name": "file", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "modified_by_id": { + "name": "modified_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "web_guides_matricole_created_by_id_tg_permissions_user_id_fk": { + "name": "web_guides_matricole_created_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_guides_matricole", + "tableTo": "tg_permissions", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "web_guides_matricole_modified_by_id_tg_permissions_user_id_fk": { + "name": "web_guides_matricole_modified_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_guides_matricole", + "tableTo": "tg_permissions", + "columnsFrom": [ + "modified_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "web_guides_matricole_version_unique": { + "name": "web_guides_matricole_version_unique", + "nullsNotDistinct": false, + "columns": [ + "version" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_projects": { + "name": "web_projects", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_projects_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_it": { + "name": "description_it", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description_en": { + "name": "description_en", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "logo": { + "name": "logo", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'general'" + }, + "order": { + "name": "order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "created_by_id": { + "name": "created_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "modified_by_id": { + "name": "modified_by_id", + "type": "bigint", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp (0) with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "web_projects_created_by_id_tg_permissions_user_id_fk": { + "name": "web_projects_created_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_projects", + "tableTo": "tg_permissions", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "web_projects_modified_by_id_tg_permissions_user_id_fk": { + "name": "web_projects_modified_by_id_tg_permissions_user_id_fk", + "tableFrom": "web_projects", + "tableTo": "tg_permissions", + "columnsFrom": [ + "modified_by_id" + ], + "columnsTo": [ + "user_id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.web_test": { + "name": "web_test", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "identity": { + "type": "always", + "name": "web_test_id_seq", + "schema": "public", + "increment": "1", + "startWith": "1", + "minValue": "1", + "maxValue": "2147483647", + "cache": "1", + "cycle": false + } + }, + "text": { + "name": "text", + "type": "varchar", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 8ad9b32..042c07b 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -106,6 +106,13 @@ "when": 1781819577408, "tag": "0014_regular_blindfold", "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1783667936607, + "tag": "0015_equal_the_spike", + "breakpoints": true } ] } \ No newline at end of file From 8f5fa332ad73a9b00a736f65ac06f326d0785f66 Mon Sep 17 00:00:00 2001 From: Bianca Date: Fri, 10 Jul 2026 12:23:24 +0200 Subject: [PATCH 4/4] feat: implement deleteBlob function and integrate it into deleteGuide mutation --- src/azure/blob.ts | 9 +++++++++ src/routers/web/guides_matricole.ts | 4 +++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/azure/blob.ts b/src/azure/blob.ts index a5c0bbd..6bb3f88 100644 --- a/src/azure/blob.ts +++ b/src/azure/blob.ts @@ -27,3 +27,12 @@ export async function uploadBlob(buffer: Buffer, extension: string, mimeType?: s logger.info(`Upload block blob ${filename} successfully with request ID: ${res.requestId}`) return { filename, url: `https://${storageAccount}.blob.core.windows.net/${containerName}/${filename}` } } + +export async function deleteBlob(url: string) { + const filename = new URL(url).pathname.split("/").pop() + if (!filename) return + + const blobClient = containerClient.getBlockBlobClient(filename) + const res = await blobClient.deleteIfExists() + logger.info(`Delete block blob ${filename} successfully: ${res.succeeded}`) +} diff --git a/src/routers/web/guides_matricole.ts b/src/routers/web/guides_matricole.ts index b2f9d6b..37ffb39 100644 --- a/src/routers/web/guides_matricole.ts +++ b/src/routers/web/guides_matricole.ts @@ -1,6 +1,6 @@ import { desc, eq } from "drizzle-orm" import z from "zod" -import { uploadBlob } from "@/azure/blob" +import { deleteBlob, uploadBlob } from "@/azure/blob" import { DB, SCHEMA } from "@/db" import { createTRPCRouter, publicProcedure } from "@/trpc" @@ -80,6 +80,8 @@ export default createTRPCRouter({ const deleted = await DB.delete(GUIDES_MATRICOLE).where(eq(GUIDES_MATRICOLE.id, id)).returning() if (deleted.length === 0) return { error: "NOT_FOUND" } + + await deleteBlob(deleted[0].file) return { error: null } }), })