mirror of
https://github.com/Hestia-Homes/assessment-model.git
synced 2026-07-27 22:45:03 +00:00
Merge pull request #387 from Hestia-Homes/feature/tags-schema
feat(db): portfolio_tag + property_tag tables (tagging system)
This commit is contained in:
commit
f801025ce3
5 changed files with 12540 additions and 0 deletions
|
|
@ -16,6 +16,7 @@ import * as CrmSchema from "@/app/db/schema/crm/hubspot_deal_table";
|
|||
import * as UploadedFilesSchema from "@/app/db/schema/uploaded_files";
|
||||
import * as PortfolioOrgSchema from "@/app/db/schema/portfolio_organisation";
|
||||
import * as BulkAddressUploadsSchema from "@/app/db/schema/bulk_address_uploads";
|
||||
import * as TagsSchema from "@/app/db/schema/tags";
|
||||
|
||||
export const pool = new Pool({
|
||||
host: process.env.DB_HOST,
|
||||
|
|
@ -43,6 +44,7 @@ const schema = {
|
|||
...UploadedFilesSchema,
|
||||
...PortfolioOrgSchema,
|
||||
...BulkAddressUploadsSchema,
|
||||
...TagsSchema,
|
||||
};
|
||||
|
||||
export const db = drizzle(pool, {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
CREATE TABLE "portfolio_tag" (
|
||||
"id" bigserial PRIMARY KEY NOT NULL,
|
||||
"portfolio_id" bigint NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"colour" text NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE "property_tag" (
|
||||
"property_id" bigint NOT NULL,
|
||||
"tag_id" bigint NOT NULL,
|
||||
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "property_tag_property_id_tag_id_pk" PRIMARY KEY("property_id","tag_id")
|
||||
);
|
||||
--> statement-breakpoint
|
||||
ALTER TABLE "portfolio_tag" ADD CONSTRAINT "portfolio_tag_portfolio_id_portfolio_id_fk" FOREIGN KEY ("portfolio_id") REFERENCES "public"."portfolio"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "property_tag" ADD CONSTRAINT "property_tag_property_id_property_id_fk" FOREIGN KEY ("property_id") REFERENCES "public"."property"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
ALTER TABLE "property_tag" ADD CONSTRAINT "property_tag_tag_id_portfolio_tag_id_fk" FOREIGN KEY ("tag_id") REFERENCES "public"."portfolio_tag"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX "uq_portfolio_tag_portfolio_lower_name" ON "portfolio_tag" USING btree ("portfolio_id",lower("name"));--> statement-breakpoint
|
||||
CREATE INDEX "ix_property_tag_tag" ON "property_tag" USING btree ("tag_id");
|
||||
12423
src/app/db/migrations/meta/0271_snapshot.json
Normal file
12423
src/app/db/migrations/meta/0271_snapshot.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1891,6 +1891,13 @@
|
|||
"when": 1783953369297,
|
||||
"tag": "0270_epc_main_heating_detail_epc_property_index",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 271,
|
||||
"version": "7",
|
||||
"when": 1783957414552,
|
||||
"tag": "0271_portfolio_tag_and_property_tag",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
87
src/app/db/schema/tags.ts
Normal file
87
src/app/db/schema/tags.ts
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
import {
|
||||
bigint,
|
||||
bigserial,
|
||||
index,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
text,
|
||||
timestamp,
|
||||
uniqueIndex,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { relations, sql } from "drizzle-orm";
|
||||
import { portfolio } from "./portfolio";
|
||||
import { property } from "./property";
|
||||
|
||||
// A Tag is a user-created, coloured label owned by a Portfolio, used to form an
|
||||
// arbitrary group of its Properties for bulk actions (CONTEXT.md, ADR-0013).
|
||||
// Flat (no hierarchy) and many-to-many with Property via `property_tag`.
|
||||
export const portfolioTag = pgTable(
|
||||
"portfolio_tag",
|
||||
{
|
||||
id: bigserial("id", { mode: "bigint" }).primaryKey(),
|
||||
portfolioId: bigint("portfolio_id", { mode: "bigint" })
|
||||
.notNull()
|
||||
.references(() => portfolio.id, { onDelete: "cascade" }),
|
||||
name: text("name").notNull(),
|
||||
// Freeform hex string (e.g. "#7C3AED"); format is validated in the app.
|
||||
colour: text("colour").notNull(),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow()
|
||||
.$onUpdate(() => new Date()),
|
||||
},
|
||||
(table) => [
|
||||
// Names are unique within a Portfolio, case-insensitively ("Void" vs "void").
|
||||
uniqueIndex("uq_portfolio_tag_portfolio_lower_name").on(
|
||||
table.portfolioId,
|
||||
sql`lower(${table.name})`,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// The many-to-many membership: one row per (Property, Tag). Both FKs cascade —
|
||||
// deleting a Tag drops its memberships (ADR-0013, hard delete), and deleting a
|
||||
// Property drops its tags.
|
||||
export const propertyTag = pgTable(
|
||||
"property_tag",
|
||||
{
|
||||
propertyId: bigint("property_id", { mode: "bigint" })
|
||||
.notNull()
|
||||
.references(() => property.id, { onDelete: "cascade" }),
|
||||
tagId: bigint("tag_id", { mode: "bigint" })
|
||||
.notNull()
|
||||
.references(() => portfolioTag.id, { onDelete: "cascade" }),
|
||||
createdAt: timestamp("created_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
// A Property can't hold the same Tag twice.
|
||||
primaryKey({ columns: [table.propertyId, table.tagId] }),
|
||||
// The composite PK leads with property_id; reverse lookups ("all Properties
|
||||
// for this Tag" — chips, filter, run resolution) need a tag_id-leading index.
|
||||
index("ix_property_tag_tag").on(table.tagId),
|
||||
],
|
||||
);
|
||||
|
||||
export const portfolioTagRelations = relations(portfolioTag, ({ one, many }) => ({
|
||||
portfolio: one(portfolio, {
|
||||
fields: [portfolioTag.portfolioId],
|
||||
references: [portfolio.id],
|
||||
}),
|
||||
memberships: many(propertyTag),
|
||||
}));
|
||||
|
||||
export const propertyTagRelations = relations(propertyTag, ({ one }) => ({
|
||||
tag: one(portfolioTag, {
|
||||
fields: [propertyTag.tagId],
|
||||
references: [portfolioTag.id],
|
||||
}),
|
||||
property: one(property, {
|
||||
fields: [propertyTag.propertyId],
|
||||
references: [property.id],
|
||||
}),
|
||||
}));
|
||||
Loading…
Add table
Reference in a new issue