feat(db): add feature flags schema for beta feature testing

Add feature_flags and feature_flag_access tables to support controlled
beta testing of new features in production without a separate test environment.
This commit is contained in:
syntaxbullet
2026-02-12 14:41:12 +01:00
parent 73ad889018
commit 64804f7066
5 changed files with 1287 additions and 0 deletions

View File

@@ -0,0 +1,25 @@
CREATE TABLE "feature_flag_access" (
"id" serial PRIMARY KEY NOT NULL,
"flag_id" integer NOT NULL,
"guild_id" bigint,
"user_id" bigint,
"role_id" bigint,
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
CREATE TABLE "feature_flags" (
"id" serial PRIMARY KEY NOT NULL,
"name" varchar(100) NOT NULL,
"enabled" boolean DEFAULT false NOT NULL,
"description" text,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "feature_flags_name_unique" UNIQUE("name")
);
--> statement-breakpoint
ALTER TABLE "items" ALTER COLUMN "rarity" SET DEFAULT 'C';--> statement-breakpoint
ALTER TABLE "feature_flag_access" ADD CONSTRAINT "feature_flag_access_flag_id_feature_flags_id_fk" FOREIGN KEY ("flag_id") REFERENCES "public"."feature_flags"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "idx_ffa_flag_id" ON "feature_flag_access" USING btree ("flag_id");--> statement-breakpoint
CREATE INDEX "idx_ffa_guild_id" ON "feature_flag_access" USING btree ("guild_id");--> statement-breakpoint
CREATE INDEX "idx_ffa_user_id" ON "feature_flag_access" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "idx_ffa_role_id" ON "feature_flag_access" USING btree ("role_id");

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,13 @@
"when": 1767716705797, "when": 1767716705797,
"tag": "0002_fancy_forge", "tag": "0002_fancy_forge",
"breakpoints": true "breakpoints": true
},
{
"idx": 3,
"version": "7",
"when": 1770903573324,
"tag": "0003_new_senator_kelly",
"breakpoints": true
} }
] ]
} }

View File

@@ -0,0 +1,49 @@
import {
pgTable,
serial,
varchar,
boolean,
text,
timestamp,
bigint,
integer,
index,
} from 'drizzle-orm/pg-core';
import { relations, type InferSelectModel } from 'drizzle-orm';
export type FeatureFlag = InferSelectModel<typeof featureFlags>;
export type FeatureFlagAccess = InferSelectModel<typeof featureFlagAccess>;
export const featureFlags = pgTable('feature_flags', {
id: serial('id').primaryKey(),
name: varchar('name', { length: 100 }).notNull().unique(),
enabled: boolean('enabled').default(false).notNull(),
description: text('description'),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull(),
});
export const featureFlagAccess = pgTable('feature_flag_access', {
id: serial('id').primaryKey(),
flagId: integer('flag_id').notNull().references(() => featureFlags.id, { onDelete: 'cascade' }),
guildId: bigint('guild_id', { mode: 'bigint' }),
userId: bigint('user_id', { mode: 'bigint' }),
roleId: bigint('role_id', { mode: 'bigint' }),
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
}, (table) => [
index('idx_ffa_flag_id').on(table.flagId),
index('idx_ffa_guild_id').on(table.guildId),
index('idx_ffa_user_id').on(table.userId),
index('idx_ffa_role_id').on(table.roleId),
]);
export const featureFlagsRelations = relations(featureFlags, ({ many }) => ({
access: many(featureFlagAccess),
}));
export const featureFlagAccessRelations = relations(featureFlagAccess, ({ one }) => ({
flag: one(featureFlags, {
fields: [featureFlagAccess.flagId],
references: [featureFlags.id],
}),
}));

View File

@@ -4,3 +4,4 @@ export * from './inventory';
export * from './economy'; export * from './economy';
export * from './quests'; export * from './quests';
export * from './moderation'; export * from './moderation';
export * from './feature-flags';