From 51bc09fddb7e12fa6a736db44cf96f33fc37630d Mon Sep 17 00:00:00 2001 From: Bianca Date: Thu, 11 Jun 2026 09:49:41 +0200 Subject: [PATCH 1/9] feat: implement projects schema and router --- src/db/schema/web/index.ts | 4 +- src/db/schema/web/projects.ts | 24 +++++++++++ src/routers/web/index.ts | 5 +++ src/routers/web/projects.ts | 77 +++++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/db/schema/web/projects.ts create mode 100644 src/routers/web/index.ts create mode 100644 src/routers/web/projects.ts diff --git a/src/db/schema/web/index.ts b/src/db/schema/web/index.ts index 963fdde..3004aa8 100644 --- a/src/db/schema/web/index.ts +++ b/src/db/schema/web/index.ts @@ -1,2 +1,4 @@ +import * as projects from "./projects" import * as test from "./test" -export const schema = { ...test } + +export const schema = { ...test, ...projects } diff --git a/src/db/schema/web/projects.ts b/src/db/schema/web/projects.ts new file mode 100644 index 0000000..085875f --- /dev/null +++ b/src/db/schema/web/projects.ts @@ -0,0 +1,24 @@ +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 projectsCategories = ["news", "deprecated", "other"] as const + +export type ProjectCategory = (typeof projectsCategories)[number] + +export const projects = createTable.web("projects", { + id: integer().primaryKey().generatedAlwaysAsIdentity(), + title: text("title").notNull(), + descriptionIt: text("description_it").notNull(), + descriptionEn: text("description_en").notNull(), + logo: text("logo"), + link: text("link"), + category: text("category", { enum: projectsCategories }).notNull().default("other"), + order: integer("order").notNull().default(0), + 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 new file mode 100644 index 0000000..552689e --- /dev/null +++ b/src/routers/web/index.ts @@ -0,0 +1,5 @@ +import { createTRPCRouter } from "@/trpc" +import associations from "./associations" +import projects from "./projects" + +export const webRouter = createTRPCRouter({ associations, projects }) diff --git a/src/routers/web/projects.ts b/src/routers/web/projects.ts new file mode 100644 index 0000000..bbff52f --- /dev/null +++ b/src/routers/web/projects.ts @@ -0,0 +1,77 @@ +import { asc, eq } from "drizzle-orm" +import z from "zod" +import { DB, SCHEMA } from "@/db" +import { projectsCategories } from "@/db/schema/web/projects" +import { createTRPCRouter, publicProcedure } from "@/trpc" + +const PROJECTS = SCHEMA.WEB.projects + +const projectSchema = z.object({ + id: z.number(), + title: z.string(), + descriptionIt: z.string(), + descriptionEn: z.string(), + logo: z.string().nullable(), + link: z.string().nullable(), + category: z.enum(projectsCategories), +}) + +export default createTRPCRouter({ + getAllProjects: publicProcedure.output(z.array(projectSchema)).query(async () => { + return await DB.select().from(PROJECTS).orderBy(asc(PROJECTS.order)) + }), + + addProject: publicProcedure.input(projectSchema.extend({ createdBy: z.number() })).mutation(async ({ input }) => { + const { title, descriptionIt, descriptionEn, logo, link, category, createdBy } = input + + const [res] = await DB.insert(PROJECTS) + .values({ + title, + descriptionIt, + descriptionEn, + logo, + link, + category, + createdBy, + order: 0, + }) + .returning() + + return res + }), + + editProject: publicProcedure.input(projectSchema.extend({ modifiedBy: z.number() })).mutation(async ({ input }) => { + const { id, title, descriptionIt, descriptionEn, logo, link, category, modifiedBy } = input + + const [res] = await DB.update(PROJECTS) + .set({ + title, + descriptionIt, + descriptionEn, + logo, + link, + category, + order: 0, + modifiedBy, + }) + .where(eq(PROJECTS.id, id)) + .returning() + + if (!res) return { error: "NOT_FOUND" } + return res + }), + + deleteProject: publicProcedure + .input( + z.object({ + id: z.number(), + }) + ) + .mutation(async ({ input }) => { + const { id } = input + const deleted = await DB.delete(PROJECTS).where(eq(PROJECTS.id, id)).returning() + + if (deleted.length === 0) return { error: "NOT_FOUND" } + return { error: null } + }), +}) From 9d7f62fbeaf98b301c5378896726042806f49355 Mon Sep 17 00:00:00 2001 From: Bianca Date: Thu, 11 Jun 2026 09:49:58 +0200 Subject: [PATCH 2/9] feat: remove associations from web router --- src/routers/web/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/routers/web/index.ts b/src/routers/web/index.ts index 552689e..bbf1bfb 100644 --- a/src/routers/web/index.ts +++ b/src/routers/web/index.ts @@ -1,5 +1,4 @@ import { createTRPCRouter } from "@/trpc" -import associations from "./associations" import projects from "./projects" -export const webRouter = createTRPCRouter({ associations, projects }) +export const webRouter = createTRPCRouter({ projects }) From 67cfe098137808ac939a341e4f553842c16e5ed4 Mon Sep 17 00:00:00 2001 From: Bianca Date: Thu, 11 Jun 2026 13:54:51 +0200 Subject: [PATCH 3/9] rename other in general --- drizzle/0013_dark_vin_gonzales.sql | 17 + drizzle/meta/0013_snapshot.json | 1300 ++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/db/schema/web/projects.ts | 4 +- src/routers/index.ts | 2 + 5 files changed, 1328 insertions(+), 2 deletions(-) create mode 100644 drizzle/0013_dark_vin_gonzales.sql create mode 100644 drizzle/meta/0013_snapshot.json diff --git a/drizzle/0013_dark_vin_gonzales.sql b/drizzle/0013_dark_vin_gonzales.sql new file mode 100644 index 0000000..2d9d986 --- /dev/null +++ b/drizzle/0013_dark_vin_gonzales.sql @@ -0,0 +1,17 @@ +CREATE TABLE "web_projects" ( + "id" integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "web_projects_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 2147483647 START WITH 1 CACHE 1), + "title" text NOT NULL, + "description_it" text NOT NULL, + "description_en" text NOT NULL, + "logo" text, + "link" text, + "category" text DEFAULT 'general' NOT NULL, + "order" integer DEFAULT 0 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 +); +--> statement-breakpoint +ALTER TABLE "web_projects" ADD CONSTRAINT "web_projects_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_projects" ADD CONSTRAINT "web_projects_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; diff --git a/drizzle/meta/0013_snapshot.json b/drizzle/meta/0013_snapshot.json new file mode 100644 index 0000000..29fd216 --- /dev/null +++ b/drizzle/meta/0013_snapshot.json @@ -0,0 +1,1300 @@ +{ + "id": "a1bb65de-4248-46e0-9233-b4eeb4aad059", + "prevId": "ed5cf808-3f4a-45df-b384-69cac0367dc8", + "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_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": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 6025025..11dca49 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1775994260371, "tag": "0012_sudden_sandman", "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1781165047621, + "tag": "0013_dark_vin_gonzales", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schema/web/projects.ts b/src/db/schema/web/projects.ts index 085875f..0dadd18 100644 --- a/src/db/schema/web/projects.ts +++ b/src/db/schema/web/projects.ts @@ -3,7 +3,7 @@ import { timeColumns } from "@/db/columns" import { createTable } from "../create-table" import { permissions } from "../tg/permissions" -export const projectsCategories = ["news", "deprecated", "other"] as const +export const projectsCategories = ["news", "deprecated", "general"] as const export type ProjectCategory = (typeof projectsCategories)[number] @@ -14,7 +14,7 @@ export const projects = createTable.web("projects", { descriptionEn: text("description_en").notNull(), logo: text("logo"), link: text("link"), - category: text("category", { enum: projectsCategories }).notNull().default("other"), + category: text("category", { enum: projectsCategories }).notNull().default("general"), order: integer("order").notNull().default(0), createdBy: bigint("created_by_id", { mode: "number" }) .references(() => permissions.userId) diff --git a/src/routers/index.ts b/src/routers/index.ts index a13c76f..8588b91 100644 --- a/src/routers/index.ts +++ b/src/routers/index.ts @@ -1,3 +1,4 @@ +import { webRouter } from "@/routers/web" import { createTRPCRouter } from "@/trpc" import { authRouter } from "./auth" import { azureRouter } from "./azure" @@ -14,6 +15,7 @@ export const appRouter = createTRPCRouter({ tg: tgRouter, azure: azureRouter, auth: authRouter, + web: webRouter, }) // export type definition of API From f820b43c982519521913767899874bd6bed54016 Mon Sep 17 00:00:00 2001 From: Bianca Date: Thu, 11 Jun 2026 14:03:57 +0200 Subject: [PATCH 4/9] feat: update project schema to include order field in add and edit operations --- src/routers/web/projects.ts | 39 ++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/routers/web/projects.ts b/src/routers/web/projects.ts index bbff52f..fe3513e 100644 --- a/src/routers/web/projects.ts +++ b/src/routers/web/projects.ts @@ -14,6 +14,7 @@ const projectSchema = z.object({ logo: z.string().nullable(), link: z.string().nullable(), category: z.enum(projectsCategories), + order: z.number(), }) export default createTRPCRouter({ @@ -21,27 +22,29 @@ export default createTRPCRouter({ return await DB.select().from(PROJECTS).orderBy(asc(PROJECTS.order)) }), - addProject: publicProcedure.input(projectSchema.extend({ createdBy: z.number() })).mutation(async ({ input }) => { - const { title, descriptionIt, descriptionEn, logo, link, category, createdBy } = input + addProject: publicProcedure + .input(projectSchema.omit({ id: true }).extend({ createdBy: z.number() })) + .mutation(async ({ input }) => { + const { title, descriptionIt, descriptionEn, logo, link, category, createdBy, order } = input - const [res] = await DB.insert(PROJECTS) - .values({ - title, - descriptionIt, - descriptionEn, - logo, - link, - category, - createdBy, - order: 0, - }) - .returning() + const [res] = await DB.insert(PROJECTS) + .values({ + title, + descriptionIt, + descriptionEn, + logo, + link, + category, + createdBy, + order, + }) + .returning() - return res - }), + return res + }), editProject: publicProcedure.input(projectSchema.extend({ modifiedBy: z.number() })).mutation(async ({ input }) => { - const { id, title, descriptionIt, descriptionEn, logo, link, category, modifiedBy } = input + const { id, title, descriptionIt, descriptionEn, logo, link, category, modifiedBy, order } = input const [res] = await DB.update(PROJECTS) .set({ @@ -51,7 +54,7 @@ export default createTRPCRouter({ logo, link, category, - order: 0, + order, modifiedBy, }) .where(eq(PROJECTS.id, id)) From c7780ac03ff3a4eca802d10a0b5dc0e3fcaf9491 Mon Sep 17 00:00:00 2001 From: Bianca Date: Thu, 11 Jun 2026 14:25:26 +0200 Subject: [PATCH 5/9] feat: add reorderProjects mutation to update project order --- src/routers/web/projects.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/routers/web/projects.ts b/src/routers/web/projects.ts index fe3513e..41c371f 100644 --- a/src/routers/web/projects.ts +++ b/src/routers/web/projects.ts @@ -64,6 +64,22 @@ export default createTRPCRouter({ return res }), + reorderProjects: publicProcedure + .input( + z.object({ + projectIds: z.array(z.number().int()).min(1), + }) + ) + .mutation(async ({ input }) => { + await DB.transaction(async (tx) => { + await Promise.all( + input.projectIds.map((id, index) => tx.update(PROJECTS).set({ order: index }).where(eq(PROJECTS.id, id))) + ) + }) + + return { error: null } + }), + deleteProject: publicProcedure .input( z.object({ From 5ca9d25a316e7015a42457ffbb84f5dd886e8b18 Mon Sep 17 00:00:00 2001 From: Bianca Date: Fri, 12 Jun 2026 10:44:00 +0200 Subject: [PATCH 6/9] feat: remove order field from project schema and related operations --- src/routers/web/projects.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/routers/web/projects.ts b/src/routers/web/projects.ts index 41c371f..6054b33 100644 --- a/src/routers/web/projects.ts +++ b/src/routers/web/projects.ts @@ -14,7 +14,6 @@ const projectSchema = z.object({ logo: z.string().nullable(), link: z.string().nullable(), category: z.enum(projectsCategories), - order: z.number(), }) export default createTRPCRouter({ @@ -25,7 +24,7 @@ export default createTRPCRouter({ addProject: publicProcedure .input(projectSchema.omit({ id: true }).extend({ createdBy: z.number() })) .mutation(async ({ input }) => { - const { title, descriptionIt, descriptionEn, logo, link, category, createdBy, order } = input + const { title, descriptionIt, descriptionEn, logo, link, category, createdBy } = input const [res] = await DB.insert(PROJECTS) .values({ @@ -36,7 +35,6 @@ export default createTRPCRouter({ link, category, createdBy, - order, }) .returning() @@ -44,7 +42,7 @@ export default createTRPCRouter({ }), editProject: publicProcedure.input(projectSchema.extend({ modifiedBy: z.number() })).mutation(async ({ input }) => { - const { id, title, descriptionIt, descriptionEn, logo, link, category, modifiedBy, order } = input + const { id, title, descriptionIt, descriptionEn, logo, link, category, modifiedBy } = input const [res] = await DB.update(PROJECTS) .set({ @@ -54,7 +52,6 @@ export default createTRPCRouter({ logo, link, category, - order, modifiedBy, }) .where(eq(PROJECTS.id, id)) From f5081b14d6a80b78303ea1bc251d79d30a97ee4e Mon Sep 17 00:00:00 2001 From: Bianca Date: Fri, 12 Jun 2026 11:05:06 +0200 Subject: [PATCH 7/9] feat: reorderProjects rejects duplicate ids --- src/routers/web/projects.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/routers/web/projects.ts b/src/routers/web/projects.ts index 6054b33..f201860 100644 --- a/src/routers/web/projects.ts +++ b/src/routers/web/projects.ts @@ -64,7 +64,10 @@ export default createTRPCRouter({ reorderProjects: publicProcedure .input( z.object({ - projectIds: z.array(z.number().int()).min(1), + projectIds: z + .array(z.number().int()) + .min(1) + .refine((projectIds) => new Set(projectIds).size === projectIds.length), }) ) .mutation(async ({ input }) => { From 509b74c34255ee33be6cd83246975dd84f8804ad Mon Sep 17 00:00:00 2001 From: Tommaso Morganti Date: Thu, 18 Jun 2026 22:58:54 +0200 Subject: [PATCH 8/9] chore: bump ver --- package.json | 4 ++-- package/package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 86889c6..c228315 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "backend", - "version": "0.16.1", + "version": "0.16.2", "description": "PoliNetwork backend server", "private": true, "keywords": [], @@ -64,4 +64,4 @@ "socket.io": "^4.8.3", "zod": "^4.1.11" } -} +} \ No newline at end of file diff --git a/package/package.json b/package/package.json index 8a27192..24362b8 100644 --- a/package/package.json +++ b/package/package.json @@ -1,6 +1,6 @@ { "name": "@polinetwork/backend", - "version": "0.16.1", + "version": "0.16.2", "description": "Utils to interact with the backend.", "repository": { "type": "git", @@ -34,4 +34,4 @@ "devDependencies": { "typescript": "^5.9.2" } -} +} \ No newline at end of file From f99f31f861214bd27555448a5507abc12deeda03 Mon Sep 17 00:00:00 2001 From: Tommaso Morganti Date: Thu, 18 Jun 2026 23:00:15 +0200 Subject: [PATCH 9/9] fix: merge conflict --- src/routers/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/routers/index.ts b/src/routers/index.ts index b79e661..5d399ca 100644 --- a/src/routers/index.ts +++ b/src/routers/index.ts @@ -4,7 +4,6 @@ import { authRouter } from "./auth" import { azureRouter } from "./azure" import { testRouter } from "./test" import { tgRouter } from "./tg" -import { webRouter } from "./web" /** * This is the primary router for your server. @@ -17,7 +16,6 @@ export const appRouter = createTRPCRouter({ azure: azureRouter, web: webRouter, auth: authRouter, - web: webRouter, }) // export type definition of API