diff --git a/db/index.ts b/db/index.ts new file mode 100644 index 0000000..eeb9f5b --- /dev/null +++ b/db/index.ts @@ -0,0 +1,10 @@ +import { drizzle } from "drizzle-orm/node-postgres" +import { Pool } from "pg" +import {secrets} from "../src/utils/secrets"; + +const pool = new Pool({ + connectionString: secrets.DATABASE_URL, + max: 10, // je nach Last +}) + +export const db = drizzle(pool) \ No newline at end of file diff --git a/db/schema/accounts.ts b/db/schema/accounts.ts new file mode 100644 index 0000000..cc154de --- /dev/null +++ b/db/schema/accounts.ts @@ -0,0 +1,24 @@ +import { + pgTable, + bigint, + timestamp, + text, +} from "drizzle-orm/pg-core" + +export const accounts = pgTable("accounts", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + number: text("number").notNull(), + label: text("label").notNull(), + + description: text("description"), +}) + +export type Account = typeof accounts.$inferSelect +export type NewAccount = typeof accounts.$inferInsert diff --git a/db/schema/auth_profiles.ts b/db/schema/auth_profiles.ts new file mode 100644 index 0000000..e8dc73a --- /dev/null +++ b/db/schema/auth_profiles.ts @@ -0,0 +1,83 @@ +import { + pgTable, + uuid, + text, + timestamp, + date, + boolean, + bigint, + doublePrecision, + jsonb, +} from "drizzle-orm/pg-core" +import { authUsers } from "./auth_users" + +export const authProfiles = pgTable("auth_profiles", { + id: uuid("id").primaryKey().defaultRandom(), + + userId: uuid("user_id").references(() => authUsers.id), + + tenantId: bigint("tenant_id", { mode: "number" }).notNull(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + firstName: text("first_name").notNull(), + lastName: text("last_name").notNull(), + + fullName: text("full_name").generatedAlwaysAs( + `((first_name || ' ') || last_name)` + ), + + mobileTel: text("mobile_tel"), + fixedTel: text("fixed_tel"), + salutation: text("salutation"), + employeeNumber: text("employee_number"), + + weeklyWorkingHours: doublePrecision("weekly_working_hours").default(0), + annualPaidLeaveDays: bigint("annual_paid_leave_days", { mode: "number" }), + + weeklyRegularWorkingHours: jsonb("weekly_regular_working_hours").default("{}"), + + clothingSizeTop: text("clothing_size_top"), + clothingSizeBottom: text("clothing_size_bottom"), + clothingSizeShoe: text("clothing_size_shoe"), + + emailSignature: text("email_signature").default("
Mit freundlichen Grüßen
"), + + birthday: date("birthday"), + entryDate: date("entry_date").defaultNow(), + + automaticHourCorrections: jsonb("automatic_hour_corrections").default("[]"), + + recreationDaysCompensation: boolean("recreation_days_compensation") + .notNull() + .default(true), + + customerForPortal: bigint("customer_for_portal", { mode: "number" }), + + pinnedOnNavigation: jsonb("pinned_on_navigation").notNull().default("[]"), + + email: text("email"), + tokenId: text("token_id"), + + weeklyWorkingDays: doublePrecision("weekly_working_days"), + + oldProfileId: uuid("old_profile_id"), + tempConfig: jsonb("temp_config"), + + stateCode: text("state_code").default("DE-NI"), + + contractType: text("contract_type"), + position: text("position"), + qualification: text("qualification"), + + addressStreet: text("address_street"), + addressZip: text("address_zip"), + addressCity: text("address_city"), + + active: boolean("active").notNull().default(true), +}) + +export type AuthProfile = typeof authProfiles.$inferSelect +export type NewAuthProfile = typeof authProfiles.$inferInsert diff --git a/db/schema/auth_role_permisssions.ts b/db/schema/auth_role_permisssions.ts new file mode 100644 index 0000000..75c95d9 --- /dev/null +++ b/db/schema/auth_role_permisssions.ts @@ -0,0 +1,23 @@ +import { pgTable, uuid, text, timestamp } from "drizzle-orm/pg-core" +import { authRoles } from "./auth_roles" + +export const authRolePermissions = pgTable( + "auth_role_permissions", + { + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + roleId: uuid("role_id") + .notNull() + .references(() => authRoles.id), + + permission: text("permission").notNull(), + }, + (table) => ({ + primaryKey: [table.roleId, table.permission], + }) +) + +export type AuthRolePermission = typeof authRolePermissions.$inferSelect +export type NewAuthRolePermission = typeof authRolePermissions.$inferInsert diff --git a/db/schema/auth_roles.ts b/db/schema/auth_roles.ts new file mode 100644 index 0000000..d2f8d9d --- /dev/null +++ b/db/schema/auth_roles.ts @@ -0,0 +1,19 @@ +import { pgTable, uuid, text, timestamp, bigint } from "drizzle-orm/pg-core" +import { authUsers } from "./auth_users" + +export const authRoles = pgTable("auth_roles", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + description: text("description"), + + createdBy: uuid("created_by").references(() => authUsers.id), + tenantId: bigint("tenant_id", {mode: "number"}), +}) + +export type AuthRole = typeof authRoles.$inferSelect +export type NewAuthRole = typeof authRoles.$inferInsert diff --git a/db/schema/auth_tenant_users.ts b/db/schema/auth_tenant_users.ts new file mode 100644 index 0000000..756a8d5 --- /dev/null +++ b/db/schema/auth_tenant_users.ts @@ -0,0 +1,22 @@ +import { pgTable, uuid, bigint, timestamp } from "drizzle-orm/pg-core" +import { authUsers } from "./auth_users" + +export const authTenantUsers = pgTable( + "auth_tenant_users", + { + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenantId: bigint("tenant_id", { mode: "number" }).notNull(), + userId: uuid("user_id").notNull(), + + createdBy: uuid("created_by").references(() => authUsers.id), + }, + (table) => ({ + primaryKey: [table.tenantId, table.userId], + }) +) + +export type AuthTenantUser = typeof authTenantUsers.$inferSelect +export type NewAuthTenantUser = typeof authTenantUsers.$inferInsert diff --git a/db/schema/auth_user_roles.ts b/db/schema/auth_user_roles.ts new file mode 100644 index 0000000..8df85a5 --- /dev/null +++ b/db/schema/auth_user_roles.ts @@ -0,0 +1,30 @@ +import { pgTable, uuid, bigint, timestamp } from "drizzle-orm/pg-core" +import { authUsers } from "./auth_users" +import { authRoles } from "./auth_roles" + +export const authUserRoles = pgTable( + "auth_user_roles", + { + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + userId: uuid("user_id") + .notNull() + .references(() => authUsers.id), + + roleId: uuid("role_id") + .notNull() + .references(() => authRoles.id), + + tenantId: bigint("tenant_id", { mode: "number" }).notNull(), + + createdBy: uuid("created_by").references(() => authUsers.id), + }, + (table) => ({ + primaryKey: [table.userId, table.roleId, table.tenantId], + }) +) + +export type AuthUserRole = typeof authUserRoles.$inferSelect +export type NewAuthUserRole = typeof authUserRoles.$inferInsert diff --git a/db/schema/auth_users.ts b/db/schema/auth_users.ts new file mode 100644 index 0000000..d6c6a7f --- /dev/null +++ b/db/schema/auth_users.ts @@ -0,0 +1,22 @@ +import { pgTable, uuid, text, boolean, timestamp } from "drizzle-orm/pg-core" + +export const authUsers = pgTable("auth_users", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + email: text("email").notNull(), + passwordHash: text("password_hash").notNull(), + + multiTenant: boolean("multi_tenant").notNull().default(true), + mustChangePassword: boolean("must_change_password").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + + ported: boolean("ported").notNull().default(true), +}) + +export type AuthUser = typeof authUsers.$inferSelect +export type NewAuthUser = typeof authUsers.$inferInsert diff --git a/db/schema/bankaccounts.ts b/db/schema/bankaccounts.ts new file mode 100644 index 0000000..2b431d4 --- /dev/null +++ b/db/schema/bankaccounts.ts @@ -0,0 +1,52 @@ +import { + pgTable, + bigint, + timestamp, + text, + doublePrecision, + boolean, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const bankaccounts = pgTable("bankaccounts", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name"), + iban: text("iban").notNull(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + bankId: text("bankId").notNull(), + ownerName: text("ownerName"), + + accountId: text("accountId").notNull(), + + balance: doublePrecision("balance"), + + expired: boolean("expired").notNull().default(false), + + datevNumber: text("datevNumber"), + + syncedAt: timestamp("synced_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + archived: boolean("archived").notNull().default(false), +}) + +export type BankAccount = typeof bankaccounts.$inferSelect +export type NewBankAccount = typeof bankaccounts.$inferInsert diff --git a/db/schema/bankrequisitions.ts b/db/schema/bankrequisitions.ts new file mode 100644 index 0000000..3cfd18e --- /dev/null +++ b/db/schema/bankrequisitions.ts @@ -0,0 +1,30 @@ +import { + pgTable, + uuid, + timestamp, + text, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const bankrequisitions = pgTable("bankrequisitions", { + id: uuid("id").primaryKey(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + institutionId: text("institutionId"), + + tenant: bigint("tenant", { mode: "number" }).references(() => tenants.id), + + status: text("status"), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type BankRequisition = typeof bankrequisitions.$inferSelect +export type NewBankRequisition = typeof bankrequisitions.$inferInsert diff --git a/db/schema/bankstatements.ts b/db/schema/bankstatements.ts new file mode 100644 index 0000000..bce2af7 --- /dev/null +++ b/db/schema/bankstatements.ts @@ -0,0 +1,70 @@ +import { + pgTable, + bigint, + timestamp, + text, + doublePrecision, + boolean, + uuid, +} from "drizzle-orm/pg-core" + +import { bankaccounts } from "./bankaccounts" +import { createddocuments } from "./createddocuments" +import { tenants } from "./tenants" +import { incominginvoices } from "./incominginvoices" +import { contracts } from "./contracts" +import { authUsers } from "./auth_users" + +export const bankstatements = pgTable("bankstatements", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + account: bigint("account", { mode: "number" }) + .notNull() + .references(() => bankaccounts.id), + + date: text("date").notNull(), + + credIban: text("credIban"), + credName: text("credName"), + + text: text("text"), + amount: doublePrecision("amount").notNull(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + debIban: text("debIban"), + debName: text("debName"), + gocardlessId: text("gocardlessId"), + currency: text("currency"), + valueDate: text("valueDate"), + + incomingInvoice: bigint("incomingInvoice", { mode: "number" }).references( + () => incominginvoices.id + ), + + mandateId: text("mandateId"), + + contract: bigint("contract", { mode: "number" }).references( + () => contracts.id + ), + + createdDocument: bigint("createdDocument", { mode: "number" }).references( + () => createddocuments.id + ), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type BankStatement = typeof bankstatements.$inferSelect +export type NewBankStatement = typeof bankstatements.$inferInsert diff --git a/db/schema/checkexecutions.ts b/db/schema/checkexecutions.ts new file mode 100644 index 0000000..455877c --- /dev/null +++ b/db/schema/checkexecutions.ts @@ -0,0 +1,27 @@ +import { + pgTable, + uuid, + timestamp, + text, +} from "drizzle-orm/pg-core" + +import { checks } from "./checks" + +export const checkexecutions = pgTable("checkexecutions", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + check: uuid("check").references(() => checks.id), + + executedAt: timestamp("executed_at"), + + // ❌ executed_by removed (was 0_profiles) + + description: text("description"), +}) + +export type CheckExecution = typeof checkexecutions.$inferSelect +export type NewCheckExecution = typeof checkexecutions.$inferInsert diff --git a/db/schema/checks.ts b/db/schema/checks.ts new file mode 100644 index 0000000..744b615 --- /dev/null +++ b/db/schema/checks.ts @@ -0,0 +1,52 @@ +import { + pgTable, + uuid, + timestamp, + text, + bigint, + boolean, + jsonb, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { vehicles } from "./vehicles" +import { inventoryItems } from "./inventoryitems" +import { authUsers } from "./auth_users" + +export const checks = pgTable("checks", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + vehicle: bigint("vehicle", { mode: "number" }) + .references(() => vehicles.id), + + // ❌ profile removed (old 0_profiles reference) + + inventoryItem: bigint("inventoryitem", { mode: "number" }) + .references(() => inventoryItems.id), + + tenant: bigint("tenant", { mode: "number" }) + .references(() => tenants.id), + + name: text("name"), + type: text("type"), + + distance: bigint("distance", { mode: "number" }).default(1), + + distanceUnit: text("distanceUnit").default("days"), + + description: text("description"), + + profiles: jsonb("profiles").notNull().default([]), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Check = typeof checks.$inferSelect +export type NewCheck = typeof checks.$inferInsert diff --git a/db/schema/citys.ts b/db/schema/citys.ts new file mode 100644 index 0000000..e54e5fd --- /dev/null +++ b/db/schema/citys.ts @@ -0,0 +1,32 @@ +import { + pgTable, + bigint, + text, + jsonb, +} from "drizzle-orm/pg-core" + +export const citys = pgTable("citys", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + name: text("name"), + short: text("short"), + long: text("long"), + + geometry: jsonb("geometry"), + + zip: bigint("zip", { mode: "number" }), + + districtCode: bigint("districtCode", { mode: "number" }), + + countryName: text("countryName"), + countryCode: bigint("countryCode", { mode: "number" }), + + districtName: text("districtName"), + + geopoint: text("geopoint"), +}) + +export type City = typeof citys.$inferSelect +export type NewCity = typeof citys.$inferInsert diff --git a/db/schema/contacts.ts b/db/schema/contacts.ts new file mode 100644 index 0000000..b3f3824 --- /dev/null +++ b/db/schema/contacts.ts @@ -0,0 +1,66 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + date, + uuid, +} from "drizzle-orm/pg-core" + +import { customers } from "./customers" +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const contacts = pgTable( + "contacts", + { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + firstName: text("firstName"), + lastName: text("lastName"), + email: text("email"), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id + ), + + tenant: bigint("tenant", { mode: "number" }).notNull(), + + phoneMobile: text("phoneMobile"), + phoneHome: text("phoneHome"), + + heroId: text("heroId"), + role: text("role"), + + fullName: text("fullName"), + + salutation: text("salutation"), + + vendor: bigint("vendor", { mode: "number" }), // vendors folgt separat + + active: boolean("active").notNull().default(true), + + birthday: date("birthday"), + notes: text("notes"), + + profiles: jsonb("profiles").notNull().default([]), + + archived: boolean("archived").notNull().default(false), + + title: text("title"), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + } +) + +export type Contact = typeof contacts.$inferSelect +export type NewContact = typeof contacts.$inferInsert diff --git a/db/schema/contracts.ts b/db/schema/contracts.ts new file mode 100644 index 0000000..3673395 --- /dev/null +++ b/db/schema/contracts.ts @@ -0,0 +1,76 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { customers } from "./customers" +import { contacts } from "./contacts" +import { authUsers } from "./auth_users" + +export const contracts = pgTable( + "contracts", + { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }).notNull(), + + name: text("name").notNull(), + + customer: bigint("customer", { mode: "number" }) + .notNull() + .references(() => customers.id), + + notes: text("notes"), + + active: boolean("active").notNull().default(true), + recurring: boolean("recurring").notNull().default(false), + + rhythm: jsonb("rhythm"), + + startDate: timestamp("startDate", { withTimezone: true }), + endDate: timestamp("endDate", { withTimezone: true }), + signDate: timestamp("signDate", { withTimezone: true }), + + duration: text("duration"), + + contact: bigint("contact", { mode: "number" }).references( + () => contacts.id + ), + + bankingIban: text("bankingIban"), + bankingBIC: text("bankingBIC"), + bankingName: text("bankingName"), + bankingOwner: text("bankingOwner"), + sepaRef: text("sepaRef"), + sepaDate: timestamp("sepaDate", { withTimezone: true }), + + paymentType: text("paymentType"), + invoiceDispatch: text("invoiceDispatch"), + + ownFields: jsonb("ownFields").notNull().default({}), + profiles: jsonb("profiles").notNull().default([]), + + archived: boolean("archived").notNull().default(false), + + contractNumber: text("contractNumber"), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + + updatedBy: uuid("updated_by").references(() => authUsers.id), + } +) + +export type Contract = typeof contracts.$inferSelect +export type NewContract = typeof contracts.$inferInsert diff --git a/db/schema/costcentres.ts b/db/schema/costcentres.ts new file mode 100644 index 0000000..b57a21a --- /dev/null +++ b/db/schema/costcentres.ts @@ -0,0 +1,50 @@ +import { + pgTable, + uuid, + timestamp, + text, + boolean, + jsonb, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { inventoryItems } from "./inventoryitems" +import { projects } from "./projects" +import { vehicles } from "./vehicles" +import { authUsers } from "./auth_users" + +export const costcentres = pgTable("costcentres", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + number: text("number").notNull(), + name: text("name").notNull(), + + vehicle: bigint("vehicle", { mode: "number" }).references(() => vehicles.id), + + project: bigint("project", { mode: "number" }).references(() => projects.id), + + inventoryitem: bigint("inventoryitem", { mode: "number" }).references( + () => inventoryItems.id + ), + + description: text("description"), + + archived: boolean("archived").notNull().default(false), + + profiles: jsonb("profiles").notNull().default([]), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type CostCentre = typeof costcentres.$inferSelect +export type NewCostCentre = typeof costcentres.$inferInsert diff --git a/db/schema/countrys.ts b/db/schema/countrys.ts new file mode 100644 index 0000000..9eb3e9f --- /dev/null +++ b/db/schema/countrys.ts @@ -0,0 +1,21 @@ +import { + pgTable, + bigint, + timestamp, + text, +} from "drizzle-orm/pg-core" + +export const countrys = pgTable("countrys", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), +}) + +export type Country = typeof countrys.$inferSelect +export type NewCountry = typeof countrys.$inferInsert diff --git a/db/schema/createddocuments.ts b/db/schema/createddocuments.ts new file mode 100644 index 0000000..dca90d6 --- /dev/null +++ b/db/schema/createddocuments.ts @@ -0,0 +1,121 @@ +import { + pgTable, + bigint, + timestamp, + text, + jsonb, + boolean, + smallint, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { customers } from "./customers" +import { contacts } from "./contacts" +import { contracts } from "./contracts" +import { letterheads } from "./letterheads" +import { projects } from "./projects" +import { plants } from "./plants" +import { authUsers } from "./auth_users" + +export const createddocuments = pgTable("createddocuments", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + type: text("type").notNull().default("INVOICE"), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id + ), + + contact: bigint("contact", { mode: "number" }).references( + () => contacts.id + ), + + address: jsonb("address"), + project: bigint("project", { mode: "number" }).references( + () => projects.id + ), + + documentNumber: text("documentNumber"), + documentDate: text("documentDate"), + + state: text("state").notNull().default("Entwurf"), + + info: jsonb("info"), + + createdBy: uuid("createdBy").references(() => authUsers.id), + + title: text("title"), + description: text("description"), + + startText: text("startText"), + endText: text("endText"), + + rows: jsonb("rows").default([]), + + deliveryDateType: text("deliveryDateType"), + paymentDays: smallint("paymentDays"), + deliveryDate: text("deliveryDate"), + + contactPerson: uuid("contactPerson"), + + serialConfig: jsonb("serialConfig").default({}), + + linkedDocument: bigint("linkedDocument", { mode: "number" }).references( + () => createddocuments.id + ), + + agriculture: jsonb("agriculture"), + + letterhead: bigint("letterhead", { mode: "number" }).references( + () => letterheads.id + ), + + advanceInvoiceResolved: boolean("advanceInvoiceResolved") + .notNull() + .default(false), + + usedAdvanceInvoices: jsonb("usedAdvanceInvoices").notNull().default([]), + + archived: boolean("archived").notNull().default(false), + + deliveryDateEnd: text("deliveryDateEnd"), + + plant: bigint("plant", { mode: "number" }).references(() => plants.id), + + taxType: text("taxType"), + + customSurchargePercentage: smallint("customSurchargePercentage") + .notNull() + .default(0), + + report: jsonb("report").notNull().default({}), + + availableInPortal: boolean("availableInPortal") + .notNull() + .default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + created_by: uuid("created_by").references(() => authUsers.id), + + payment_type: text("payment_type").default("transfer"), + + contract: bigint("contract", { mode: "number" }).references( + () => contracts.id + ), +}) + +export type CreatedDocument = typeof createddocuments.$inferSelect +export type NewCreatedDocument = typeof createddocuments.$inferInsert diff --git a/db/schema/createdletters.ts b/db/schema/createdletters.ts new file mode 100644 index 0000000..a180624 --- /dev/null +++ b/db/schema/createdletters.ts @@ -0,0 +1,43 @@ +import { + pgTable, + uuid, + timestamp, + bigint, + text, + jsonb, + boolean, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { customers } from "./customers" +import { vendors } from "./vendors" +import { authUsers } from "./auth_users" + +export const createdletters = pgTable("createdletters", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }).references(() => tenants.id), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id + ), + + vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id), + + contentJson: jsonb("content_json").default([]), + + contentText: text("content_text"), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + + updatedBy: uuid("updated_by").references(() => authUsers.id), + + archived: boolean("archived").notNull().default(false), +}) + +export type CreatedLetter = typeof createdletters.$inferSelect +export type NewCreatedLetter = typeof createdletters.$inferInsert diff --git a/db/schema/customers.ts b/db/schema/customers.ts new file mode 100644 index 0000000..67743e8 --- /dev/null +++ b/db/schema/customers.ts @@ -0,0 +1,69 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + smallint, + uuid, +} from "drizzle-orm/pg-core" +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const customers = pgTable( + "customers", + { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + customerNumber: text("customerNumber").notNull(), + name: text("name").notNull(), + + tenant: bigint("tenant", { mode: "number" }).notNull(), + + infoData: jsonb("infoData").default({}), + active: boolean("active").notNull().default(true), + + notes: text("notes"), + + type: text("type").default("Privat"), + heroId: text("heroId"), + + isCompany: boolean("isCompany").notNull().default(false), + + profiles: jsonb("profiles").notNull().default([]), + + customPaymentDays: smallint("customPaymentDays"), + + firstname: text("firstname"), + lastname: text("lastname"), + + archived: boolean("archived").notNull().default(false), + + customSurchargePercentage: smallint("customSurchargePercentage") + .notNull() + .default(0), + + salutation: text("salutation"), + title: text("title"), + nameAddition: text("nameAddition"), + + availableInPortal: boolean("availableInPortal") + .notNull() + .default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + customPaymentType: text("custom_payment_type"), // ENUM payment_types separat? + } +) + +export type Customer = typeof customers.$inferSelect +export type NewCustomer = typeof customers.$inferInsert diff --git a/db/schema/devices.ts b/db/schema/devices.ts new file mode 100644 index 0000000..4825826 --- /dev/null +++ b/db/schema/devices.ts @@ -0,0 +1,29 @@ +import { + pgTable, + uuid, + timestamp, + text, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" + +export const devices = pgTable("devices", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + type: text("type").notNull(), + + tenant: bigint("tenant", { mode: "number" }).references(() => tenants.id), + + password: text("password"), + + externalId: text("externalId"), +}) + +export type Device = typeof devices.$inferSelect +export type NewDevice = typeof devices.$inferInsert diff --git a/db/schema/documentboxes.ts b/db/schema/documentboxes.ts new file mode 100644 index 0000000..1111a19 --- /dev/null +++ b/db/schema/documentboxes.ts @@ -0,0 +1,28 @@ +import { pgTable, uuid, timestamp, text, boolean, bigint } from "drizzle-orm/pg-core" + +import { spaces } from "./spaces" +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const documentboxes = pgTable("documentboxes", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + + space: bigint("space", { mode: "number" }).references(() => spaces.id), + + key: text("key").notNull(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type DocumentBox = typeof documentboxes.$inferSelect +export type NewDocumentBox = typeof documentboxes.$inferInsert diff --git a/db/schema/enums.ts b/db/schema/enums.ts new file mode 100644 index 0000000..3030302 --- /dev/null +++ b/db/schema/enums.ts @@ -0,0 +1,97 @@ +import { pgEnum } from "drizzle-orm/pg-core" + +// public.textTemplatePositions +export const textTemplatePositionsEnum = pgEnum("texttemplatepositions", [ + "startText", + "endText", +]) + +// public.folderFunctions +export const folderFunctionsEnum = pgEnum("folderfunctions", [ + "none", + "yearSubCategory", + "incomingInvoices", + "invoices", + "quotes", + "confirmationOrders", + "deliveryNotes", + "vehicleData", + "reminders", + "taxData", + "deposit", + "timeEvaluations", +]) + +// public.locked_tenant +export const lockedTenantEnum = pgEnum("locked_tenant", [ + "maintenance_tenant", + "maintenance", + "general", + "no_subscription", +]) + +// public.credential_types +export const credentialTypesEnum = pgEnum("credential_types", [ + "mail", + "m365", +]) + +// public.payment_types +export const paymentTypesEnum = pgEnum("payment_types", [ + "transfer", + "direct_debit", +]) + +// public.notification_status +export const notificationStatusEnum = pgEnum("notification_status", [ + "queued", + "sent", + "failed", + "read", +]) + +// public.notification_channel +export const notificationChannelEnum = pgEnum("notification_channel", [ + "email", + "inapp", + "sms", + "push", + "webhook", +]) + +// public.notification_severity +export const notificationSeverityEnum = pgEnum("notification_severity", [ + "info", + "success", + "warning", + "error", +]) + +// public.times_state +export const timesStateEnum = pgEnum("times_state", [ + "submitted", + "approved", + "draft", +]) + +export const helpdeskStatusEnum = [ + "open", + "in_progress", + "waiting_for_customer", + "answered", + "closed", +] as const + +export const helpdeskPriorityEnum = [ + "low", + "normal", + "high", +] as const + +export const helpdeskDirectionEnum = [ + "incoming", + "outgoing", + "internal", + "system", +] as const + diff --git a/db/schema/events.ts b/db/schema/events.ts new file mode 100644 index 0000000..b703c3e --- /dev/null +++ b/db/schema/events.ts @@ -0,0 +1,60 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { customers } from "./customers" +import { authUsers } from "./auth_users" + +export const events = pgTable( + "events", + { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }).notNull(), + + name: text("name").notNull(), + + startDate: timestamp("startDate", { withTimezone: true }).notNull(), + endDate: timestamp("endDate", { withTimezone: true }), + + eventtype: text("eventtype").default("Umsetzung"), + + project: bigint("project", { mode: "number" }), // FK follows when projects.ts exists + + resources: jsonb("resources").default([]), + notes: text("notes"), + link: text("link"), + + profiles: jsonb("profiles").notNull().default([]), + archived: boolean("archived").notNull().default(false), + + vehicles: jsonb("vehicles").notNull().default([]), + inventoryitems: jsonb("inventoryitems").notNull().default([]), + inventoryitemgroups: jsonb("inventoryitemgroups").notNull().default([]), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id + ), + + vendor: bigint("vendor", { mode: "number" }), // will link once vendors.ts is created + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + } +) + +export type Event = typeof events.$inferSelect +export type NewEvent = typeof events.$inferInsert diff --git a/db/schema/files.ts b/db/schema/files.ts new file mode 100644 index 0000000..f118834 --- /dev/null +++ b/db/schema/files.ts @@ -0,0 +1,79 @@ +import { + pgTable, + uuid, + timestamp, + text, + boolean, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { projects } from "./projects" +import { customers } from "./customers" +import { contracts } from "./contracts" +import { vendors } from "./vendors" +import { incominginvoices } from "./incominginvoices" +import { plants } from "./plants" +import { createddocuments } from "./createddocuments" +import { vehicles } from "./vehicles" +import { products } from "./products" +import { inventoryItems } from "./inventoryitems" +import { folders } from "./folders" +import { filetags } from "./filetags" +import { authUsers } from "./auth_users" +import { authProfiles } from "./auth_profiles" +import { spaces } from "./spaces" +import { documentboxes } from "./documentboxes" +import { checks } from "./checks" + +export const files = pgTable("files", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + path: text("path"), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + project: bigint("project", { mode: "number" }).references(() => projects.id), + customer: bigint("customer", { mode: "number" }).references(() => customers.id), + contract: bigint("contract", { mode: "number" }).references(() => contracts.id), + vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id), + incominginvoice: bigint("incominginvoice", { mode: "number" }).references(() => incominginvoices.id), + plant: bigint("plant", { mode: "number" }).references(() => plants.id), + createddocument: bigint("createddocument", { mode: "number" }).references(() => createddocuments.id), + vehicle: bigint("vehicle", { mode: "number" }).references(() => vehicles.id), + product: bigint("product", { mode: "number" }).references(() => products.id), + + check: uuid("check").references(() => checks.id), + + inventoryitem: bigint("inventoryitem", { mode: "number" }).references(() => inventoryItems.id), + + folder: uuid("folder").references(() => folders.id), + + mimeType: text("mimeType"), + + archived: boolean("archived").notNull().default(false), + + space: bigint("space", { mode: "number" }).references(() => spaces.id), + + type: uuid("type").references(() => filetags.id), + + documentbox: uuid("documentbox").references(() => documentboxes.id), + + name: text("name"), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + createdBy: uuid("created_by").references(() => authUsers.id), + + authProfile: uuid("auth_profile").references(() => authProfiles.id), +}) + +export type File = typeof files.$inferSelect +export type NewFile = typeof files.$inferInsert diff --git a/db/schema/filetags.ts b/db/schema/filetags.ts new file mode 100644 index 0000000..3dc47cf --- /dev/null +++ b/db/schema/filetags.ts @@ -0,0 +1,33 @@ +import { + pgTable, + uuid, + timestamp, + text, + boolean, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" + +export const filetags = pgTable("filetags", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + color: text("color"), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + createdDocumentType: text("createddocumenttype").default(""), + incomingDocumentType: text("incomingDocumentType"), + + archived: boolean("archived").notNull().default(false), +}) + +export type FileTag = typeof filetags.$inferSelect +export type NewFileTag = typeof filetags.$inferInsert diff --git a/db/schema/folders.ts b/db/schema/folders.ts new file mode 100644 index 0000000..74a9a11 --- /dev/null +++ b/db/schema/folders.ts @@ -0,0 +1,51 @@ +import { + pgTable, + uuid, + timestamp, + text, + boolean, + integer, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import { filetags } from "./filetags" +import { folderFunctionsEnum } from "./enums" + +export const folders = pgTable("folders", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + name: text("name").notNull(), + icon: text("icon"), + + parent: uuid("parent").references(() => folders.id), + + isSystemUsed: boolean("isSystemUsed").notNull().default(false), + + function: folderFunctionsEnum("function"), + + year: integer("year"), + + standardFiletype: uuid("standardFiletype").references(() => filetags.id), + + standardFiletypeIsOptional: boolean("standardFiletypeIsOptional") + .notNull() + .default(true), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + archived: boolean("archived").notNull().default(false), +}) + +export type Folder = typeof folders.$inferSelect +export type NewFolder = typeof folders.$inferInsert diff --git a/db/schema/generatedexports.ts b/db/schema/generatedexports.ts new file mode 100644 index 0000000..b2a5c84 --- /dev/null +++ b/db/schema/generatedexports.ts @@ -0,0 +1,35 @@ +import { + pgTable, + bigint, + timestamp, + text, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" + +export const generatedexports = pgTable("exports", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + startDate: timestamp("start_date", { withTimezone: true }).notNull(), + endDate: timestamp("end_date", { withTimezone: true }).notNull(), + + validUntil: timestamp("valid_until", { withTimezone: true }), + + type: text("type").notNull().default("datev"), + + url: text("url").notNull(), + filePath: text("file_path"), +}) + +export type Export = typeof generatedexports.$inferSelect +export type NewExport = typeof generatedexports.$inferInsert diff --git a/db/schema/globalmessages.ts b/db/schema/globalmessages.ts new file mode 100644 index 0000000..3a2ffb9 --- /dev/null +++ b/db/schema/globalmessages.ts @@ -0,0 +1,22 @@ +import { + pgTable, + bigint, + timestamp, + text, +} from "drizzle-orm/pg-core" + +export const globalmessages = pgTable("globalmessages", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + title: text("title"), + description: text("description"), +}) + +export type GlobalMessage = typeof globalmessages.$inferSelect +export type NewGlobalMessage = typeof globalmessages.$inferInsert diff --git a/db/schema/globalmessagesseen.ts b/db/schema/globalmessagesseen.ts new file mode 100644 index 0000000..7ee9c8e --- /dev/null +++ b/db/schema/globalmessagesseen.ts @@ -0,0 +1,17 @@ +import { + pgTable, + timestamp, + bigint, +} from "drizzle-orm/pg-core" + +import { globalmessages } from "./globalmessages" + +export const globalmessagesseen = pgTable("globalmessagesseen", { + message: bigint("message", { mode: "number" }) + .notNull() + .references(() => globalmessages.id), + + seenAt: timestamp("seen_at", { withTimezone: true }) + .notNull() + .defaultNow(), +}) diff --git a/db/schema/helpdesk_channel_instances.ts b/db/schema/helpdesk_channel_instances.ts new file mode 100644 index 0000000..29edfea --- /dev/null +++ b/db/schema/helpdesk_channel_instances.ts @@ -0,0 +1,44 @@ +import { + pgTable, + uuid, + timestamp, + text, + boolean, + jsonb, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import { helpdesk_channel_types } from "./helpdesk_channel_types" + +export const helpdesk_channel_instances = pgTable("helpdesk_channel_instances", { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id, { onDelete: "cascade" }), + + typeId: text("type_id") + .notNull() + .references(() => helpdesk_channel_types.id), + + name: text("name").notNull(), + + isActive: boolean("is_active").notNull().default(true), + + config: jsonb("config").notNull(), + publicConfig: jsonb("public_config").notNull().default({}), + + publicToken: text("public_token").unique(), + secretToken: text("secret_token"), + + createdBy: uuid("created_by").references(() => authUsers.id), + + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(), +}) + +export type HelpdeskChannelInstance = + typeof helpdesk_channel_instances.$inferSelect +export type NewHelpdeskChannelInstance = + typeof helpdesk_channel_instances.$inferInsert diff --git a/db/schema/helpdesk_channel_types.ts b/db/schema/helpdesk_channel_types.ts new file mode 100644 index 0000000..8b1a8f3 --- /dev/null +++ b/db/schema/helpdesk_channel_types.ts @@ -0,0 +1,9 @@ +import { pgTable, text } from "drizzle-orm/pg-core" + +export const helpdesk_channel_types = pgTable("helpdesk_channel_types", { + id: text("id").primaryKey(), + description: text("description").notNull(), +}) + +export type HelpdeskChannelType = typeof helpdesk_channel_types.$inferSelect +export type NewHelpdeskChannelType = typeof helpdesk_channel_types.$inferInsert diff --git a/db/schema/helpdesk_contacts.ts b/db/schema/helpdesk_contacts.ts new file mode 100644 index 0000000..baeab18 --- /dev/null +++ b/db/schema/helpdesk_contacts.ts @@ -0,0 +1,45 @@ +import { + pgTable, + uuid, + timestamp, + text, + jsonb, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { customers } from "./customers" +import { contacts } from "./contacts" +import { helpdesk_channel_instances } from "./helpdesk_channel_instances" // placeholder + +export const helpdesk_contacts = pgTable("helpdesk_contacts", { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id, { onDelete: "cascade" }), + + customerId: bigint("customer_id", { mode: "number" }) + .references(() => customers.id, { onDelete: "set null" }), + + email: text("email"), + phone: text("phone"), + + externalRef: jsonb("external_ref"), + displayName: text("display_name"), + + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(), + + sourceChannelId: uuid("source_channel_id").references( + () => helpdesk_channel_instances.id, + { onDelete: "set null" } + ), + + contactId: bigint("contact_id", { mode: "number" }).references( + () => contacts.id, + { onDelete: "set null" } + ), +}) + +export type HelpdeskContact = typeof helpdesk_contacts.$inferSelect +export type NewHelpdeskContact = typeof helpdesk_contacts.$inferInsert diff --git a/db/schema/helpdesk_conversation_participants.ts b/db/schema/helpdesk_conversation_participants.ts new file mode 100644 index 0000000..c4d7c6b --- /dev/null +++ b/db/schema/helpdesk_conversation_participants.ts @@ -0,0 +1,34 @@ +import { + pgTable, + uuid, + text, +} from "drizzle-orm/pg-core" + +import { helpdesk_conversations } from "./helpdesk_conversations" +import { authUsers } from "./auth_users" + +export const helpdesk_conversation_participants = pgTable( + "helpdesk_conversation_participants", + { + conversationId: uuid("conversation_id") + .notNull() + .references(() => helpdesk_conversations.id, { onDelete: "cascade" }), + + userId: uuid("user_id") + .notNull() + .references(() => authUsers.id, { onDelete: "cascade" }), + + role: text("role"), + }, + (table) => ({ + pk: { + name: "helpdesk_conversation_participants_pkey", + columns: [table.conversationId, table.userId], + }, + }) +) + +export type HelpdeskConversationParticipant = + typeof helpdesk_conversation_participants.$inferSelect +export type NewHelpdeskConversationParticipant = + typeof helpdesk_conversation_participants.$inferInsert diff --git a/db/schema/helpdesk_conversations.ts b/db/schema/helpdesk_conversations.ts new file mode 100644 index 0000000..f2c14dd --- /dev/null +++ b/db/schema/helpdesk_conversations.ts @@ -0,0 +1,59 @@ +import { + pgTable, + uuid, + timestamp, + text, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { helpdesk_contacts } from "./helpdesk_contacts" +import { contacts } from "./contacts" +import { customers } from "./customers" +import { authUsers } from "./auth_users" +import { helpdesk_channel_instances } from "./helpdesk_channel_instances" + +export const helpdesk_conversations = pgTable("helpdesk_conversations", { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id, { onDelete: "cascade" }), + + channelInstanceId: uuid("channel_instance_id") + .notNull() + .references(() => helpdesk_channel_instances.id, { onDelete: "cascade" }), + + contactId: uuid("contact_id").references(() => helpdesk_contacts.id, { + onDelete: "set null", + }), + + subject: text("subject"), + + status: text("status").notNull().default("open"), + + priority: text("priority").default("normal"), + + assigneeUserId: uuid("assignee_user_id").references(() => authUsers.id), + + lastMessageAt: timestamp("last_message_at", { withTimezone: true }), + + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(), + + customerId: bigint("customer_id", { mode: "number" }).references( + () => customers.id, + { onDelete: "set null" } + ), + + contactPersonId: bigint("contact_person_id", { mode: "number" }).references( + () => contacts.id, + { onDelete: "set null" } + ), + + ticketNumber: text("ticket_number"), +}) + +export type HelpdeskConversation = + typeof helpdesk_conversations.$inferSelect +export type NewHelpdeskConversation = + typeof helpdesk_conversations.$inferInsert diff --git a/db/schema/helpdesk_messages.ts b/db/schema/helpdesk_messages.ts new file mode 100644 index 0000000..08eec71 --- /dev/null +++ b/db/schema/helpdesk_messages.ts @@ -0,0 +1,46 @@ +import { + pgTable, + uuid, + timestamp, + text, + jsonb, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { helpdesk_contacts } from "./helpdesk_contacts" +import { helpdesk_conversations } from "./helpdesk_conversations" +import { authUsers } from "./auth_users" + +export const helpdesk_messages = pgTable("helpdesk_messages", { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id, { onDelete: "cascade" }), + + conversationId: uuid("conversation_id") + .notNull() + .references(() => helpdesk_conversations.id, { onDelete: "cascade" }), + + direction: text("direction").notNull(), + + authorUserId: uuid("author_user_id").references(() => authUsers.id), + + payload: jsonb("payload").notNull(), + + rawMeta: jsonb("raw_meta"), + + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(), + + contactId: uuid("contact_id").references(() => helpdesk_contacts.id, { + onDelete: "set null", + }), + + externalMessageId: text("external_message_id").unique(), + + receivedAt: timestamp("received_at", { withTimezone: true }).defaultNow(), +}) + +export type HelpdeskMessage = typeof helpdesk_messages.$inferSelect +export type NewHelpdeskMessage = typeof helpdesk_messages.$inferInsert diff --git a/db/schema/helpdesk_routing_rules.ts b/db/schema/helpdesk_routing_rules.ts new file mode 100644 index 0000000..4c3065b --- /dev/null +++ b/db/schema/helpdesk_routing_rules.ts @@ -0,0 +1,33 @@ +import { + pgTable, + uuid, + timestamp, + text, + jsonb, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const helpdesk_routing_rules = pgTable("helpdesk_routing_rules", { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id, { onDelete: "cascade" }), + + name: text("name").notNull(), + + condition: jsonb("condition").notNull(), + action: jsonb("action").notNull(), + + createdBy: uuid("created_by").references(() => authUsers.id), + + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(), +}) + +export type HelpdeskRoutingRule = + typeof helpdesk_routing_rules.$inferSelect +export type NewHelpdeskRoutingRule = + typeof helpdesk_routing_rules.$inferInsert diff --git a/db/schema/historyitems.ts b/db/schema/historyitems.ts new file mode 100644 index 0000000..40da4c9 --- /dev/null +++ b/db/schema/historyitems.ts @@ -0,0 +1,140 @@ +import { + pgTable, + bigint, + uuid, + timestamp, + text, + jsonb, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { customers } from "./customers" +import { vendors } from "./vendors" +import { projects } from "./projects" +import { plants } from "./plants" +import { incominginvoices } from "./incominginvoices" +import { contacts } from "./contacts" +import { inventoryItems } from "./inventoryitems" +import { products } from "./products" +import { tasks } from "./tasks" +import { vehicles } from "./vehicles" +import { bankstatements } from "./bankstatements" +import { spaces } from "./spaces" +import { costcentres } from "./costcentres" +import { ownaccounts } from "./ownaccounts" +import { createddocuments } from "./createddocuments" +import { documentboxes } from "./documentboxes" +import { hourrates } from "./hourrates" +import { projecttypes } from "./projecttypes" +import { checks } from "./checks" +import { services } from "./services" +import { events } from "./events" +import { inventoryitemgroups } from "./inventoryitemgroups" +import { authUsers } from "./auth_users" +import {files} from "./files"; + +export const historyitems = pgTable("historyitems", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + text: text("text").notNull(), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id, + { onDelete: "cascade" } + ), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id), + + project: bigint("project", { mode: "number" }).references( + () => projects.id, + { onDelete: "cascade" } + ), + + plant: bigint("plant", { mode: "number" }).references( + () => plants.id, + { onDelete: "cascade" } + ), + + incomingInvoice: bigint("incomingInvoice", { mode: "number" }).references( + () => incominginvoices.id, + { onDelete: "cascade" } + ), + + contact: bigint("contact", { mode: "number" }).references(() => contacts.id, { + onDelete: "cascade", + }), + + inventoryitem: bigint("inventoryitem", { mode: "number" }).references( + () => inventoryItems.id, + { onDelete: "cascade" } + ), + + product: bigint("product", { mode: "number" }).references( + () => products.id, + { onDelete: "cascade" } + ), + + event: bigint("event", { mode: "number" }).references(() => events.id), + + newVal: text("newVal"), + oldVal: text("oldVal"), + + task: bigint("task", { mode: "number" }).references(() => tasks.id), + + vehicle: bigint("vehicle", { mode: "number" }).references(() => vehicles.id), + + bankstatement: bigint("bankstatement", { mode: "number" }).references( + () => bankstatements.id + ), + + space: bigint("space", { mode: "number" }).references(() => spaces.id), + + config: jsonb("config"), + + projecttype: bigint("projecttype", { mode: "number" }).references( + () => projecttypes.id + ), + + check: uuid("check").references(() => checks.id), + + service: bigint("service", { mode: "number" }).references( + () => services.id + ), + + createddocument: bigint("createddocument", { mode: "number" }).references( + () => createddocuments.id + ), + + file: uuid("file").references(() => files.id), + + inventoryitemgroup: uuid("inventoryitemgroup").references( + () => inventoryitemgroups.id + ), + + source: text("source").default("Software"), + + costcentre: uuid("costcentre").references(() => costcentres.id), + + ownaccount: uuid("ownaccount").references(() => ownaccounts.id), + + documentbox: uuid("documentbox").references(() => documentboxes.id), + + hourrate: uuid("hourrate").references(() => hourrates.id), + + createdBy: uuid("created_by").references(() => authUsers.id), + + action: text("action"), +}) + +export type HistoryItem = typeof historyitems.$inferSelect +export type NewHistoryItem = typeof historyitems.$inferInsert diff --git a/db/schema/holidays.ts b/db/schema/holidays.ts new file mode 100644 index 0000000..76d3e9e --- /dev/null +++ b/db/schema/holidays.ts @@ -0,0 +1,18 @@ +import { pgTable, bigint, date, text, timestamp } from "drizzle-orm/pg-core" + +export const holidays = pgTable("holidays", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedAlwaysAsIdentity(), + + date: date("date").notNull(), + + name: text("name").notNull(), + + stateCode: text("state_code").notNull(), + + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(), +}) + +export type Holiday = typeof holidays.$inferSelect +export type NewHoliday = typeof holidays.$inferInsert diff --git a/db/schema/hourrates.ts b/db/schema/hourrates.ts new file mode 100644 index 0000000..299c2d5 --- /dev/null +++ b/db/schema/hourrates.ts @@ -0,0 +1,27 @@ +import { pgTable, uuid, timestamp, text, boolean, bigint, doublePrecision } from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const hourrates = pgTable("hourrates", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + name: text("name").notNull(), + + purchasePrice: doublePrecision("purchasePrice").notNull(), + sellingPrice: doublePrecision("sellingPrice").notNull(), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type HourRate = typeof hourrates.$inferSelect +export type NewHourRate = typeof hourrates.$inferInsert diff --git a/db/schema/incominginvoices.ts b/db/schema/incominginvoices.ts new file mode 100644 index 0000000..07ac711 --- /dev/null +++ b/db/schema/incominginvoices.ts @@ -0,0 +1,63 @@ +import { + pgTable, + bigint, + timestamp, + text, + boolean, + jsonb, + uuid, +} from "drizzle-orm/pg-core" + +import { vendors } from "./vendors" +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const incominginvoices = pgTable("incominginvoices", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + state: text("state").notNull().default("Entwurf"), + + vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id), + + reference: text("reference"), + date: text("date"), + + document: bigint("document", { mode: "number" }), + + dueDate: text("dueDate"), + + description: text("description"), + + paymentType: text("paymentType"), + + accounts: jsonb("accounts").notNull().default([ + { + account: null, + taxType: null, + amountNet: null, + amountTax: 19, + costCentre: null, + }, + ]), + + paid: boolean("paid").notNull().default(false), + expense: boolean("expense").notNull().default(true), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + archived: boolean("archived").notNull().default(false), +}) + +export type IncomingInvoice = typeof incominginvoices.$inferSelect +export type NewIncomingInvoice = typeof incominginvoices.$inferInsert diff --git a/db/schema/index.ts b/db/schema/index.ts new file mode 100644 index 0000000..4208507 --- /dev/null +++ b/db/schema/index.ts @@ -0,0 +1,70 @@ +export * from "./accounts" +export * from "./auth_profiles" +export * from "./auth_role_permisssions" +export * from "./auth_roles" +export * from "./auth_tenant_users" +export * from "./auth_user_roles" +export * from "./auth_users" +export * from "./bankaccounts" +export * from "./bankrequisitions" +export * from "./bankstatements" +export * from "./checkexecutions" +export * from "./checks" +export * from "./citys" +export * from "./contacts" +export * from "./contracts" +export * from "./costcentres" +export * from "./countrys" +export * from "./createddocuments" +export * from "./createdletters" +export * from "./customers" +export * from "./devices" +export * from "./documentboxes" +export * from "./enums" +export * from "./events" +export * from "./files" +export * from "./filetags" +export * from "./folders" +export * from "./generatedexports" +export * from "./globalmessages" +export * from "./globalmessagesseen" +export * from "./helpdesk_channel_instances" +export * from "./helpdesk_channel_types" +export * from "./helpdesk_contacts" +export * from "./helpdesk_conversation_participants" +export * from "./helpdesk_conversations" +export * from "./helpdesk_messages" +export * from "./helpdesk_routing_rules" +export * from "./historyitems" +export * from "./holidays" +export * from "./hourrates" +export * from "./incominginvoices" +export * from "./inventoryitemgroups" +export * from "./inventoryitems" +export * from "./letterheads" +export * from "./movements" +export * from "./notifications_event_types" +export * from "./notifications_items" +export * from "./notifications_preferences" +export * from "./notifications_preferences_defaults" +export * from "./ownaccounts" +export * from "./plants" +export * from "./productcategories" +export * from "./products" +export * from "./projects" +export * from "./projecttypes" +export * from "./servicecategories" +export * from "./services" +export * from "./spaces" +export * from "./staff_time_entries" +export * from "./staff_time_entry_connects" +export * from "./staff_zeitstromtimestamps" +export * from "./statementallocations" +export * from "./tasks" +export * from "./taxtypes" +export * from "./tenants" +export * from "./texttemplates" +export * from "./units" +export * from "./user_credentials" +export * from "./vehicles" +export * from "./vendors" \ No newline at end of file diff --git a/db/schema/inventoryitemgroups.ts b/db/schema/inventoryitemgroups.ts new file mode 100644 index 0000000..3fe9dc1 --- /dev/null +++ b/db/schema/inventoryitemgroups.ts @@ -0,0 +1,39 @@ +import { + pgTable, + uuid, + timestamp, + text, + boolean, + jsonb, bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const inventoryitemgroups = pgTable("inventoryitemgroups", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }).notNull().references(() => tenants.id), + + name: text("name").notNull(), + + inventoryitems: jsonb("inventoryitems").notNull().default([]), + + description: text("description"), + + archived: boolean("archived").notNull().default(false), + + profiles: jsonb("profiles").notNull().default([]), + + usePlanning: boolean("usePlanning").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type InventoryItemGroup = typeof inventoryitemgroups.$inferSelect +export type NewInventoryItemGroup = typeof inventoryitemgroups.$inferInsert diff --git a/db/schema/inventoryitems.ts b/db/schema/inventoryitems.ts new file mode 100644 index 0000000..9a56d5d --- /dev/null +++ b/db/schema/inventoryitems.ts @@ -0,0 +1,68 @@ +import { + pgTable, + bigint, + timestamp, + text, + boolean, + doublePrecision, + uuid, + jsonb, + date, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { vendors } from "./vendors" +import { spaces } from "./spaces" +import { authUsers } from "./auth_users" + +export const inventoryItems = pgTable("inventoryitems", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + + usePlanning: boolean("usePlanning").notNull().default(false), + + description: text("description"), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + currentSpace: bigint("currentSpace", { mode: "number" }).references( + () => spaces.id + ), + + articleNumber: text("articleNumber"), + serialNumber: text("serialNumber"), + + purchaseDate: date("purchaseDate"), + + vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id), + + quantity: bigint("quantity", { mode: "number" }).notNull().default(0), + + purchasePrice: doublePrecision("purchasePrice").default(0), + + manufacturer: text("manufacturer"), + manufacturerNumber: text("manufacturerNumber"), + + currentValue: doublePrecision("currentValue"), + + archived: boolean("archived").notNull().default(false), + + profiles: jsonb("profiles").notNull().default([]), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => + authUsers.id + ), +}) + +export type InventoryItem = typeof inventoryItems.$inferSelect +export type NewInventoryItem = typeof inventoryItems.$inferInsert diff --git a/db/schema/letterheads.ts b/db/schema/letterheads.ts new file mode 100644 index 0000000..2cbc534 --- /dev/null +++ b/db/schema/letterheads.ts @@ -0,0 +1,39 @@ +import { + pgTable, + bigint, + timestamp, + text, + boolean, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const letterheads = pgTable("letterheads", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + name: text("name").default("Standard"), + + path: text("path").notNull(), + + documentTypes: text("documentTypes").array().notNull().default([]), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + archived: boolean("archived").notNull().default(false), +}) + +export type Letterhead = typeof letterheads.$inferSelect +export type NewLetterhead = typeof letterheads.$inferInsert diff --git a/db/schema/movements.ts b/db/schema/movements.ts new file mode 100644 index 0000000..72a2759 --- /dev/null +++ b/db/schema/movements.ts @@ -0,0 +1,49 @@ +import { + pgTable, + bigint, + timestamp, + text, + uuid, +} from "drizzle-orm/pg-core" + +import { products } from "./products" +import { spaces } from "./spaces" +import { tenants } from "./tenants" +import { projects } from "./projects" +import { authUsers } from "./auth_users" + +export const movements = pgTable("movements", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + quantity: bigint("quantity", { mode: "number" }).notNull(), + + productId: bigint("productId", { mode: "number" }) + .notNull() + .references(() => products.id), + + spaceId: bigint("spaceId", { mode: "number" }).references(() => spaces.id), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + projectId: bigint("projectId", { mode: "number" }).references( + () => projects.id + ), + + notes: text("notes"), + + serials: text("serials").array(), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Movement = typeof movements.$inferSelect +export type NewMovement = typeof movements.$inferInsert diff --git a/db/schema/notifications_event_types.ts b/db/schema/notifications_event_types.ts new file mode 100644 index 0000000..c16d648 --- /dev/null +++ b/db/schema/notifications_event_types.ts @@ -0,0 +1,34 @@ +import { + pgTable, + text, + jsonb, + boolean, + timestamp, +} from "drizzle-orm/pg-core" +import {notificationSeverityEnum} from "./enums"; + + +export const notificationsEventTypes = pgTable("notifications_event_types", { + eventKey: text("event_key").primaryKey(), + + displayName: text("display_name").notNull(), + description: text("description"), + category: text("category"), + + severity: notificationSeverityEnum("severity").notNull().default("info"), + + allowedChannels: jsonb("allowed_channels").notNull().default(["inapp", "email"]), + + payloadSchema: jsonb("payload_schema"), + + isActive: boolean("is_active").notNull().default(true), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), +}) + +export type NotificationsEventType = + typeof notificationsEventTypes.$inferSelect +export type NewNotificationsEventType = + typeof notificationsEventTypes.$inferInsert diff --git a/db/schema/notifications_items.ts b/db/schema/notifications_items.ts new file mode 100644 index 0000000..d6c6cb3 --- /dev/null +++ b/db/schema/notifications_items.ts @@ -0,0 +1,54 @@ +import { + pgTable, + uuid, + bigint, + text, + jsonb, + timestamp, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import { notificationsEventTypes } from "./notifications_event_types" +import {notificationChannelEnum, notificationStatusEnum} from "./enums"; + + +export const notificationsItems = pgTable("notifications_items", { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id, { onDelete: "cascade", onUpdate: "cascade" }), + + userId: uuid("user_id") + .notNull() + .references(() => authUsers.id, { onDelete: "cascade", onUpdate: "cascade" }), + + eventType: text("event_type") + .notNull() + .references(() => notificationsEventTypes.eventKey, { + onUpdate: "cascade", + onDelete: "restrict", + }), + + title: text("title").notNull(), + message: text("message").notNull(), + + payload: jsonb("payload"), + + channel: notificationChannelEnum("channel").notNull(), + + status: notificationStatusEnum("status").notNull().default("queued"), + + error: text("error"), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + sentAt: timestamp("sent_at", { withTimezone: true }), + readAt: timestamp("read_at", { withTimezone: true }), +}) + +export type NotificationItem = typeof notificationsItems.$inferSelect +export type NewNotificationItem = typeof notificationsItems.$inferInsert diff --git a/db/schema/notifications_preferences.ts b/db/schema/notifications_preferences.ts new file mode 100644 index 0000000..8397a0a --- /dev/null +++ b/db/schema/notifications_preferences.ts @@ -0,0 +1,60 @@ +import { + pgTable, + uuid, + bigint, + text, + boolean, + timestamp, + uniqueIndex, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import { notificationsEventTypes } from "./notifications_event_types" +import {notificationChannelEnum} from "./enums"; + +export const notificationsPreferences = pgTable( + "notifications_preferences", + { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + + userId: uuid("user_id") + .notNull() + .references(() => authUsers.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + + eventType: text("event_type") + .notNull() + .references(() => notificationsEventTypes.eventKey, { + onDelete: "restrict", + onUpdate: "cascade", + }), + + channel: notificationChannelEnum("channel").notNull(), + + enabled: boolean("enabled").notNull().default(true), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + uniquePrefs: uniqueIndex( + "notifications_preferences_tenant_id_user_id_event_type_chan_key", + ).on(table.tenantId, table.userId, table.eventType, table.channel), + }), +) + +export type NotificationPreference = + typeof notificationsPreferences.$inferSelect +export type NewNotificationPreference = + typeof notificationsPreferences.$inferInsert diff --git a/db/schema/notifications_preferences_defaults.ts b/db/schema/notifications_preferences_defaults.ts new file mode 100644 index 0000000..3c263c9 --- /dev/null +++ b/db/schema/notifications_preferences_defaults.ts @@ -0,0 +1,52 @@ +import { + pgTable, + uuid, + bigint, + text, + boolean, + timestamp, + uniqueIndex, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { notificationsEventTypes } from "./notifications_event_types" +import {notificationChannelEnum} from "./enums"; + +export const notificationsPreferencesDefaults = pgTable( + "notifications_preferences_defaults", + { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id, { + onDelete: "cascade", + onUpdate: "cascade", + }), + + eventKey: text("event_key") + .notNull() + .references(() => notificationsEventTypes.eventKey, { + onDelete: "restrict", + onUpdate: "cascade", + }), + + channel: notificationChannelEnum("channel").notNull(), + + enabled: boolean("enabled").notNull().default(true), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + }, + (table) => ({ + uniqueDefaults: uniqueIndex( + "notifications_preferences_defau_tenant_id_event_key_channel_key", + ).on(table.tenantId, table.eventKey, table.channel), + }), +) + +export type NotificationPreferenceDefault = + typeof notificationsPreferencesDefaults.$inferSelect +export type NewNotificationPreferenceDefault = + typeof notificationsPreferencesDefaults.$inferInsert diff --git a/db/schema/ownaccounts.ts b/db/schema/ownaccounts.ts new file mode 100644 index 0000000..1970adc --- /dev/null +++ b/db/schema/ownaccounts.ts @@ -0,0 +1,39 @@ +import { + pgTable, + uuid, + timestamp, + text, + boolean, + jsonb, + bigint, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const ownaccounts = pgTable("ownaccounts", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + number: text("number").notNull(), + name: text("name").notNull(), + + description: text("description"), + + archived: boolean("archived").notNull().default(false), + + profiles: jsonb("profiles").notNull().default([]), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type OwnAccount = typeof ownaccounts.$inferSelect +export type NewOwnAccount = typeof ownaccounts.$inferInsert diff --git a/db/schema/plants.ts b/db/schema/plants.ts new file mode 100644 index 0000000..a7b35f4 --- /dev/null +++ b/db/schema/plants.ts @@ -0,0 +1,56 @@ +import { + pgTable, + bigint, + timestamp, + text, + jsonb, + boolean, + uuid, + date, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { customers } from "./customers" +import { contracts } from "./contracts" +import { authUsers } from "./auth_users" + +export const plants = pgTable("plants", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + name: text("name").notNull(), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id + ), + + infoData: jsonb("infoData"), + contract: bigint("contract", { mode: "number" }).references( + () => contracts.id + ), + + description: jsonb("description").default({ + html: "", + json: [], + text: "", + }), + + archived: boolean("archived").notNull().default(false), + + profiles: jsonb("profiles").notNull().default([]), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Plant = typeof plants.$inferSelect +export type NewPlant = typeof plants.$inferInsert diff --git a/db/schema/productcategories.ts b/db/schema/productcategories.ts new file mode 100644 index 0000000..ec8753c --- /dev/null +++ b/db/schema/productcategories.ts @@ -0,0 +1,37 @@ +import { + pgTable, + bigint, + timestamp, + text, + boolean, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const productCategories = pgTable("productcategories", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + name: text("name").notNull(), + + description: text("description"), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type ProductCategory = typeof productCategories.$inferSelect +export type NewProductCategory = typeof productCategories.$inferInsert diff --git a/db/schema/products.ts b/db/schema/products.ts new file mode 100644 index 0000000..3379d21 --- /dev/null +++ b/db/schema/products.ts @@ -0,0 +1,69 @@ +import { + pgTable, + bigint, + timestamp, + text, + doublePrecision, + boolean, + smallint, + uuid, + jsonb, + json, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { units } from "./units" +import { authUsers } from "./auth_users" + +export const products = pgTable("products", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + manufacturer: text("manufacturer"), + + unit: bigint("unit", { mode: "number" }) + .notNull() + .references(() => units.id), + + tags: json("tags").notNull().default([]), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + ean: text("ean"), + barcode: text("barcode"), + + purchasePrice: doublePrecision("purchasePrice"), + sellingPrice: doublePrecision("sellingPrice"), + + description: text("description"), + + manufacturerNumber: text("manufacturerNumber"), + + vendorAllocation: jsonb("vendorAllocation").default([]), + + articleNumber: text("articleNumber"), + + barcodes: text("barcodes").array().notNull().default([]), + + productcategories: jsonb("productcategories").default([]), + + archived: boolean("archived").notNull().default(false), + + taxPercentage: smallint("taxPercentage").notNull().default(19), + + markupPercentage: doublePrecision("markupPercentage"), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Product = typeof products.$inferSelect +export type NewProduct = typeof products.$inferInsert diff --git a/db/schema/projects.ts b/db/schema/projects.ts new file mode 100644 index 0000000..fc12993 --- /dev/null +++ b/db/schema/projects.ts @@ -0,0 +1,78 @@ +import { + pgTable, + bigint, + timestamp, + text, + jsonb, + json, + boolean, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { customers } from "./customers" +import { contracts } from "./contracts" +import { projecttypes } from "./projecttypes" +import { authUsers } from "./auth_users" + +export const projects = pgTable("projects", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + name: text("name").notNull(), + + notes: text("notes"), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id + ), + + phases: jsonb("phases").default([]), + + description: json("description"), + + forms: jsonb("forms").default([]), + + heroId: text("heroId"), + + measure: text("measure"), + + material: jsonb("material"), + + plant: bigint("plant", { mode: "number" }), + + profiles: uuid("profiles").array().notNull().default([]), + + projectNumber: text("projectNumber"), + + contract: bigint("contract", { mode: "number" }).references( + () => contracts.id + ), + + projectType: text("projectType").default("Projekt"), + + projecttype: bigint("projecttype", { mode: "number" }).references( + () => projecttypes.id + ), + + archived: boolean("archived").notNull().default(false), + + customerRef: text("customerRef"), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + activePhase: text("active_phase"), +}) + +export type Project = typeof projects.$inferSelect +export type NewProject = typeof projects.$inferInsert diff --git a/db/schema/projecttypes.ts b/db/schema/projecttypes.ts new file mode 100644 index 0000000..fc7b90d --- /dev/null +++ b/db/schema/projecttypes.ts @@ -0,0 +1,41 @@ +import { + pgTable, + bigint, + timestamp, + text, + jsonb, + boolean, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const projecttypes = pgTable("projecttypes", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + + initialPhases: jsonb("initialPhases"), + addablePhases: jsonb("addablePhases"), + + icon: text("icon"), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type ProjectType = typeof projecttypes.$inferSelect +export type NewProjectType = typeof projecttypes.$inferInsert diff --git a/db/schema/servicecategories.ts b/db/schema/servicecategories.ts new file mode 100644 index 0000000..25ba702 --- /dev/null +++ b/db/schema/servicecategories.ts @@ -0,0 +1,39 @@ +import { + pgTable, + bigint, + timestamp, + text, + doublePrecision, + boolean, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const serviceCategories = pgTable("servicecategories", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + name: text("name").notNull(), + description: text("description"), + + discount: doublePrecision("discount").default(0), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type ServiceCategory = typeof serviceCategories.$inferSelect +export type NewServiceCategory = typeof serviceCategories.$inferInsert diff --git a/db/schema/services.ts b/db/schema/services.ts new file mode 100644 index 0000000..0dcdd4b --- /dev/null +++ b/db/schema/services.ts @@ -0,0 +1,63 @@ +import { + pgTable, + bigint, + timestamp, + text, + doublePrecision, + jsonb, + boolean, + smallint, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { units } from "./units" +import { authUsers } from "./auth_users" + +export const services = pgTable("services", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + + sellingPrice: doublePrecision("sellingPrice"), + + description: text("description"), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + unit: bigint("unit", { mode: "number" }).references(() => units.id), + + serviceNumber: bigint("serviceNumber", { mode: "number" }), + + tags: jsonb("tags").default([]), + servicecategories: jsonb("servicecategories").notNull().default([]), + + archived: boolean("archived").notNull().default(false), + + purchasePriceComposed: jsonb("purchasePriceComposed") + .notNull() + .default({ total: 0 }), + + sellingPriceComposed: jsonb("sellingPriceComposed") + .notNull() + .default({ total: 0 }), + + taxPercentage: smallint("taxPercentage").notNull().default(19), + + materialComposition: jsonb("materialComposition").notNull().default([]), + personalComposition: jsonb("personalComposition").notNull().default([]), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Service = typeof services.$inferSelect +export type NewService = typeof services.$inferInsert diff --git a/db/schema/spaces.ts b/db/schema/spaces.ts new file mode 100644 index 0000000..b0d8116 --- /dev/null +++ b/db/schema/spaces.ts @@ -0,0 +1,49 @@ +import { + pgTable, + bigint, + timestamp, + text, + boolean, + jsonb, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const spaces = pgTable("spaces", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name"), + type: text("type").notNull(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + spaceNumber: text("spaceNumber").notNull(), + + parentSpace: bigint("parentSpace", { mode: "number" }).references( + () => spaces.id + ), + + infoData: jsonb("infoData") + .notNull() + .default({ zip: "", city: "", streetNumber: "" }), + + description: text("description"), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Space = typeof spaces.$inferSelect +export type NewSpace = typeof spaces.$inferInsert diff --git a/db/schema/staff_time_entries.ts b/db/schema/staff_time_entries.ts new file mode 100644 index 0000000..06534c5 --- /dev/null +++ b/db/schema/staff_time_entries.ts @@ -0,0 +1,68 @@ +import { + pgTable, + uuid, + bigint, + timestamp, + integer, + text, + boolean, + numeric, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import { timesStateEnum } from "./enums" +import {sql} from "drizzle-orm"; + +export const staffTimeEntries = pgTable("staff_time_entries", { + id: uuid("id").primaryKey().defaultRandom(), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + userId: uuid("user_id") + .notNull() + .references(() => authUsers.id, { onDelete: "cascade" }), + + startedAt: timestamp("started_at", { withTimezone: true }).notNull(), + stoppedAt: timestamp("stopped_at", { withTimezone: true }), + + durationMinutes: integer("duration_minutes").generatedAlwaysAs( + sql`CASE + WHEN stopped_at IS NOT NULL + THEN (EXTRACT(epoch FROM (stopped_at - started_at)) / 60) + ELSE NULL + END` + ), + + type: text("type").default("work"), + + description: text("description"), + + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(), + + archived: boolean("archived").notNull().default(false), + + updatedBy: uuid("updated_by").references(() => authUsers.id), + + source: text("source"), + + state: timesStateEnum("state").notNull().default("draft"), + + device: uuid("device"), + + internalNote: text("internal_note"), + + vacationReason: text("vacation_reason"), + vacationDays: numeric("vacation_days", { precision: 5, scale: 2 }), + + approvedBy: uuid("approved_by").references(() => authUsers.id), + approvedAt: timestamp("approved_at", { withTimezone: true }), + + sickReason: text("sick_reason"), +}) + +export type StaffTimeEntry = typeof staffTimeEntries.$inferSelect +export type NewStaffTimeEntry = typeof staffTimeEntries.$inferInsert diff --git a/db/schema/staff_time_entry_connects.ts b/db/schema/staff_time_entry_connects.ts new file mode 100644 index 0000000..5569605 --- /dev/null +++ b/db/schema/staff_time_entry_connects.ts @@ -0,0 +1,38 @@ +import { + pgTable, + uuid, + bigint, + timestamp, + integer, + text, +} from "drizzle-orm/pg-core" + +import { staffTimeEntries } from "./staff_time_entries" +import {sql} from "drizzle-orm"; + +export const staffTimeEntryConnects = pgTable("staff_time_entry_connects", { + id: uuid("id").primaryKey().defaultRandom(), + + timeEntryId: uuid("time_entry_id") + .notNull() + .references(() => staffTimeEntries.id, { onDelete: "cascade" }), + + projectId: bigint("project_id", { mode: "number" }), // referenziert später projects.id + + startedAt: timestamp("started_at", { withTimezone: true }).notNull(), + stoppedAt: timestamp("stopped_at", { withTimezone: true }).notNull(), + + durationMinutes: integer("duration_minutes").generatedAlwaysAs( + sql`(EXTRACT(epoch FROM (stopped_at - started_at)) / 60)` + ), + + notes: text("notes"), + + createdAt: timestamp("created_at", { withTimezone: true }).defaultNow(), + updatedAt: timestamp("updated_at", { withTimezone: true }).defaultNow(), +}) + +export type StaffTimeEntryConnect = + typeof staffTimeEntryConnects.$inferSelect +export type NewStaffTimeEntryConnect = + typeof staffTimeEntryConnects.$inferInsert diff --git a/db/schema/staff_zeitstromtimestamps.ts b/db/schema/staff_zeitstromtimestamps.ts new file mode 100644 index 0000000..2e70480 --- /dev/null +++ b/db/schema/staff_zeitstromtimestamps.ts @@ -0,0 +1,44 @@ +import { + pgTable, + uuid, + timestamp, + bigint, + text, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authProfiles } from "./auth_profiles" +import { staffTimeEntries } from "./staff_time_entries" + +export const staffZeitstromTimestamps = pgTable("staff_zeitstromtimestamps", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + profile: uuid("profile") + .notNull() + .references(() => authProfiles.id), + + key: text("key").notNull(), + + intent: text("intent").notNull(), + + time: timestamp("time", { withTimezone: true }).notNull(), + + staffTimeEntry: uuid("staff_time_entry").references( + () => staffTimeEntries.id + ), + + internalNote: text("internal_note"), +}) + +export type StaffZeitstromTimestamp = + typeof staffZeitstromTimestamps.$inferSelect +export type NewStaffZeitstromTimestamp = + typeof staffZeitstromTimestamps.$inferInsert diff --git a/db/schema/statementallocations.ts b/db/schema/statementallocations.ts new file mode 100644 index 0000000..52fa531 --- /dev/null +++ b/db/schema/statementallocations.ts @@ -0,0 +1,69 @@ +import { + pgTable, + uuid, + bigint, + integer, + text, + timestamp, + boolean, + doublePrecision, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import { customers } from "./customers" +import { vendors } from "./vendors" +import { ownaccounts } from "./ownaccounts" +import { incominginvoices } from "./incominginvoices" +import { createddocuments } from "./createddocuments" +import { bankstatements } from "./bankstatements" +import { accounts } from "./accounts" // Falls noch nicht erstellt → bitte melden! + +export const statementAllocations = pgTable("statementallocations", { + id: uuid("id").primaryKey().defaultRandom(), + + // foreign keys + bsId: integer("bs_id") + .notNull() + .references(() => bankstatements.id), + + cdId: integer("cd_id").references(() => createddocuments.id), + + amount: doublePrecision("amount").notNull().default(0), + + iiId: bigint("ii_id", { mode: "number" }).references( + () => incominginvoices.id + ), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + account: bigint("account", { mode: "number" }).references( + () => accounts.id + ), + + createdAt: timestamp("created_at", { + withTimezone: false, + }).defaultNow(), + + ownaccount: uuid("ownaccount").references(() => ownaccounts.id), + + description: text("description"), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id + ), + + vendor: bigint("vendor", { mode: "number" }).references(() => vendors.id), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + + updatedBy: uuid("updated_by").references(() => authUsers.id), + + archived: boolean("archived").notNull().default(false), +}) + +export type StatementAllocation = typeof statementAllocations.$inferSelect +export type NewStatementAllocation = + typeof statementAllocations.$inferInsert diff --git a/db/schema/tasks.ts b/db/schema/tasks.ts new file mode 100644 index 0000000..aa40617 --- /dev/null +++ b/db/schema/tasks.ts @@ -0,0 +1,51 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import { customers } from "./customers" + +export const tasks = pgTable("tasks", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + description: text("description"), + categorie: text("categorie"), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + // FIXED: user_id statt profile, verweist auf auth_users.id + userId: uuid("user_id").references(() => authUsers.id), + + project: bigint("project", { mode: "number" }), + plant: bigint("plant", { mode: "number" }), + + customer: bigint("customer", { mode: "number" }).references( + () => customers.id + ), + + profiles: jsonb("profiles").notNull().default([]), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Task = typeof tasks.$inferSelect +export type NewTask = typeof tasks.$inferInsert diff --git a/db/schema/taxtypes.ts b/db/schema/taxtypes.ts new file mode 100644 index 0000000..5d3c45d --- /dev/null +++ b/db/schema/taxtypes.ts @@ -0,0 +1,28 @@ +import { + pgTable, + bigint, + timestamp, + text, + uuid, +} from "drizzle-orm/pg-core" + +import { authUsers } from "./auth_users" + +export const taxTypes = pgTable("taxtypes", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + label: text("label").notNull(), + percentage: bigint("percentage", { mode: "number" }).notNull(), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type TaxType = typeof taxTypes.$inferSelect +export type NewTaxType = typeof taxTypes.$inferInsert diff --git a/db/schema/tenants.ts b/db/schema/tenants.ts new file mode 100644 index 0000000..b798aab --- /dev/null +++ b/db/schema/tenants.ts @@ -0,0 +1,140 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + integer, + smallint, + date, + uuid, + pgEnum, +} from "drizzle-orm/pg-core" +import { authUsers } from "./auth_users" +import {lockedTenantEnum} from "./enums"; + +export const tenants = pgTable( + "tenants", + { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + short: text("short").notNull(), + + calendarConfig: jsonb("calendarConfig").default({ + eventTypes: [ + { color: "blue", label: "Büro" }, + { color: "yellow", label: "Besprechung" }, + { color: "green", label: "Umsetzung" }, + { color: "red", label: "Vor Ort Termin" }, + ], + }), + + timeConfig: jsonb("timeConfig").notNull().default({}), + + tags: jsonb("tags").notNull().default({ + products: [], + documents: [], + }), + + measures: jsonb("measures") + .notNull() + .default([ + { name: "Netzwerktechnik", short: "NWT" }, + { name: "Elektrotechnik", short: "ELT" }, + { name: "Photovoltaik", short: "PV" }, + { name: "Videüberwachung", short: "VÜA" }, + { name: "Projekt", short: "PRJ" }, + { name: "Smart Home", short: "SHO" }, + ]), + + businessInfo: jsonb("businessInfo").default({ + zip: "", + city: "", + name: "", + street: "", + }), + + features: jsonb("features").default({ + objects: true, + calendar: true, + contacts: true, + projects: true, + vehicles: true, + contracts: true, + inventory: true, + accounting: true, + timeTracking: true, + planningBoard: true, + workingTimeTracking: true, + }), + + ownFields: jsonb("ownFields"), + + numberRanges: jsonb("numberRanges") + .notNull() + .default({ + vendors: { prefix: "", suffix: "", nextNumber: 10000 }, + customers: { prefix: "", suffix: "", nextNumber: 10000 }, + products: { prefix: "AT-", suffix: "", nextNumber: 1000 }, + quotes: { prefix: "AN-", suffix: "", nextNumber: 1000 }, + confirmationOrders: { prefix: "AB-", suffix: "", nextNumber: 1000 }, + invoices: { prefix: "RE-", suffix: "", nextNumber: 1000 }, + spaces: { prefix: "LP-", suffix: "", nextNumber: 1000 }, + inventoryitems: { prefix: "IA-", suffix: "", nextNumber: 1000 }, + projects: { prefix: "PRJ-", suffix: "", nextNumber: 1000 }, + costcentres: { prefix: "KST-", suffix: "", nextNumber: 1000 }, + }), + + standardEmailForInvoices: text("standardEmailForInvoices"), + + extraModules: jsonb("extraModules").notNull().default([]), + + isInTrial: boolean("isInTrial").default(false), + trialEndDate: date("trialEndDate"), + + stripeCustomerId: text("stripeCustomerId"), + + hasActiveLicense: boolean("hasActiveLicense").notNull().default(false), + + userLicenseCount: integer("userLicenseCount") + .notNull() + .default(0), + + workstationLicenseCount: integer("workstationLicenseCount") + .notNull() + .default(0), + + standardPaymentDays: smallint("standardPaymentDays") + .notNull() + .default(14), + + dokuboxEmailAddresses: jsonb("dokuboxEmailAddresses").default([]), + + dokuboxkey: uuid("dokuboxkey").notNull().defaultRandom(), + + autoPrepareIncomingInvoices: boolean("autoPrepareIncomingInvoices") + .default(true), + + portalDomain: text("portalDomain"), + + portalConfig: jsonb("portalConfig") + .notNull() + .default({ primayColor: "#69c350" }), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), + + locked: lockedTenantEnum("locked"), + } +) + +export type Tenant = typeof tenants.$inferSelect +export type NewTenant = typeof tenants.$inferInsert diff --git a/db/schema/texttemplates.ts b/db/schema/texttemplates.ts new file mode 100644 index 0000000..ce1ac94 --- /dev/null +++ b/db/schema/texttemplates.ts @@ -0,0 +1,44 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import { textTemplatePositionsEnum } from "./enums" + +export const textTemplates = pgTable("texttemplates", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + name: text("name").notNull(), + text: text("text").notNull(), + + documentType: text("documentType").default(""), + + default: boolean("default").notNull().default(false), + + pos: textTemplatePositionsEnum("pos").notNull(), + + archived: boolean("archived").notNull().default(false), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type TextTemplate = typeof textTemplates.$inferSelect +export type NewTextTemplate = typeof textTemplates.$inferInsert diff --git a/db/schema/units.ts b/db/schema/units.ts new file mode 100644 index 0000000..61e5e11 --- /dev/null +++ b/db/schema/units.ts @@ -0,0 +1,27 @@ +import { + pgTable, + bigint, + timestamp, + text, +} from "drizzle-orm/pg-core" + +export const units = pgTable("units", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + single: text("single").notNull(), + + multiple: text("multiple"), + short: text("short"), + + step: text("step").notNull().default("1"), +}) + +export type Unit = typeof units.$inferSelect +export type NewUnit = typeof units.$inferInsert diff --git a/db/schema/user_credentials.ts b/db/schema/user_credentials.ts new file mode 100644 index 0000000..f1ae045 --- /dev/null +++ b/db/schema/user_credentials.ts @@ -0,0 +1,53 @@ +import { + pgTable, + uuid, + timestamp, + bigint, + boolean, + jsonb, + numeric, pgEnum, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" +import {credentialTypesEnum} from "./enums"; + + + +export const userCredentials = pgTable("user_credentials", { + id: uuid("id").primaryKey().defaultRandom(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + userId: uuid("user_id") + .notNull() + .references(() => authUsers.id), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + + tenantId: bigint("tenant_id", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + smtpPort: numeric("smtp_port"), + smtpSsl: boolean("smtp_ssl"), + + type: credentialTypesEnum("type").notNull(), + + imapPort: numeric("imap_port"), + imapSsl: boolean("imap_ssl"), + + emailEncrypted: jsonb("email_encrypted"), + passwordEncrypted: jsonb("password_encrypted"), + + smtpHostEncrypted: jsonb("smtp_host_encrypted"), + imapHostEncrypted: jsonb("imap_host_encrypted"), + + accessTokenEncrypted: jsonb("access_token_encrypted"), + refreshTokenEncrypted: jsonb("refresh_token_encrypted"), +}) + +export type UserCredential = typeof userCredentials.$inferSelect +export type NewUserCredential = typeof userCredentials.$inferInsert diff --git a/db/schema/vehicles.ts b/db/schema/vehicles.ts new file mode 100644 index 0000000..7915657 --- /dev/null +++ b/db/schema/vehicles.ts @@ -0,0 +1,57 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + uuid, + doublePrecision, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const vehicles = pgTable("vehicles", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + licensePlate: text("licensePlate"), + name: text("name"), + type: text("type"), + + active: boolean("active").default(true), + + // FIXED: driver references auth_users.id + driver: uuid("driver").references(() => authUsers.id), + + vin: text("vin"), + + tankSize: doublePrecision("tankSize").notNull().default(0), + + archived: boolean("archived").notNull().default(false), + + buildYear: text("buildYear"), + + towingCapacity: bigint("towingCapacity", { mode: "number" }), + powerInKW: bigint("powerInKW", { mode: "number" }), + + color: text("color"), + + profiles: jsonb("profiles").notNull().default([]), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Vehicle = typeof vehicles.$inferSelect +export type NewVehicle = typeof vehicles.$inferInsert diff --git a/db/schema/vendors.ts b/db/schema/vendors.ts new file mode 100644 index 0000000..32ae94f --- /dev/null +++ b/db/schema/vendors.ts @@ -0,0 +1,45 @@ +import { + pgTable, + bigint, + text, + timestamp, + boolean, + jsonb, + uuid, +} from "drizzle-orm/pg-core" + +import { tenants } from "./tenants" +import { authUsers } from "./auth_users" + +export const vendors = pgTable("vendors", { + id: bigint("id", { mode: "number" }) + .primaryKey() + .generatedByDefaultAsIdentity(), + + createdAt: timestamp("created_at", { withTimezone: true }) + .notNull() + .defaultNow(), + + name: text("name").notNull(), + vendorNumber: text("vendorNumber").notNull(), + + tenant: bigint("tenant", { mode: "number" }) + .notNull() + .references(() => tenants.id), + + infoData: jsonb("infoData").notNull().default({}), + notes: text("notes"), + + hasSEPA: boolean("hasSEPA").notNull().default(false), + + profiles: jsonb("profiles").notNull().default([]), + archived: boolean("archived").notNull().default(false), + + defaultPaymentMethod: text("defaultPaymentMethod"), + + updatedAt: timestamp("updated_at", { withTimezone: true }), + updatedBy: uuid("updated_by").references(() => authUsers.id), +}) + +export type Vendor = typeof vendors.$inferSelect +export type NewVendor = typeof vendors.$inferInsert diff --git a/drizzle.config.ts b/drizzle.config.ts new file mode 100644 index 0000000..d2b0225 --- /dev/null +++ b/drizzle.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "drizzle-kit" +import {secrets} from "./src/utils/secrets"; + +export default defineConfig({ + dialect: "postgresql", + schema: "./db/schema", + out: "./db/migrations", + dbCredentials: { + url: secrets.DATABASE_URL || process.env.DATABASE_URL, + }, +}) \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2abc221..c6aa764 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,12 +28,14 @@ "canvas": "^3.2.0", "crypto": "^1.0.1", "dayjs": "^1.11.18", + "drizzle-orm": "^0.45.0", "fastify": "^5.5.0", "fastify-plugin": "^5.0.1", "imapflow": "^1.1.1", "jsonwebtoken": "^9.0.2", "nodemailer": "^7.0.6", "pdf-lib": "^1.17.1", + "pg": "^8.16.3", "pngjs": "^7.0.0", "sharp": "^0.34.5", "xmlbuilder": "^15.1.1", @@ -44,6 +46,7 @@ "@types/bcrypt": "^6.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^24.3.0", + "drizzle-kit": "^0.31.8", "prisma": "^6.15.0", "tsx": "^4.20.5", "typescript": "^5.9.2" @@ -5273,6 +5276,13 @@ "tslib": "^2.1.0" } }, + "node_modules/@drizzle-team/brocli": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", + "integrity": "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/@emnapi/runtime": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.0.tgz", @@ -5283,6 +5293,442 @@ "tslib": "^2.4.0" } }, + "node_modules/@esbuild-kit/core-utils": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@esbuild-kit/core-utils/-/core-utils-3.3.2.tgz", + "integrity": "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.18.20", + "source-map-support": "^0.5.21" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", + "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", + "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/android-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", + "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", + "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/darwin-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", + "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", + "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/freebsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", + "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", + "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", + "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", + "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-loong64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", + "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-mips64el": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", + "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-ppc64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", + "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-riscv64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", + "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-s390x": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", + "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/linux-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", + "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/netbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", + "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/openbsd-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", + "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/sunos-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", + "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-arm64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", + "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-ia32": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", + "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/@esbuild/win32-x64": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", + "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild-kit/core-utils/node_modules/esbuild": { + "version": "0.18.20", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", + "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.20", + "@esbuild/android-arm64": "0.18.20", + "@esbuild/android-x64": "0.18.20", + "@esbuild/darwin-arm64": "0.18.20", + "@esbuild/darwin-x64": "0.18.20", + "@esbuild/freebsd-arm64": "0.18.20", + "@esbuild/freebsd-x64": "0.18.20", + "@esbuild/linux-arm": "0.18.20", + "@esbuild/linux-arm64": "0.18.20", + "@esbuild/linux-ia32": "0.18.20", + "@esbuild/linux-loong64": "0.18.20", + "@esbuild/linux-mips64el": "0.18.20", + "@esbuild/linux-ppc64": "0.18.20", + "@esbuild/linux-riscv64": "0.18.20", + "@esbuild/linux-s390x": "0.18.20", + "@esbuild/linux-x64": "0.18.20", + "@esbuild/netbsd-x64": "0.18.20", + "@esbuild/openbsd-x64": "0.18.20", + "@esbuild/sunos-x64": "0.18.20", + "@esbuild/win32-arm64": "0.18.20", + "@esbuild/win32-ia32": "0.18.20", + "@esbuild/win32-x64": "0.18.20" + } + }, + "node_modules/@esbuild-kit/esm-loader": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/@esbuild-kit/esm-loader/-/esm-loader-2.6.5.tgz", + "integrity": "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA==", + "deprecated": "Merged into tsx: https://tsx.is", + "dev": true, + "license": "MIT", + "dependencies": { + "@esbuild-kit/core-utils": "^3.3.2", + "get-tsconfig": "^4.7.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", @@ -7686,9 +8132,9 @@ } }, "node_modules/archiver-utils/node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "license": "ISC", "dependencies": { "foreground-child": "^3.1.0", @@ -7964,6 +8410,13 @@ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/bwip-js": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/bwip-js/-/bwip-js-4.8.0.tgz", @@ -8323,6 +8776,147 @@ "url": "https://dotenvx.com" } }, + "node_modules/drizzle-kit": { + "version": "0.31.8", + "resolved": "https://registry.npmjs.org/drizzle-kit/-/drizzle-kit-0.31.8.tgz", + "integrity": "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@drizzle-team/brocli": "^0.10.2", + "@esbuild-kit/esm-loader": "^2.5.5", + "esbuild": "^0.25.4", + "esbuild-register": "^3.5.0" + }, + "bin": { + "drizzle-kit": "bin.cjs" + } + }, + "node_modules/drizzle-orm": { + "version": "0.45.0", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.45.0.tgz", + "integrity": "sha512-lyd9VRk3SXKRjV/gQckQzmJgkoYMvVG3A2JAV0vh3L+Lwk+v9+rK5Gj0H22y+ZBmxsrRBgJ5/RbQCN7DWd1dtQ==", + "license": "Apache-2.0", + "peerDependencies": { + "@aws-sdk/client-rds-data": ">=3", + "@cloudflare/workers-types": ">=4", + "@electric-sql/pglite": ">=0.2.0", + "@libsql/client": ">=0.10.0", + "@libsql/client-wasm": ">=0.10.0", + "@neondatabase/serverless": ">=0.10.0", + "@op-engineering/op-sqlite": ">=2", + "@opentelemetry/api": "^1.4.1", + "@planetscale/database": ">=1.13", + "@prisma/client": "*", + "@tidbcloud/serverless": "*", + "@types/better-sqlite3": "*", + "@types/pg": "*", + "@types/sql.js": "*", + "@upstash/redis": ">=1.34.7", + "@vercel/postgres": ">=0.8.0", + "@xata.io/client": "*", + "better-sqlite3": ">=7", + "bun-types": "*", + "expo-sqlite": ">=14.0.0", + "gel": ">=2", + "knex": "*", + "kysely": "*", + "mysql2": ">=2", + "pg": ">=8", + "postgres": ">=3", + "sql.js": ">=1", + "sqlite3": ">=5" + }, + "peerDependenciesMeta": { + "@aws-sdk/client-rds-data": { + "optional": true + }, + "@cloudflare/workers-types": { + "optional": true + }, + "@electric-sql/pglite": { + "optional": true + }, + "@libsql/client": { + "optional": true + }, + "@libsql/client-wasm": { + "optional": true + }, + "@neondatabase/serverless": { + "optional": true + }, + "@op-engineering/op-sqlite": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@prisma/client": { + "optional": true + }, + "@tidbcloud/serverless": { + "optional": true + }, + "@types/better-sqlite3": { + "optional": true + }, + "@types/pg": { + "optional": true + }, + "@types/sql.js": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/postgres": { + "optional": true + }, + "@xata.io/client": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "bun-types": { + "optional": true + }, + "expo-sqlite": { + "optional": true + }, + "gel": { + "optional": true + }, + "knex": { + "optional": true + }, + "kysely": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "postgres": { + "optional": true + }, + "prisma": { + "optional": true + }, + "sql.js": { + "optional": true + }, + "sqlite3": { + "optional": true + } + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -8484,6 +9078,19 @@ "@esbuild/win32-x64": "0.25.9" } }, + "node_modules/esbuild-register": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/esbuild-register/-/esbuild-register-3.6.0.tgz", + "integrity": "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.4" + }, + "peerDependencies": { + "esbuild": ">=0.12 <1" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -8866,14 +9473,14 @@ "license": "MIT" }, "node_modules/glob": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.3.tgz", - "integrity": "sha512-2Nim7dha1KVkaiF4q6Dj+ngPPMdfvLJEOpZk/jKiUAkqKebpGAWQXAq9z1xu9HKu5lWfqw/FASuccEjyznjPaA==", - "license": "ISC", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "license": "BlueOak-1.0.0", "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", - "minimatch": "^10.0.3", + "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" @@ -9200,12 +9807,12 @@ } }, "node_modules/jws": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", - "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.3.tgz", + "integrity": "sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g==", "license": "MIT", "dependencies": { - "jwa": "^1.4.1", + "jwa": "^1.4.2", "safe-buffer": "^5.0.1" } }, @@ -9436,10 +10043,10 @@ } }, "node_modules/minimatch": { - "version": "10.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.3.tgz", - "integrity": "sha512-IPZ167aShDZZUMdRk66cyQAW3qr0WzbHkPdMYa8bzZhlHhO3jALbKdxcaak7W9FfT2rZNpQuUu4Od7ILEpXSaw==", - "license": "ISC", + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/brace-expansion": "^5.0.0" }, @@ -9663,6 +10270,95 @@ "devOptional": true, "license": "MIT" }, + "node_modules/pg": { + "version": "8.16.3", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.16.3.tgz", + "integrity": "sha512-enxc1h0jA/aq5oSDMvqyW3q89ra6XIIDZgCX9vkMrnz5DFTw/Ny3Li2lFQ+pt3L6MCgm/5o2o8HW9hiJji+xvw==", + "license": "MIT", + "dependencies": { + "pg-connection-string": "^2.9.1", + "pg-pool": "^3.10.1", + "pg-protocol": "^1.10.3", + "pg-types": "2.2.0", + "pgpass": "1.0.5" + }, + "engines": { + "node": ">= 16.0.0" + }, + "optionalDependencies": { + "pg-cloudflare": "^1.2.7" + }, + "peerDependencies": { + "pg-native": ">=3.0.1" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-cloudflare": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.2.7.tgz", + "integrity": "sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==", + "license": "MIT", + "optional": true + }, + "node_modules/pg-connection-string": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.9.1.tgz", + "integrity": "sha512-nkc6NpDcvPVpZXxrreI/FOtX3XemeLl8E0qFr6F2Lrm/I8WOnaWNhIPK2Z7OHpw7gh5XJThi6j6ppgNoaT1w4w==", + "license": "MIT" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "license": "ISC", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.10.1.tgz", + "integrity": "sha512-Tu8jMlcX+9d8+QVzKIvM/uJtp07PKr82IUOYEphaWcoBhIYkoHpLXN3qO59nAI11ripznDsEzEv8nUxBVWajGg==", + "license": "MIT", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.10.3.tgz", + "integrity": "sha512-6DIBgBQaTKDJyxnXaLiLR8wBpQQcGWuAESkRBX/t6OwA8YsqP+iVSiond2EDy6Y/dsGk8rh/jtax3js5NeV7JQ==", + "license": "MIT" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "license": "MIT", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz", + "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==", + "license": "MIT", + "dependencies": { + "split2": "^4.1.0" + } + }, "node_modules/pino": { "version": "9.9.0", "resolved": "https://registry.npmjs.org/pino/-/pino-9.9.0.tgz", @@ -9721,6 +10417,45 @@ "node": ">=14.19.0" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "license": "MIT", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", @@ -10222,6 +10957,27 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -10715,6 +11471,15 @@ "node": ">=8.0" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yaml": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", diff --git a/package.json b/package.json index 46bea4d..e70ea9c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", - "start": "node dist/index.js" + "start": "node dist/index.js", + "schema:index": "ts-node scripts/generate-schema-index.ts" }, "repository": { "type": "git", @@ -35,12 +36,14 @@ "canvas": "^3.2.0", "crypto": "^1.0.1", "dayjs": "^1.11.18", + "drizzle-orm": "^0.45.0", "fastify": "^5.5.0", "fastify-plugin": "^5.0.1", "imapflow": "^1.1.1", "jsonwebtoken": "^9.0.2", "nodemailer": "^7.0.6", "pdf-lib": "^1.17.1", + "pg": "^8.16.3", "pngjs": "^7.0.0", "sharp": "^0.34.5", "xmlbuilder": "^15.1.1", @@ -51,6 +54,7 @@ "@types/bcrypt": "^6.0.0", "@types/jsonwebtoken": "^9.0.10", "@types/node": "^24.3.0", + "drizzle-kit": "^0.31.8", "prisma": "^6.15.0", "tsx": "^4.20.5", "typescript": "^5.9.2" diff --git a/scripts/generate-schema-index.ts b/scripts/generate-schema-index.ts new file mode 100644 index 0000000..a7e6c29 --- /dev/null +++ b/scripts/generate-schema-index.ts @@ -0,0 +1,16 @@ +import fs from "node:fs" +import path from "node:path" + +const schemaDir = path.resolve("db/schema") +const indexFile = path.join(schemaDir, "index.ts") + +const files = fs + .readdirSync(schemaDir) + .filter((f) => f.endsWith(".ts") && f !== "index.ts") + +const exportsToWrite = files + .map((f) => `export * from "./${f.replace(".ts", "")}"`) + .join("\n") + +fs.writeFileSync(indexFile, exportsToWrite) +console.log("✓ schema/index.ts generated") \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 37a4e55..73cf634 100644 --- a/src/index.ts +++ b/src/index.ts @@ -12,6 +12,7 @@ import authPlugin from "./plugins/auth"; import adminRoutes from "./routes/admin"; import corsPlugin from "./plugins/cors"; import queryConfigPlugin from "./plugins/queryconfig"; +import dbPlugin from "./plugins/db"; import resourceRoutes from "./routes/resources"; import resourceRoutesSpecial from "./routes/resourcesSpecial"; import fastifyCookie from "@fastify/cookie"; @@ -57,6 +58,7 @@ async function main() { await app.register(supabasePlugin); await app.register(tenantPlugin); await app.register(dayjsPlugin); + await app.register(dbPlugin); app.addHook('preHandler', (req, reply, done) => { console.log(req.method) @@ -114,6 +116,14 @@ async function main() { },{prefix: "/api"}) + app.ready(async () => { + try { + const result = await app.db.execute("SELECT NOW()"); + console.log("✓ DB connection OK: " + JSON.stringify(result.rows[0])); + } catch (err) { + console.log("❌ DB connection failed:", err); + } + }); // Start try { diff --git a/src/plugins/db.ts b/src/plugins/db.ts new file mode 100644 index 0000000..092393a --- /dev/null +++ b/src/plugins/db.ts @@ -0,0 +1,34 @@ +import fp from "fastify-plugin" +import {drizzle, NodePgDatabase} from "drizzle-orm/node-postgres" +import { Pool } from "pg" +import * as schema from "../../db/schema" + +export default fp(async (server, opts) => { + const pool = new Pool({ + host: "db-001.netbird.cloud", + port: Number(process.env.DB_PORT || 5432), + user: "postgres", + password: "wJw7aNpEBJdcxgoct6GXNpvY4Cn6ECqu", + database: "fedeo", + ssl: process.env.DB_DISABLE_SSL === "true" ? false : undefined, + }) + + // Drizzle instance + const db = drizzle(pool, { schema }) + + // Dekorieren -> überall server.db + server.decorate("db", db) + + // Graceful Shutdown + server.addHook("onClose", async () => { + await pool.end() + }) + + server.log.info("Drizzle database connected") +}) + +declare module "fastify" { + interface FastifyInstance { + db:NodePgDatabaseHallo,
-dein Passwort wurde zurückgesetzt.
-Neues Passwort: ${plainPassword}
-Bitte ändere es nach dem Login umgehend.
` - ) + ` +Hallo,
+Dein Passwort wurde zurückgesetzt.
+Neues Passwort: ${plainPassword}
+Bitte ändere es nach dem Login umgehend.
+ ` + ); - return { success: true } - }) -} \ No newline at end of file + return { success: true }; + }); +} diff --git a/src/utils/secrets.ts b/src/utils/secrets.ts index 1d384c2..b67a1e7 100644 --- a/src/utils/secrets.ts +++ b/src/utils/secrets.ts @@ -13,6 +13,7 @@ export let secrets = { JWT_SECRET: string PORT: number HOST: string + DATABASE_URL: string SUPABASE_URL: string SUPABASE_SERVICE_ROLE_KEY: string S3_BUCKET: string