diff --git a/.claude/settings.json b/.claude/settings.json
index 8f6349b5..e2e82f74 100644
--- a/.claude/settings.json
+++ b/.claude/settings.json
@@ -110,6 +110,7 @@
"code-review@claude-plugins-official": true,
"commit-commands@claude-plugins-official": true,
"supabase@claude-plugins-official": true,
- "vercel@claude-plugins-official": true
+ "vercel@claude-plugins-official": true,
+ "code-simplifier@claude-plugins-official": true
}
}
diff --git a/docs/PATTERNS.md b/docs/PATTERNS.md
index e7c771e6..b639bfb0 100644
--- a/docs/PATTERNS.md
+++ b/docs/PATTERNS.md
@@ -19,6 +19,7 @@ trigger: always_on
- Cached Data Fetching (React `cache()`)
- [Mutations](./patterns/mutations.md)
- Server Action + Zod Validation + Redirect
+ - Server Action Error Handling
- [Authentication](./patterns/authentication.md)
- Auth Check in Server Components
- Auth Check in Server Actions
@@ -39,6 +40,7 @@ trigger: always_on
- [Testing Patterns](./patterns/testing-patterns.md)
- Playwright E2E Tests
- Integration Tests with PGlite
+ - Integration Tests with Module Mocking
- Unit Tests
- Vitest Deep Mocks (vitest-mock-extended)
- [Logging](./patterns/logging.md)
@@ -50,6 +52,14 @@ trigger: always_on
- Consistent Site URL Resolution
- [Database Migrations](./patterns/database-migrations.md)
- Drizzle Migrations + Test Schema Export
+- [Config-Driven Enums](./patterns/config-driven-enums.md) ⭐ **NEW**
+ - Single Source of Truth for Domain Enums
+ - Rich Metadata (labels, icons, styles, descriptions)
+- [Service Layer Transactions](./patterns/service-layer-transactions.md) ⭐ **NEW**
+ - Transactional Updates with Timeline Events
+ - Notification Error Handling
+- [Discriminated Union Props](./patterns/ui-patterns/discriminated-union-props.md) ⭐ **NEW**
+ - Type-Safe Component Props for Multi-Type Components
## Adding New Patterns
diff --git a/docs/patterns/config-driven-enums.md b/docs/patterns/config-driven-enums.md
new file mode 100644
index 00000000..dd377bd1
--- /dev/null
+++ b/docs/patterns/config-driven-enums.md
@@ -0,0 +1,169 @@
+# Config-Driven Enum with Rich Metadata
+
+**Status**: Established Pattern
+**Version**: 1.0
+**Last Updated**: January 10, 2026
+
+---
+
+## Overview
+
+Use centralized configuration objects for domain enums that need UI metadata (labels, styles, icons, descriptions). This pattern provides type safety, runtime validation, and a single source of truth for all enum-related data.
+
+## Example: Issue Status System
+
+[src/lib/issues/status.ts](file:///home/froeht/Code/PinPoint/src/lib/issues/status.ts)
+
+## Pattern Structure
+
+```typescript
+import { Icon1, Icon2 } from "lucide-react";
+import type { LucideIcon } from "lucide-react";
+
+// 1. Named constants for type-safe access and IDE autocomplete
+export const MY_ENUM = {
+ VALUE_A: "value_a",
+ VALUE_B: "value_b",
+ VALUE_C: "value_c",
+} as const;
+
+// 2. Runtime array for validation (Object.values)
+export const ALL_MY_ENUM_VALUES = Object.values(MY_ENUM);
+
+// 3. Type-safe array with literal types (for Drizzle schema)
+export const MY_ENUM_VALUES = ["value_a", "value_b", "value_c"] as const;
+
+// 4. Derive the canonical type from the const array
+export type MyEnum = (typeof MY_ENUM_VALUES)[number];
+
+// 5. Configuration object with all metadata
+export const MY_ENUM_CONFIG: Record<
+ MyEnum,
+ { label: string; description: string; styles: string; icon: LucideIcon }
+> = {
+ value_a: {
+ label: "Value A",
+ description: "Description for A",
+ styles: "bg-blue-500/20 text-blue-400 border-blue-500",
+ icon: Icon1,
+ },
+ value_b: {
+ label: "Value B",
+ description: "Description for B",
+ styles: "bg-green-500/20 text-green-400 border-green-500",
+ icon: Icon2,
+ },
+ value_c: {
+ label: "Value C",
+ description: "Description for C",
+ styles: "bg-red-500/20 text-red-400 border-red-500",
+ icon: Icon1,
+ },
+};
+
+// 6. Getter functions for type-safe access
+export function getMyEnumLabel(value: MyEnum): string {
+ return MY_ENUM_CONFIG[value].label;
+}
+
+export function getMyEnumIcon(value: MyEnum): LucideIcon {
+ return MY_ENUM_CONFIG[value].icon;
+}
+
+export function getMyEnumStyles(value: MyEnum): string {
+ return MY_ENUM_CONFIG[value].styles;
+}
+
+// 7. Optional: Grouping exports
+export const MY_ENUM_GROUPS = {
+ groupA: [MY_ENUM.VALUE_A, MY_ENUM.VALUE_B],
+ groupB: [MY_ENUM.VALUE_C],
+} as const;
+```
+
+## Usage in Database Schema
+
+```typescript
+// src/server/db/schema.ts
+import { pgTable, text } from "drizzle-orm/pg-core";
+import { MY_ENUM_VALUES } from "~/lib/enums/my-enum";
+
+export const myTable = pgTable("my_table", {
+ status: text("status", {
+ enum: MY_ENUM_VALUES as unknown as [string, ...string[]],
+ })
+ .notNull()
+ .default("value_a"),
+});
+```
+
+## Usage in Zod Validation
+
+```typescript
+// src/app/actions.ts
+import { z } from "zod";
+import { MY_ENUM_VALUES } from "~/lib/enums/my-enum";
+
+const mySchema = z.object({
+ status: z.enum(MY_ENUM_VALUES),
+});
+```
+
+## Usage in Components
+
+```typescript
+// src/components/MyBadge.tsx
+import { getMyEnumLabel, getMyEnumIcon, getMyEnumStyles } from "~/lib/enums/my-enum";
+import type { MyEnum } from "~/lib/types";
+import { Badge } from "~/components/ui/badge";
+
+export function MyBadge({ value }: { value: MyEnum }) {
+ const label = getMyEnumLabel(value);
+ const Icon = getMyEnumIcon(value);
+ const styles = getMyEnumStyles(value);
+
+ return (
+
+
+ {label}
+
+ );
+}
+```
+
+## Benefits
+
+1. **Single Source of Truth**: All enum metadata in one place
+2. **Type Safety**: TypeScript ensures all values have configuration
+3. **Runtime Validation**: Can validate string inputs against `ALL_MY_ENUM_VALUES`
+4. **IDE Autocomplete**: Named constants (`MY_ENUM.VALUE_A`) provide excellent DX
+5. **UI Consistency**: Styles, labels, icons centrally managed
+6. **Easy to Extend**: Add new value = add to array and config object
+7. **No Component Logic**: Components delegate to getter functions
+
+## When to Use
+
+✅ Domain enums with UI metadata (status, severity, priority, etc.)
+✅ Enums that appear in dropdowns/selects with labels
+✅ Enums with associated colors, icons, or descriptions
+✅ Enums that need grouping (e.g., "open" vs "closed" statuses)
+
+❌ Simple string unions without metadata (use plain type)
+❌ Enums that never appear in UI (use plain const)
+
+## Related Patterns
+
+- [Discriminated Union Component Props](./ui-patterns/discriminated-union-props.md)
+- [Database Schema Enums](./database-migrations.md#enum-types)
+- [Form Validation with Zod](./mutations.md#zod-validation)
+
+## Real-World Example
+
+See [src/lib/issues/status.ts](file:///home/froeht/Code/PinPoint/src/lib/issues/status.ts) for full implementation of:
+
+- `IssueStatus` (11 values, 3 groups)
+- `IssueSeverity` (4 values)
+- `IssuePriority` (3 values)
+- `IssueConsistency` (3 values)
+
+All with labels, descriptions, Tailwind styles, and Lucide icons.
diff --git a/docs/patterns/service-layer-transactions.md b/docs/patterns/service-layer-transactions.md
new file mode 100644
index 00000000..18bb5afb
--- /dev/null
+++ b/docs/patterns/service-layer-transactions.md
@@ -0,0 +1,326 @@
+# Service Layer Transaction Pattern
+
+**Status**: Established Pattern
+**Version**: 1.0
+**Last Updated**: January 10, 2026
+
+---
+
+## Overview
+
+Consistent structure for service layer functions that perform database updates with transactions, timeline events, and notifications.
+
+## Pattern Structure
+
+```typescript
+import { db } from "~/server/db";
+import { createTimelineEvent } from "~/lib/timeline/events";
+import { createNotification } from "~/lib/notifications";
+import { log } from "~/lib/logger";
+
+export async function updateSomething({
+ id,
+ newValue,
+ userId,
+}: {
+ id: string;
+ newValue: string;
+ userId: string;
+}): Promise<{ id: string; oldValue: string; newValue: string }> {
+ return await db.transaction(async (tx) => {
+ // 1. Get current state (with needed relations)
+ const current = await tx.query.myTable.findFirst({
+ where: eq(myTable.id, id),
+ columns: { value: true /* other needed fields */ },
+ with: { relatedEntity: true },
+ });
+
+ if (!current) {
+ throw new Error("Entity not found");
+ }
+
+ const oldValue = current.value;
+
+ // 2. Perform update
+ await tx
+ .update(myTable)
+ .set({
+ value: newValue,
+ updatedAt: new Date(),
+ })
+ .where(eq(myTable.id, id));
+
+ // 3. Create timeline event (inside transaction for atomicity)
+ await createTimelineEvent(
+ id,
+ `Value changed from ${oldValue} to ${newValue}`,
+ tx
+ );
+
+ // 4. Structured logging
+ log.info(
+ {
+ id,
+ oldValue,
+ newValue,
+ userId,
+ action: "updateSomething",
+ },
+ "Entity value updated"
+ );
+
+ // 5. Trigger notifications (with error handling)
+ try {
+ await createNotification(
+ {
+ type: "value_changed",
+ resourceId: id,
+ resourceType: "entity",
+ actorId: userId,
+ // ... notification metadata ...
+ },
+ tx
+ );
+ } catch (error) {
+ log.error(
+ { error, action: "updateSomething.notifications" },
+ "Failed to send notification"
+ );
+ // Don't fail the transaction if notifications fail
+ }
+
+ // 6. Return explicit result
+ return { id, oldValue, newValue };
+ });
+}
+```
+
+## Key Principles
+
+### 1. Transaction Boundaries
+
+**✅ Inside Transaction**:
+
+- Database updates
+- Timeline events (for atomicity)
+- Related entity updates
+
+**✅ Outside Transaction** (or with error handling):
+
+- External notifications (email, webhooks)
+- Non-critical side effects
+- Cache invalidation
+
+### 2. Timeline Events
+
+Always create timeline events for audit trails:
+
+```typescript
+await createTimelineEvent(
+ entityId,
+ `Human-readable description of change`,
+ tx // Pass transaction for atomicity
+);
+```
+
+### 3. Notification Error Handling
+
+Notifications should never cause transaction rollback:
+
+```typescript
+try {
+ await createNotification(/*...*/);
+} catch (error) {
+ log.error(
+ { error, action: "functionName.notifications" },
+ "Failed to send notification"
+ );
+ // Don't re-throw - let transaction complete
+}
+```
+
+### 4. Structured Logging
+
+Use consistent log structure:
+
+```typescript
+log.info(
+ {
+ entityId,
+ oldValue,
+ newValue,
+ userId,
+ action: "functionName", // Consistent naming
+ },
+ "Human-readable summary"
+);
+```
+
+### 5. Explicit Return Types
+
+Always specify return type for clarity:
+
+```typescript
+export async function updateSomething({...}): Promise<{
+ entityId: string;
+ oldValue: string;
+ newValue: string;
+}> {
+ // ...
+}
+```
+
+## Real-World Examples
+
+### Update Issue Status
+
+[src/services/issues.ts:212-299](file:///home/froeht/Code/PinPoint/src/services/issues.ts#L212-L299)
+
+```typescript
+export async function updateIssueStatus({
+ issueId,
+ status,
+ userId,
+}: UpdateIssueStatusParams): Promise<{
+ issueId: string;
+ oldStatus: string;
+ newStatus: string;
+}> {
+ return await db.transaction(async (tx) => {
+ // Get current state
+ const currentIssue = await tx.query.issues.findFirst({
+ where: eq(issues.id, issueId),
+ with: { machine: true },
+ });
+
+ if (!currentIssue) throw new Error("Issue not found");
+
+ const oldStatus = currentIssue.status;
+ const isClosed = CLOSED_STATUSES.includes(status);
+
+ // Update
+ await tx
+ .update(issues)
+ .set({
+ status,
+ updatedAt: new Date(),
+ closedAt: isClosed ? new Date() : null,
+ })
+ .where(eq(issues.id, issueId));
+
+ // Timeline event
+ const oldLabel = getIssueStatusLabel(oldStatus);
+ const newLabel = getIssueStatusLabel(status);
+ await createTimelineEvent(
+ issueId,
+ `Status changed from ${oldLabel} to ${newLabel}`,
+ tx
+ );
+
+ // Logging
+ log.info({ issueId, oldStatus, newStatus: status }, "Issue status updated");
+
+ // Notifications (with error handling)
+ try {
+ await createNotification(
+ {
+ /*...*/
+ },
+ tx
+ );
+ } catch (error) {
+ log.error({ error }, "Failed to send notification");
+ }
+
+ return { issueId, oldStatus, newStatus: status };
+ });
+}
+```
+
+### Create Issue with Auto-Watchers
+
+[src/services/issues.ts:84-206](file:///home/froeht/Code/PinPoint/src/services/issues.ts#L84-L206)
+
+Demonstrates:
+
+- Sequential number generation (atomic)
+- Multiple insert operations
+- Conditional watcher logic
+- Notification creation
+
+## Anti-Patterns
+
+### ❌ Missing Transaction
+
+```typescript
+// BAD: No transaction, non-atomic
+export async function updateValue(id, newValue) {
+ await db.update(myTable).set({ value: newValue });
+ await createTimelineEvent(id, "changed"); // Could fail after update
+}
+```
+
+### ❌ Failing on Notification Error
+
+```typescript
+// BAD: Notification failure causes rollback
+export async function updateValue(id, newValue) {
+ return await db.transaction(async (tx) => {
+ await tx.update(myTable).set({ value: newValue });
+ await createNotification({
+ /*...*/
+ }); // If this fails, update rolls back
+ });
+}
+```
+
+### ❌ Missing Logging
+
+```typescript
+// BAD: No audit trail
+export async function updateValue(id, newValue) {
+ return await db.transaction(async (tx) => {
+ await tx.update(myTable).set({ value: newValue });
+ // No log.info() - hard to debug issues
+ });
+}
+```
+
+### ❌ Implicit Return Type
+
+```typescript
+// BAD: Return type unclear
+export async function updateValue(id, newValue) {
+ return await db.transaction(async (tx) => {
+ const result = await tx.update(/*...*/).returning();
+ return result[0]; // What shape is this?
+ });
+}
+```
+
+## Checklist
+
+Use this checklist for all service layer update functions:
+
+- [ ] Wrapped in `db.transaction()`
+- [ ] Current state fetched within transaction
+- [ ] Null/error checks for entity not found
+- [ ] Update operation performed
+- [ ] Timeline event created (inside transaction)
+- [ ] Structured logging added
+- [ ] Notifications wrapped in try/catch
+- [ ] Explicit return type specified
+- [ ] Integration test written
+
+## Related Patterns
+
+- [Server Actions with Error Handling](./mutations.md#error-handling)
+- [Timeline Events](./timeline-events.md)
+- [Structured Logging](./logging.md)
+- [Integration Testing](./testing-patterns.md#integration-tests)
+
+## References
+
+- [src/services/issues.ts](file:///home/froeht/Code/PinPoint/src/services/issues.ts) - Full implementation
+- [CORE-PERF-001](file:///home/froeht/Code/PinPoint/docs/NON_NEGOTIABLES.md#performance--caching) - Cache service fetchers
+- [Drizzle Transactions Docs](https://orm.drizzle.team/docs/transactions)
diff --git a/docs/patterns/testing-patterns.md b/docs/patterns/testing-patterns.md
index 08dec826..e4db8af8 100644
--- a/docs/patterns/testing-patterns.md
+++ b/docs/patterns/testing-patterns.md
@@ -72,6 +72,99 @@ describe("Machine CRUD Operations (PGlite)", () => {
- Test database operations, not Server Components
- Integration tests in `src/test/integration/`
+### Integration Tests with Module Mocking
+
+For testing service layer functions that import `~/server/db`, use module mocking to redirect to PGlite:
+
+```typescript
+// src/test/integration/supabase/issue-services.test.ts
+import { describe, it, expect, beforeEach, vi, beforeAll } from "vitest";
+import { getTestDb, setupTestDb } from "~/test/setup/pglite";
+import { updateIssueStatus } from "~/services/issues";
+
+// Mock the database module to use PGlite instance
+vi.mock("~/server/db", () => ({
+ db: {
+ insert: vi.fn((...args: any[]) =>
+ (globalThis as any).testDb.insert(...args)
+ ),
+ update: vi.fn((...args: any[]) =>
+ (globalThis as any).testDb.update(...args)
+ ),
+ query: {
+ issues: {
+ findFirst: vi.fn((...args: any[]) =>
+ (globalThis as any).testDb.query.issues.findFirst(...args)
+ ),
+ },
+ },
+ transaction: vi.fn((cb: any) => cb((globalThis as any).testDb)),
+ },
+}));
+
+describe("Issue Service Functions", () => {
+ setupTestDb();
+
+ let testIssue: any;
+
+ beforeAll(async () => {
+ (globalThis as any).testDb = await getTestDb();
+ });
+
+ beforeEach(async () => {
+ const db = await getTestDb();
+ // Set up test data
+ const [issue] = await db
+ .insert(issues)
+ .values({
+ /*...*/
+ })
+ .returning();
+ testIssue = issue;
+ });
+
+ it("should update status and create timeline event", async () => {
+ await updateIssueStatus({
+ issueId: testIssue.id,
+ status: "in_progress",
+ userId: "test-user",
+ });
+
+ const db = await getTestDb();
+ const updated = await db.query.issues.findFirst({
+ where: eq(issues.id, testIssue.id),
+ });
+
+ expect(updated?.status).toBe("in_progress");
+
+ // Verify timeline event was created
+ const events = await db.query.issueComments.findMany({
+ where: eq(issueComments.issueId, testIssue.id),
+ });
+ expect(events.some((e) => e.content.includes("Status changed"))).toBe(true);
+ });
+});
+```
+
+**Key points**:
+
+- Mock `~/server/db` module at file level with `vi.mock()`
+- Forward all calls to `(globalThis as any).testDb`
+- Set `globalThis.testDb` in `beforeAll`
+- Allows testing actual service functions (not mocked logic)
+- Verifies transactions and side effects (timeline events, notifications)
+- **Use `any` types in mock setup** (acceptable for test infrastructure)
+
+**When to use**:
+
+✅ Testing service layer functions that import `db` from `~/server/db`
+✅ Verifying transaction behavior
+✅ Testing timeline events and notifications
+✅ Integration tests that need full database interaction
+
+❌ Don't mock individual database methods (prefer this full module mock)
+❌ Don't use for unit tests (those should test pure functions)
+
## Unit Tests
```typescript
diff --git a/docs/patterns/ui-patterns/discriminated-union-props.md b/docs/patterns/ui-patterns/discriminated-union-props.md
new file mode 100644
index 00000000..858987b3
--- /dev/null
+++ b/docs/patterns/ui-patterns/discriminated-union-props.md
@@ -0,0 +1,249 @@
+# Discriminated Union Component Props
+
+**Status**: Established Pattern
+**Version**: 1.0
+**Last Updated**: January 10, 2026
+
+---
+
+## Overview
+
+Use TypeScript discriminated unions for component props when a single component handles multiple related types. This ensures type safety by linking the `type` prop to the correct `value` type.
+
+## Pattern Structure
+
+```typescript
+// Base props shared across all variants
+type BaseProps = {
+ variant?: "normal" | "compact";
+ className?: string;
+ showTooltip?: boolean;
+};
+
+// Discriminated union for type-specific props
+type ComponentProps = BaseProps &
+ (
+ | { type: "status"; value: StatusEnum }
+ | { type: "priority"; value: PriorityEnum }
+ | { type: "severity"; value: SeverityEnum }
+ );
+
+export function MyComponent({
+ type,
+ value,
+ variant,
+ className,
+}: ComponentProps) {
+ // TypeScript knows value type based on type prop
+ // e.g., if type is "status", value must be StatusEnum
+}
+```
+
+## Real-World Example: IssueBadge
+
+[src/components/issues/IssueBadge.tsx](file:///home/froeht/Code/PinPoint/src/components/issues/IssueBadge.tsx)
+
+```typescript
+type IssueBadgeProps = {
+ variant?: "normal" | "strip";
+ className?: string;
+ showTooltip?: boolean;
+} & (
+ | { type: "status"; value: IssueStatus }
+ | { type: "severity"; value: IssueSeverity }
+ | { type: "priority"; value: IssuePriority }
+ | { type: "consistency"; value: IssueConsistency }
+);
+
+export function IssueBadge({ type, value, variant, className, showTooltip }: IssueBadgeProps) {
+ let label = "";
+ let styles = "";
+ let Icon: React.ElementType | null = null;
+
+ // Switch on discriminant to get correct config
+ switch (type) {
+ case "status":
+ label = getIssueStatusLabel(value); // value is IssueStatus
+ styles = STATUS_STYLES[value];
+ Icon = getIssueStatusIcon(value);
+ break;
+ case "severity":
+ label = getIssueSeverityLabel(value); // value is IssueSeverity
+ styles = SEVERITY_STYLES[value];
+ Icon = ISSUE_FIELD_ICONS.severity;
+ break;
+ // ... other cases
+ }
+
+ return (
+
+
+ {label}
+
+ );
+}
+```
+
+## Usage
+
+```tsx
+// ✅ Correct: Type and value match
+
+
+
+// ❌ TypeScript error: value doesn't match type
+ // Error: "major" is not IssueStatus
+ // Error: "new" is not IssueSeverity
+```
+
+## Benefits
+
+1. **Type Safety**: TypeScript enforces that `value` matches `type`
+2. **Single Component**: One component handles multiple related types
+3. **Exhaustive Checking**: Switch statements ensure all types are handled
+4. **Clear API**: Users can't pass invalid type/value combinations
+5. **Good DX**: IDE autocomplete shows correct values for each type
+
+## When to Use
+
+✅ Component renders different content based on a `type` prop
+✅ Each type requires a different value type
+✅ Types share common rendering logic (colors, icons, labels)
+✅ Types are conceptually related (all badges, all inputs, etc.)
+
+❌ Types have completely different rendering logic (use separate components)
+❌ Only one type exists (use simple props)
+❌ Types don't share any props (use separate components)
+
+## Pattern Variations
+
+### With Optional Discriminant
+
+```typescript
+type Props = {
+ value: string;
+} & (
+ | { showIcon: true; icon: LucideIcon }
+ | { showIcon?: false; icon?: never }
+);
+
+// ✅ If showIcon is true, icon is required
+
+
+// ✅ If showIcon is false/undefined, icon is not allowed
+
+```
+
+### With Nested Discriminants
+
+```typescript
+type Props =
+ | { type: "text"; value: string; placeholder?: string }
+ | { type: "number"; value: number; min?: number; max?: number }
+ | {
+ type: "select";
+ value: string;
+ options: Array<{ label: string; value: string }>;
+ };
+```
+
+## Complementary Patterns
+
+Discriminated union props work well with:
+
+- [Config-Driven Enums](./config-driven-enums.md) - Use enum configs to get metadata
+- [Pick\u003cType, Keys\u003e](./type-boundaries.md) - Narrow prop types for grid/list components
+
+## Real-World Examples
+
+### IssueBadge
+
+[src/components/issues/IssueBadge.tsx](file:///home/froeht/Code/PinPoint/src/components/issues/IssueBadge.tsx)
+
+Single component handles 4 badge types (status, severity, priority, consistency) with type-safe values.
+
+### IssueBadgeGrid
+
+[src/components/issues/IssueBadgeGrid.tsx](file:///home/froeht/Code/PinPoint/src/components/issues/IssueBadgeGrid.tsx)
+
+Uses `Pick` to accept minimal props and composes `IssueBadge` components.
+
+## Anti-Patterns
+
+### ❌ String Union Without Discrimination
+
+```typescript
+// BAD: No type safety between category and value
+type Props = {
+ category: "status" | "severity";
+ value: string; // Could be anything!
+};
+```
+
+### ❌ Separate Boolean Props
+
+```typescript
+// BAD: Can set multiple booleans to true
+type Props = {
+ isStatus?: boolean;
+ isSeverity?: boolean;
+ value: string;
+};
+```
+
+### ❌ Any Type Value
+
+```typescript
+// BAD: Loses type safety
+type Props = {
+ type: "status" | "severity";
+ value: any; // Defeats the purpose!
+};
+```
+
+## TypeScript Tips
+
+### Narrowing in Switch
+
+```typescript
+function MyComponent(props: ComponentProps) {
+ switch (props.type) {
+ case "status":
+ // TypeScript knows props.value is IssueStatus here
+ const label = getIssueStatusLabel(props.value);
+ break;
+ case "severity":
+ // TypeScript knows props.value is IssueSeverity here
+ const label = getIssueSeverityLabel(props.value);
+ break;
+ }
+}
+```
+
+### Exhaustive Checking
+
+```typescript
+function MyComponent(props: ComponentProps) {
+ switch (props.type) {
+ case "status":
+ return ;
+ case "severity":
+ return ;
+ default:
+ // TypeScript error if we miss a case
+ const exhaustive: never = props.type;
+ throw new Error(`Unhandled type: ${exhaustive}`);
+ }
+}
+```
+
+## Related Patterns
+
+- [Config-Driven Enums](./config-driven-enums.md)
+- [Type Boundaries](./type-boundaries.md)
+- [Component Composition](./ui-patterns/component-composition.md)
+
+## References
+
+- [TypeScript Handbook: Discriminated Unions](https://www.typescriptlang.org/docs/handbook/2/narrowing.html#discriminated-unions)
+- [src/components/issues/IssueBadge.tsx](file:///home/froeht/Code/PinPoint/src/components/issues/IssueBadge.tsx)
diff --git a/drizzle/0000_init-schema.sql b/drizzle/0000_initv2.sql
similarity index 67%
rename from drizzle/0000_init-schema.sql
rename to drizzle/0000_initv2.sql
index e24a2181..35e9eb74 100644
--- a/drizzle/0000_init-schema.sql
+++ b/drizzle/0000_initv2.sql
@@ -1,4 +1,3 @@
-
CREATE TABLE "issue_comments" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"issue_id" uuid NOT NULL,
@@ -22,14 +21,17 @@ CREATE TABLE "issues" (
"title" text NOT NULL,
"description" text,
"status" text DEFAULT 'new' NOT NULL,
- "severity" text DEFAULT 'playable' NOT NULL,
- "priority" text DEFAULT 'low' NOT NULL,
+ "severity" text DEFAULT 'minor' NOT NULL,
+ "priority" text DEFAULT 'medium' NOT NULL,
+ "consistency" text DEFAULT 'intermittent' NOT NULL,
"reported_by" uuid,
+ "unconfirmed_reported_by" uuid,
"assigned_to" uuid,
- "resolved_at" timestamp with time zone,
+ "closed_at" timestamp with time zone,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
- CONSTRAINT "unique_issue_number" UNIQUE("machine_initials","issue_number")
+ CONSTRAINT "unique_issue_number" UNIQUE("machine_initials","issue_number"),
+ CONSTRAINT "reporter_check" CHECK ((reported_by IS NULL OR unconfirmed_reported_by IS NULL))
);
--> statement-breakpoint
CREATE TABLE "machines" (
@@ -38,10 +40,12 @@ CREATE TABLE "machines" (
"next_issue_number" integer DEFAULT 1 NOT NULL,
"name" text NOT NULL,
"owner_id" uuid,
+ "unconfirmed_owner_id" uuid,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "machines_initials_unique" UNIQUE("initials"),
- CONSTRAINT "initials_check" CHECK (initials ~ '^[A-Z0-9]{2,6}$')
+ CONSTRAINT "initials_check" CHECK (initials ~ '^[A-Z0-9]{2,6}$'),
+ CONSTRAINT "owner_check" CHECK ((owner_id IS NULL OR unconfirmed_owner_id IS NULL))
);
--> statement-breakpoint
CREATE TABLE "notification_preferences" (
@@ -70,15 +74,29 @@ CREATE TABLE "notifications" (
"created_at" timestamp with time zone DEFAULT now() NOT NULL
);
--> statement-breakpoint
+CREATE TABLE "unconfirmed_users" (
+ "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
+ "first_name" text NOT NULL,
+ "last_name" text NOT NULL,
+ "name" text GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED NOT NULL,
+ "email" text NOT NULL,
+ "role" text DEFAULT 'guest' NOT NULL,
+ "created_at" timestamp with time zone DEFAULT now() NOT NULL,
+ "invite_sent_at" timestamp with time zone,
+ CONSTRAINT "unconfirmed_users_email_unique" UNIQUE("email")
+);
+--> statement-breakpoint
CREATE TABLE "user_profiles" (
"id" uuid PRIMARY KEY NOT NULL,
+ "email" text NOT NULL,
"first_name" text NOT NULL,
"last_name" text NOT NULL,
"name" text GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED NOT NULL,
"avatar_url" text,
"role" text DEFAULT 'member' NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
- "updated_at" timestamp with time zone DEFAULT now() NOT NULL
+ "updated_at" timestamp with time zone DEFAULT now() NOT NULL,
+ CONSTRAINT "user_profiles_email_unique" UNIQUE("email")
);
--> statement-breakpoint
ALTER TABLE "issue_comments" ADD CONSTRAINT "issue_comments_issue_id_issues_id_fk" FOREIGN KEY ("issue_id") REFERENCES "public"."issues"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
@@ -87,10 +105,21 @@ ALTER TABLE "issue_watchers" ADD CONSTRAINT "issue_watchers_issue_id_issues_id_f
ALTER TABLE "issue_watchers" ADD CONSTRAINT "issue_watchers_user_id_user_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user_profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issues" ADD CONSTRAINT "issues_machine_initials_machines_initials_fk" FOREIGN KEY ("machine_initials") REFERENCES "public"."machines"("initials") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issues" ADD CONSTRAINT "issues_reported_by_user_profiles_id_fk" FOREIGN KEY ("reported_by") REFERENCES "public"."user_profiles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "issues" ADD CONSTRAINT "issues_unconfirmed_reported_by_unconfirmed_users_id_fk" FOREIGN KEY ("unconfirmed_reported_by") REFERENCES "public"."unconfirmed_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "issues" ADD CONSTRAINT "issues_assigned_to_user_profiles_id_fk" FOREIGN KEY ("assigned_to") REFERENCES "public"."user_profiles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "machines" ADD CONSTRAINT "machines_owner_id_user_profiles_id_fk" FOREIGN KEY ("owner_id") REFERENCES "public"."user_profiles"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "machines" ADD CONSTRAINT "machines_unconfirmed_owner_id_unconfirmed_users_id_fk" FOREIGN KEY ("unconfirmed_owner_id") REFERENCES "public"."unconfirmed_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "notification_preferences" ADD CONSTRAINT "notification_preferences_user_id_user_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user_profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "notifications" ADD CONSTRAINT "notifications_user_id_user_profiles_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user_profiles"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
-CREATE INDEX "idx_issue_watchers_issue_id" ON "issue_watchers" USING btree ("issue_id");--> statement-breakpoint
+CREATE INDEX "idx_issue_comments_issue_id" ON "issue_comments" USING btree ("issue_id");--> statement-breakpoint
+CREATE INDEX "idx_issue_comments_author_id" ON "issue_comments" USING btree ("author_id");--> statement-breakpoint
+CREATE INDEX "idx_issue_watchers_user_id" ON "issue_watchers" USING btree ("user_id");--> statement-breakpoint
+CREATE INDEX "idx_issues_assigned_to" ON "issues" USING btree ("assigned_to");--> statement-breakpoint
+CREATE INDEX "idx_issues_reported_by" ON "issues" USING btree ("reported_by");--> statement-breakpoint
+CREATE INDEX "idx_issues_status" ON "issues" USING btree ("status");--> statement-breakpoint
+CREATE INDEX "idx_issues_created_at" ON "issues" USING btree ("created_at");--> statement-breakpoint
+CREATE INDEX "idx_issues_unconfirmed_reported_by" ON "issues" USING btree ("unconfirmed_reported_by");--> statement-breakpoint
+CREATE INDEX "idx_machines_owner_id" ON "machines" USING btree ("owner_id");--> statement-breakpoint
+CREATE INDEX "idx_machines_unconfirmed_owner_id" ON "machines" USING btree ("unconfirmed_owner_id");--> statement-breakpoint
CREATE INDEX "idx_notif_prefs_global_watch_email" ON "notification_preferences" USING btree ("email_watch_new_issues_global");--> statement-breakpoint
CREATE INDEX "idx_notifications_user_unread" ON "notifications" USING btree ("user_id","read_at","created_at");
diff --git a/drizzle/0001_add_performance_indexes.sql b/drizzle/0001_add_performance_indexes.sql
deleted file mode 100644
index c70896b0..00000000
--- a/drizzle/0001_add_performance_indexes.sql
+++ /dev/null
@@ -1,3 +0,0 @@
-CREATE INDEX "idx_issue_comments_issue_id" ON "issue_comments" USING btree ("issue_id");--> statement-breakpoint
-CREATE INDEX "idx_issues_assigned_to" ON "issues" USING btree ("assigned_to");--> statement-breakpoint
-CREATE INDEX "idx_issues_reported_by" ON "issues" USING btree ("reported_by");
\ No newline at end of file
diff --git a/drizzle/0002_add_more_performance_indexes.sql b/drizzle/0002_add_more_performance_indexes.sql
deleted file mode 100644
index be878c65..00000000
--- a/drizzle/0002_add_more_performance_indexes.sql
+++ /dev/null
@@ -1,2 +0,0 @@
-CREATE INDEX "idx_issues_status" ON "issues" USING btree ("status");--> statement-breakpoint
-CREATE INDEX "idx_machines_owner_id" ON "machines" USING btree ("owner_id");
\ No newline at end of file
diff --git a/drizzle/0003_add_unconfirmed_users.sql b/drizzle/0003_add_unconfirmed_users.sql
deleted file mode 100644
index c341422e..00000000
--- a/drizzle/0003_add_unconfirmed_users.sql
+++ /dev/null
@@ -1,51 +0,0 @@
-CREATE TABLE "unconfirmed_users" (
- "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
- "first_name" text NOT NULL,
- "last_name" text NOT NULL,
- "name" text GENERATED ALWAYS AS (first_name || ' ' || last_name) STORED NOT NULL,
- "email" text NOT NULL,
- "role" text DEFAULT 'guest' NOT NULL,
- "created_at" timestamp with time zone DEFAULT now() NOT NULL,
- "invite_sent_at" timestamp with time zone,
- CONSTRAINT "unconfirmed_users_email_unique" UNIQUE("email")
-);
---> statement-breakpoint
-ALTER TABLE "issues" ADD COLUMN "unconfirmed_reported_by" uuid;--> statement-breakpoint
-ALTER TABLE "machines" ADD COLUMN "unconfirmed_owner_id" uuid;--> statement-breakpoint
-ALTER TABLE "issues" ADD CONSTRAINT "issues_unconfirmed_reported_by_unconfirmed_users_id_fk" FOREIGN KEY ("unconfirmed_reported_by") REFERENCES "public"."unconfirmed_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "machines" ADD CONSTRAINT "machines_unconfirmed_owner_id_unconfirmed_users_id_fk" FOREIGN KEY ("unconfirmed_owner_id") REFERENCES "public"."unconfirmed_users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
-ALTER TABLE "issues" ADD CONSTRAINT "reporter_check" CHECK ((reported_by IS NULL OR unconfirmed_reported_by IS NULL));--> statement-breakpoint
-ALTER TABLE "machines" ADD CONSTRAINT "owner_check" CHECK ((owner_id IS NULL OR unconfirmed_owner_id IS NULL));
--- Custom trigger to auto-link machines and issues when unconfirmed user signs up
-CREATE OR REPLACE FUNCTION public.handle_unconfirmed_user_signup()
-RETURNS TRIGGER AS $$
-BEGIN
- -- Update machines where the unconfirmed user was the owner
- UPDATE public.machines
- SET
- owner_id = NEW.id,
- unconfirmed_owner_id = NULL
- WHERE unconfirmed_owner_id = (
- SELECT id FROM public.unconfirmed_users WHERE email = NEW.email
- );
-
- -- Update issues where the unconfirmed user was the reporter
- UPDATE public.issues
- SET
- reported_by = NEW.id,
- unconfirmed_reported_by = NULL
- WHERE unconfirmed_reported_by = (
- SELECT id FROM public.unconfirmed_users WHERE email = NEW.email
- );
-
- -- Delete the unconfirmed user record
- DELETE FROM public.unconfirmed_users WHERE email = NEW.email;
-
- RETURN NEW;
-END;
-$$ LANGUAGE plpgsql SECURITY DEFINER;
-
-CREATE TRIGGER on_unconfirmed_user_signup
- AFTER INSERT ON public.user_profiles
- FOR EACH ROW
- EXECUTE FUNCTION public.handle_unconfirmed_user_signup();
diff --git a/drizzle/0004_whole_kate_bishop.sql b/drizzle/0004_whole_kate_bishop.sql
deleted file mode 100644
index 5d1ba3ac..00000000
--- a/drizzle/0004_whole_kate_bishop.sql
+++ /dev/null
@@ -1,3 +0,0 @@
-DROP INDEX "idx_issue_watchers_issue_id";--> statement-breakpoint
-CREATE INDEX "idx_issue_comments_author_id" ON "issue_comments" USING btree ("author_id");--> statement-breakpoint
-CREATE INDEX "idx_issue_watchers_user_id" ON "issue_watchers" USING btree ("user_id");
\ No newline at end of file
diff --git a/drizzle/0005_add_email_to_profiles.sql b/drizzle/0005_add_email_to_profiles.sql
deleted file mode 100644
index 76dbcca9..00000000
--- a/drizzle/0005_add_email_to_profiles.sql
+++ /dev/null
@@ -1,10 +0,0 @@
--- Step 1: Add column as nullable
-ALTER TABLE "user_profiles" ADD COLUMN "email" text;
-
--- Step 2: Backfill data from auth.users
--- Note: This is an internal Supabase migration pattern. We use the join on id.
-UPDATE "user_profiles" SET "email" = (SELECT email FROM auth.users WHERE auth.users.id = "user_profiles".id);
-
--- Step 3: Add NOT NULL and UNIQUE constraints
-ALTER TABLE "user_profiles" ALTER COLUMN "email" SET NOT NULL;
-ALTER TABLE "user_profiles" ADD CONSTRAINT "user_profiles_email_unique" UNIQUE("email");
diff --git a/drizzle/0006_add_performance_indexes.sql b/drizzle/0006_add_performance_indexes.sql
deleted file mode 100644
index dc7e90d7..00000000
--- a/drizzle/0006_add_performance_indexes.sql
+++ /dev/null
@@ -1,3 +0,0 @@
-CREATE INDEX "idx_issues_created_at" ON "issues" USING btree ("created_at");--> statement-breakpoint
-CREATE INDEX "idx_issues_unconfirmed_reported_by" ON "issues" USING btree ("unconfirmed_reported_by");--> statement-breakpoint
-CREATE INDEX "idx_machines_unconfirmed_owner_id" ON "machines" USING btree ("unconfirmed_owner_id");
\ No newline at end of file
diff --git a/drizzle/meta/0000_snapshot.json b/drizzle/meta/0000_snapshot.json
index def921aa..06564589 100644
--- a/drizzle/meta/0000_snapshot.json
+++ b/drizzle/meta/0000_snapshot.json
@@ -1,5 +1,5 @@
{
- "id": "37e0cbd4-92a2-4504-9431-7979c4fe6eda",
+ "id": "9419c8f2-2c0d-41ab-ac7c-16230472cec9",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
@@ -80,7 +80,38 @@
"default": "now()"
}
},
- "indexes": {},
+ "indexes": {
+ "idx_issue_comments_issue_id": {
+ "name": "idx_issue_comments_issue_id",
+ "columns": [
+ {
+ "expression": "issue_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_issue_comments_author_id": {
+ "name": "idx_issue_comments_author_id",
+ "columns": [
+ {
+ "expression": "author_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
"foreignKeys": {
"issue_comments_issue_id_issues_id_fk": {
"name": "issue_comments_issue_id_issues_id_fk",
@@ -125,11 +156,11 @@
}
},
"indexes": {
- "idx_issue_watchers_issue_id": {
- "name": "idx_issue_watchers_issue_id",
+ "idx_issue_watchers_user_id": {
+ "name": "idx_issue_watchers_user_id",
"columns": [
{
- "expression": "issue_id",
+ "expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
@@ -219,14 +250,21 @@
"type": "text",
"primaryKey": false,
"notNull": true,
- "default": "'playable'"
+ "default": "'minor'"
},
"priority": {
"name": "priority",
"type": "text",
"primaryKey": false,
"notNull": true,
- "default": "'low'"
+ "default": "'medium'"
+ },
+ "consistency": {
+ "name": "consistency",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'intermittent'"
},
"reported_by": {
"name": "reported_by",
@@ -234,14 +272,20 @@
"primaryKey": false,
"notNull": false
},
+ "unconfirmed_reported_by": {
+ "name": "unconfirmed_reported_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
"assigned_to": {
"name": "assigned_to",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
- "resolved_at": {
- "name": "resolved_at",
+ "closed_at": {
+ "name": "closed_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
@@ -261,7 +305,83 @@
"default": "now()"
}
},
- "indexes": {},
+ "indexes": {
+ "idx_issues_assigned_to": {
+ "name": "idx_issues_assigned_to",
+ "columns": [
+ {
+ "expression": "assigned_to",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_issues_reported_by": {
+ "name": "idx_issues_reported_by",
+ "columns": [
+ {
+ "expression": "reported_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_issues_status": {
+ "name": "idx_issues_status",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_issues_created_at": {
+ "name": "idx_issues_created_at",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_issues_unconfirmed_reported_by": {
+ "name": "idx_issues_unconfirmed_reported_by",
+ "columns": [
+ {
+ "expression": "unconfirmed_reported_by",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
"foreignKeys": {
"issues_machine_initials_machines_initials_fk": {
"name": "issues_machine_initials_machines_initials_fk",
@@ -281,6 +401,15 @@
"onDelete": "no action",
"onUpdate": "no action"
},
+ "issues_unconfirmed_reported_by_unconfirmed_users_id_fk": {
+ "name": "issues_unconfirmed_reported_by_unconfirmed_users_id_fk",
+ "tableFrom": "issues",
+ "tableTo": "unconfirmed_users",
+ "columnsFrom": ["unconfirmed_reported_by"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
"issues_assigned_to_user_profiles_id_fk": {
"name": "issues_assigned_to_user_profiles_id_fk",
"tableFrom": "issues",
@@ -300,7 +429,12 @@
}
},
"policies": {},
- "checkConstraints": {},
+ "checkConstraints": {
+ "reporter_check": {
+ "name": "reporter_check",
+ "value": "(reported_by IS NULL OR unconfirmed_reported_by IS NULL)"
+ }
+ },
"isRLSEnabled": false
},
"public.machines": {
@@ -339,6 +473,12 @@
"primaryKey": false,
"notNull": false
},
+ "unconfirmed_owner_id": {
+ "name": "unconfirmed_owner_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
@@ -354,7 +494,38 @@
"default": "now()"
}
},
- "indexes": {},
+ "indexes": {
+ "idx_machines_owner_id": {
+ "name": "idx_machines_owner_id",
+ "columns": [
+ {
+ "expression": "owner_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "idx_machines_unconfirmed_owner_id": {
+ "name": "idx_machines_unconfirmed_owner_id",
+ "columns": [
+ {
+ "expression": "unconfirmed_owner_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
"foreignKeys": {
"machines_owner_id_user_profiles_id_fk": {
"name": "machines_owner_id_user_profiles_id_fk",
@@ -364,6 +535,15 @@
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
+ },
+ "machines_unconfirmed_owner_id_unconfirmed_users_id_fk": {
+ "name": "machines_unconfirmed_owner_id_unconfirmed_users_id_fk",
+ "tableFrom": "machines",
+ "tableTo": "unconfirmed_users",
+ "columnsFrom": ["unconfirmed_owner_id"],
+ "columnsTo": ["id"],
+ "onDelete": "no action",
+ "onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
@@ -379,6 +559,10 @@
"initials_check": {
"name": "initials_check",
"value": "initials ~ '^[A-Z0-9]{2,6}$'"
+ },
+ "owner_check": {
+ "name": "owner_check",
+ "value": "(owner_id IS NULL OR unconfirmed_owner_id IS NULL)"
}
},
"isRLSEnabled": false
@@ -607,6 +791,80 @@
"checkConstraints": {},
"isRLSEnabled": false
},
+ "public.unconfirmed_users": {
+ "name": "unconfirmed_users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "generated": {
+ "as": "first_name || ' ' || last_name",
+ "type": "stored"
+ }
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'guest'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "invite_sent_at": {
+ "name": "invite_sent_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "unconfirmed_users_email_unique": {
+ "name": "unconfirmed_users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
"public.user_profiles": {
"name": "user_profiles",
"schema": "",
@@ -617,6 +875,12 @@
"primaryKey": true,
"notNull": true
},
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
"first_name": {
"name": "first_name",
"type": "text",
@@ -670,7 +934,13 @@
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
- "uniqueConstraints": {},
+ "uniqueConstraints": {
+ "user_profiles_email_unique": {
+ "name": "user_profiles_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json
deleted file mode 100644
index 285e0357..00000000
--- a/drizzle/meta/0001_snapshot.json
+++ /dev/null
@@ -1,737 +0,0 @@
-{
- "id": "277d032f-4a11-401a-a0cf-56f0e64df34f",
- "prevId": "37e0cbd4-92a2-4504-9431-7979c4fe6eda",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "auth.users": {
- "name": "users",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_comments": {
- "name": "issue_comments",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "author_id": {
- "name": "author_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "content": {
- "name": "content",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "is_system": {
- "name": "is_system",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issue_comments_issue_id": {
- "name": "idx_issue_comments_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_comments_issue_id_issues_id_fk": {
- "name": "issue_comments_issue_id_issues_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_comments_author_id_user_profiles_id_fk": {
- "name": "issue_comments_author_id_user_profiles_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "user_profiles",
- "columnsFrom": ["author_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_watchers": {
- "name": "issue_watchers",
- "schema": "",
- "columns": {
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {
- "idx_issue_watchers_issue_id": {
- "name": "idx_issue_watchers_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_watchers_issue_id_issues_id_fk": {
- "name": "issue_watchers_issue_id_issues_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_watchers_user_id_user_profiles_id_fk": {
- "name": "issue_watchers_user_id_user_profiles_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "issue_watchers_issue_id_user_id_pk": {
- "name": "issue_watchers_issue_id_user_id_pk",
- "columns": ["issue_id", "user_id"]
- }
- },
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issues": {
- "name": "issues",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "machine_initials": {
- "name": "machine_initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "issue_number": {
- "name": "issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "status": {
- "name": "status",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'new'"
- },
- "severity": {
- "name": "severity",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'playable'"
- },
- "priority": {
- "name": "priority",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'low'"
- },
- "reported_by": {
- "name": "reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "assigned_to": {
- "name": "assigned_to",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "resolved_at": {
- "name": "resolved_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issues_assigned_to": {
- "name": "idx_issues_assigned_to",
- "columns": [
- {
- "expression": "assigned_to",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_reported_by": {
- "name": "idx_issues_reported_by",
- "columns": [
- {
- "expression": "reported_by",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issues_machine_initials_machines_initials_fk": {
- "name": "issues_machine_initials_machines_initials_fk",
- "tableFrom": "issues",
- "tableTo": "machines",
- "columnsFrom": ["machine_initials"],
- "columnsTo": ["initials"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issues_reported_by_user_profiles_id_fk": {
- "name": "issues_reported_by_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_assigned_to_user_profiles_id_fk": {
- "name": "issues_assigned_to_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["assigned_to"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unique_issue_number": {
- "name": "unique_issue_number",
- "nullsNotDistinct": false,
- "columns": ["machine_initials", "issue_number"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.machines": {
- "name": "machines",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "initials": {
- "name": "initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "next_issue_number": {
- "name": "next_issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 1
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "owner_id": {
- "name": "owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {
- "machines_owner_id_user_profiles_id_fk": {
- "name": "machines_owner_id_user_profiles_id_fk",
- "tableFrom": "machines",
- "tableTo": "user_profiles",
- "columnsFrom": ["owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "machines_initials_unique": {
- "name": "machines_initials_unique",
- "nullsNotDistinct": false,
- "columns": ["initials"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "initials_check": {
- "name": "initials_check",
- "value": "initials ~ '^[A-Z0-9]{2,6}$'"
- }
- },
- "isRLSEnabled": false
- },
- "public.notification_preferences": {
- "name": "notification_preferences",
- "schema": "",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email_enabled": {
- "name": "email_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_enabled": {
- "name": "in_app_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_assigned": {
- "name": "email_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_assigned": {
- "name": "in_app_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_status_change": {
- "name": "email_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_status_change": {
- "name": "in_app_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_comment": {
- "name": "email_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_comment": {
- "name": "in_app_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_issue": {
- "name": "email_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_issue": {
- "name": "in_app_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_watch_new_issues_global": {
- "name": "email_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "in_app_watch_new_issues_global": {
- "name": "in_app_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- }
- },
- "indexes": {
- "idx_notif_prefs_global_watch_email": {
- "name": "idx_notif_prefs_global_watch_email",
- "columns": [
- {
- "expression": "email_watch_new_issues_global",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notification_preferences_user_id_user_profiles_id_fk": {
- "name": "notification_preferences_user_id_user_profiles_id_fk",
- "tableFrom": "notification_preferences",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.notifications": {
- "name": "notifications",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "resource_id": {
- "name": "resource_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "resource_type": {
- "name": "resource_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "read_at": {
- "name": "read_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_notifications_user_unread": {
- "name": "idx_notifications_user_unread",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "read_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notifications_user_id_user_profiles_id_fk": {
- "name": "notifications_user_id_user_profiles_id_fk",
- "tableFrom": "notifications",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.user_profiles": {
- "name": "user_profiles",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "avatar_url": {
- "name": "avatar_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'member'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {},
- "schemas": {},
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-}
diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json
deleted file mode 100644
index c550ffc8..00000000
--- a/drizzle/meta/0002_snapshot.json
+++ /dev/null
@@ -1,768 +0,0 @@
-{
- "id": "75cbbb20-95fa-4137-a951-fdd6c7008ea6",
- "prevId": "277d032f-4a11-401a-a0cf-56f0e64df34f",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "auth.users": {
- "name": "users",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_comments": {
- "name": "issue_comments",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "author_id": {
- "name": "author_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "content": {
- "name": "content",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "is_system": {
- "name": "is_system",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issue_comments_issue_id": {
- "name": "idx_issue_comments_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_comments_issue_id_issues_id_fk": {
- "name": "issue_comments_issue_id_issues_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_comments_author_id_user_profiles_id_fk": {
- "name": "issue_comments_author_id_user_profiles_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "user_profiles",
- "columnsFrom": ["author_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_watchers": {
- "name": "issue_watchers",
- "schema": "",
- "columns": {
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {
- "idx_issue_watchers_issue_id": {
- "name": "idx_issue_watchers_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_watchers_issue_id_issues_id_fk": {
- "name": "issue_watchers_issue_id_issues_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_watchers_user_id_user_profiles_id_fk": {
- "name": "issue_watchers_user_id_user_profiles_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "issue_watchers_issue_id_user_id_pk": {
- "name": "issue_watchers_issue_id_user_id_pk",
- "columns": ["issue_id", "user_id"]
- }
- },
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issues": {
- "name": "issues",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "machine_initials": {
- "name": "machine_initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "issue_number": {
- "name": "issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "status": {
- "name": "status",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'new'"
- },
- "severity": {
- "name": "severity",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'playable'"
- },
- "priority": {
- "name": "priority",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'low'"
- },
- "reported_by": {
- "name": "reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "assigned_to": {
- "name": "assigned_to",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "resolved_at": {
- "name": "resolved_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issues_assigned_to": {
- "name": "idx_issues_assigned_to",
- "columns": [
- {
- "expression": "assigned_to",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_reported_by": {
- "name": "idx_issues_reported_by",
- "columns": [
- {
- "expression": "reported_by",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_status": {
- "name": "idx_issues_status",
- "columns": [
- {
- "expression": "status",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issues_machine_initials_machines_initials_fk": {
- "name": "issues_machine_initials_machines_initials_fk",
- "tableFrom": "issues",
- "tableTo": "machines",
- "columnsFrom": ["machine_initials"],
- "columnsTo": ["initials"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issues_reported_by_user_profiles_id_fk": {
- "name": "issues_reported_by_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_assigned_to_user_profiles_id_fk": {
- "name": "issues_assigned_to_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["assigned_to"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unique_issue_number": {
- "name": "unique_issue_number",
- "nullsNotDistinct": false,
- "columns": ["machine_initials", "issue_number"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.machines": {
- "name": "machines",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "initials": {
- "name": "initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "next_issue_number": {
- "name": "next_issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 1
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "owner_id": {
- "name": "owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_machines_owner_id": {
- "name": "idx_machines_owner_id",
- "columns": [
- {
- "expression": "owner_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "machines_owner_id_user_profiles_id_fk": {
- "name": "machines_owner_id_user_profiles_id_fk",
- "tableFrom": "machines",
- "tableTo": "user_profiles",
- "columnsFrom": ["owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "machines_initials_unique": {
- "name": "machines_initials_unique",
- "nullsNotDistinct": false,
- "columns": ["initials"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "initials_check": {
- "name": "initials_check",
- "value": "initials ~ '^[A-Z0-9]{2,6}$'"
- }
- },
- "isRLSEnabled": false
- },
- "public.notification_preferences": {
- "name": "notification_preferences",
- "schema": "",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email_enabled": {
- "name": "email_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_enabled": {
- "name": "in_app_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_assigned": {
- "name": "email_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_assigned": {
- "name": "in_app_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_status_change": {
- "name": "email_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_status_change": {
- "name": "in_app_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_comment": {
- "name": "email_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_comment": {
- "name": "in_app_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_issue": {
- "name": "email_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_issue": {
- "name": "in_app_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_watch_new_issues_global": {
- "name": "email_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "in_app_watch_new_issues_global": {
- "name": "in_app_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- }
- },
- "indexes": {
- "idx_notif_prefs_global_watch_email": {
- "name": "idx_notif_prefs_global_watch_email",
- "columns": [
- {
- "expression": "email_watch_new_issues_global",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notification_preferences_user_id_user_profiles_id_fk": {
- "name": "notification_preferences_user_id_user_profiles_id_fk",
- "tableFrom": "notification_preferences",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.notifications": {
- "name": "notifications",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "resource_id": {
- "name": "resource_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "resource_type": {
- "name": "resource_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "read_at": {
- "name": "read_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_notifications_user_unread": {
- "name": "idx_notifications_user_unread",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "read_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notifications_user_id_user_profiles_id_fk": {
- "name": "notifications_user_id_user_profiles_id_fk",
- "tableFrom": "notifications",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.user_profiles": {
- "name": "user_profiles",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "avatar_url": {
- "name": "avatar_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'member'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {},
- "schemas": {},
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-}
diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json
deleted file mode 100644
index d8f3d0b7..00000000
--- a/drizzle/meta/0003_snapshot.json
+++ /dev/null
@@ -1,881 +0,0 @@
-{
- "id": "05bed52c-fdef-484f-bf20-73d4cc0108fa",
- "prevId": "75cbbb20-95fa-4137-a951-fdd6c7008ea6",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "auth.users": {
- "name": "users",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_comments": {
- "name": "issue_comments",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "author_id": {
- "name": "author_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "content": {
- "name": "content",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "is_system": {
- "name": "is_system",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issue_comments_issue_id": {
- "name": "idx_issue_comments_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_comments_issue_id_issues_id_fk": {
- "name": "issue_comments_issue_id_issues_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_comments_author_id_user_profiles_id_fk": {
- "name": "issue_comments_author_id_user_profiles_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "user_profiles",
- "columnsFrom": ["author_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_watchers": {
- "name": "issue_watchers",
- "schema": "",
- "columns": {
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {
- "idx_issue_watchers_issue_id": {
- "name": "idx_issue_watchers_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_watchers_issue_id_issues_id_fk": {
- "name": "issue_watchers_issue_id_issues_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_watchers_user_id_user_profiles_id_fk": {
- "name": "issue_watchers_user_id_user_profiles_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "issue_watchers_issue_id_user_id_pk": {
- "name": "issue_watchers_issue_id_user_id_pk",
- "columns": ["issue_id", "user_id"]
- }
- },
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issues": {
- "name": "issues",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "machine_initials": {
- "name": "machine_initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "issue_number": {
- "name": "issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "status": {
- "name": "status",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'new'"
- },
- "severity": {
- "name": "severity",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'playable'"
- },
- "priority": {
- "name": "priority",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'low'"
- },
- "reported_by": {
- "name": "reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "unconfirmed_reported_by": {
- "name": "unconfirmed_reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "assigned_to": {
- "name": "assigned_to",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "resolved_at": {
- "name": "resolved_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issues_assigned_to": {
- "name": "idx_issues_assigned_to",
- "columns": [
- {
- "expression": "assigned_to",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_reported_by": {
- "name": "idx_issues_reported_by",
- "columns": [
- {
- "expression": "reported_by",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_status": {
- "name": "idx_issues_status",
- "columns": [
- {
- "expression": "status",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issues_machine_initials_machines_initials_fk": {
- "name": "issues_machine_initials_machines_initials_fk",
- "tableFrom": "issues",
- "tableTo": "machines",
- "columnsFrom": ["machine_initials"],
- "columnsTo": ["initials"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issues_reported_by_user_profiles_id_fk": {
- "name": "issues_reported_by_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_unconfirmed_reported_by_unconfirmed_users_id_fk": {
- "name": "issues_unconfirmed_reported_by_unconfirmed_users_id_fk",
- "tableFrom": "issues",
- "tableTo": "unconfirmed_users",
- "columnsFrom": ["unconfirmed_reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_assigned_to_user_profiles_id_fk": {
- "name": "issues_assigned_to_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["assigned_to"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unique_issue_number": {
- "name": "unique_issue_number",
- "nullsNotDistinct": false,
- "columns": ["machine_initials", "issue_number"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "reporter_check": {
- "name": "reporter_check",
- "value": "(reported_by IS NULL OR unconfirmed_reported_by IS NULL)"
- }
- },
- "isRLSEnabled": false
- },
- "public.machines": {
- "name": "machines",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "initials": {
- "name": "initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "next_issue_number": {
- "name": "next_issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 1
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "owner_id": {
- "name": "owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "unconfirmed_owner_id": {
- "name": "unconfirmed_owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_machines_owner_id": {
- "name": "idx_machines_owner_id",
- "columns": [
- {
- "expression": "owner_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "machines_owner_id_user_profiles_id_fk": {
- "name": "machines_owner_id_user_profiles_id_fk",
- "tableFrom": "machines",
- "tableTo": "user_profiles",
- "columnsFrom": ["owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "machines_unconfirmed_owner_id_unconfirmed_users_id_fk": {
- "name": "machines_unconfirmed_owner_id_unconfirmed_users_id_fk",
- "tableFrom": "machines",
- "tableTo": "unconfirmed_users",
- "columnsFrom": ["unconfirmed_owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "machines_initials_unique": {
- "name": "machines_initials_unique",
- "nullsNotDistinct": false,
- "columns": ["initials"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "initials_check": {
- "name": "initials_check",
- "value": "initials ~ '^[A-Z0-9]{2,6}$'"
- },
- "owner_check": {
- "name": "owner_check",
- "value": "(owner_id IS NULL OR unconfirmed_owner_id IS NULL)"
- }
- },
- "isRLSEnabled": false
- },
- "public.notification_preferences": {
- "name": "notification_preferences",
- "schema": "",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email_enabled": {
- "name": "email_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_enabled": {
- "name": "in_app_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_assigned": {
- "name": "email_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_assigned": {
- "name": "in_app_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_status_change": {
- "name": "email_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_status_change": {
- "name": "in_app_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_comment": {
- "name": "email_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_comment": {
- "name": "in_app_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_issue": {
- "name": "email_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_issue": {
- "name": "in_app_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_watch_new_issues_global": {
- "name": "email_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "in_app_watch_new_issues_global": {
- "name": "in_app_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- }
- },
- "indexes": {
- "idx_notif_prefs_global_watch_email": {
- "name": "idx_notif_prefs_global_watch_email",
- "columns": [
- {
- "expression": "email_watch_new_issues_global",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notification_preferences_user_id_user_profiles_id_fk": {
- "name": "notification_preferences_user_id_user_profiles_id_fk",
- "tableFrom": "notification_preferences",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.notifications": {
- "name": "notifications",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "resource_id": {
- "name": "resource_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "resource_type": {
- "name": "resource_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "read_at": {
- "name": "read_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_notifications_user_unread": {
- "name": "idx_notifications_user_unread",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "read_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notifications_user_id_user_profiles_id_fk": {
- "name": "notifications_user_id_user_profiles_id_fk",
- "tableFrom": "notifications",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.unconfirmed_users": {
- "name": "unconfirmed_users",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'guest'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "invite_sent_at": {
- "name": "invite_sent_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unconfirmed_users_email_unique": {
- "name": "unconfirmed_users_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.user_profiles": {
- "name": "user_profiles",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "avatar_url": {
- "name": "avatar_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'member'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {},
- "schemas": {},
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-}
diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json
deleted file mode 100644
index 45d456f0..00000000
--- a/drizzle/meta/0004_snapshot.json
+++ /dev/null
@@ -1,896 +0,0 @@
-{
- "id": "1cdfad92-de66-436c-a84c-15c7366f6d45",
- "prevId": "05bed52c-fdef-484f-bf20-73d4cc0108fa",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "auth.users": {
- "name": "users",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_comments": {
- "name": "issue_comments",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "author_id": {
- "name": "author_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "content": {
- "name": "content",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "is_system": {
- "name": "is_system",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issue_comments_issue_id": {
- "name": "idx_issue_comments_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issue_comments_author_id": {
- "name": "idx_issue_comments_author_id",
- "columns": [
- {
- "expression": "author_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_comments_issue_id_issues_id_fk": {
- "name": "issue_comments_issue_id_issues_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_comments_author_id_user_profiles_id_fk": {
- "name": "issue_comments_author_id_user_profiles_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "user_profiles",
- "columnsFrom": ["author_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_watchers": {
- "name": "issue_watchers",
- "schema": "",
- "columns": {
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {
- "idx_issue_watchers_user_id": {
- "name": "idx_issue_watchers_user_id",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_watchers_issue_id_issues_id_fk": {
- "name": "issue_watchers_issue_id_issues_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_watchers_user_id_user_profiles_id_fk": {
- "name": "issue_watchers_user_id_user_profiles_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "issue_watchers_issue_id_user_id_pk": {
- "name": "issue_watchers_issue_id_user_id_pk",
- "columns": ["issue_id", "user_id"]
- }
- },
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issues": {
- "name": "issues",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "machine_initials": {
- "name": "machine_initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "issue_number": {
- "name": "issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "status": {
- "name": "status",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'new'"
- },
- "severity": {
- "name": "severity",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'playable'"
- },
- "priority": {
- "name": "priority",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'low'"
- },
- "reported_by": {
- "name": "reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "unconfirmed_reported_by": {
- "name": "unconfirmed_reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "assigned_to": {
- "name": "assigned_to",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "resolved_at": {
- "name": "resolved_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issues_assigned_to": {
- "name": "idx_issues_assigned_to",
- "columns": [
- {
- "expression": "assigned_to",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_reported_by": {
- "name": "idx_issues_reported_by",
- "columns": [
- {
- "expression": "reported_by",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_status": {
- "name": "idx_issues_status",
- "columns": [
- {
- "expression": "status",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issues_machine_initials_machines_initials_fk": {
- "name": "issues_machine_initials_machines_initials_fk",
- "tableFrom": "issues",
- "tableTo": "machines",
- "columnsFrom": ["machine_initials"],
- "columnsTo": ["initials"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issues_reported_by_user_profiles_id_fk": {
- "name": "issues_reported_by_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_unconfirmed_reported_by_unconfirmed_users_id_fk": {
- "name": "issues_unconfirmed_reported_by_unconfirmed_users_id_fk",
- "tableFrom": "issues",
- "tableTo": "unconfirmed_users",
- "columnsFrom": ["unconfirmed_reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_assigned_to_user_profiles_id_fk": {
- "name": "issues_assigned_to_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["assigned_to"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unique_issue_number": {
- "name": "unique_issue_number",
- "nullsNotDistinct": false,
- "columns": ["machine_initials", "issue_number"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "reporter_check": {
- "name": "reporter_check",
- "value": "(reported_by IS NULL OR unconfirmed_reported_by IS NULL)"
- }
- },
- "isRLSEnabled": false
- },
- "public.machines": {
- "name": "machines",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "initials": {
- "name": "initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "next_issue_number": {
- "name": "next_issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 1
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "owner_id": {
- "name": "owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "unconfirmed_owner_id": {
- "name": "unconfirmed_owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_machines_owner_id": {
- "name": "idx_machines_owner_id",
- "columns": [
- {
- "expression": "owner_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "machines_owner_id_user_profiles_id_fk": {
- "name": "machines_owner_id_user_profiles_id_fk",
- "tableFrom": "machines",
- "tableTo": "user_profiles",
- "columnsFrom": ["owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "machines_unconfirmed_owner_id_unconfirmed_users_id_fk": {
- "name": "machines_unconfirmed_owner_id_unconfirmed_users_id_fk",
- "tableFrom": "machines",
- "tableTo": "unconfirmed_users",
- "columnsFrom": ["unconfirmed_owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "machines_initials_unique": {
- "name": "machines_initials_unique",
- "nullsNotDistinct": false,
- "columns": ["initials"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "initials_check": {
- "name": "initials_check",
- "value": "initials ~ '^[A-Z0-9]{2,6}$'"
- },
- "owner_check": {
- "name": "owner_check",
- "value": "(owner_id IS NULL OR unconfirmed_owner_id IS NULL)"
- }
- },
- "isRLSEnabled": false
- },
- "public.notification_preferences": {
- "name": "notification_preferences",
- "schema": "",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email_enabled": {
- "name": "email_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_enabled": {
- "name": "in_app_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_assigned": {
- "name": "email_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_assigned": {
- "name": "in_app_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_status_change": {
- "name": "email_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_status_change": {
- "name": "in_app_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_comment": {
- "name": "email_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_comment": {
- "name": "in_app_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_issue": {
- "name": "email_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_issue": {
- "name": "in_app_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_watch_new_issues_global": {
- "name": "email_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "in_app_watch_new_issues_global": {
- "name": "in_app_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- }
- },
- "indexes": {
- "idx_notif_prefs_global_watch_email": {
- "name": "idx_notif_prefs_global_watch_email",
- "columns": [
- {
- "expression": "email_watch_new_issues_global",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notification_preferences_user_id_user_profiles_id_fk": {
- "name": "notification_preferences_user_id_user_profiles_id_fk",
- "tableFrom": "notification_preferences",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.notifications": {
- "name": "notifications",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "resource_id": {
- "name": "resource_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "resource_type": {
- "name": "resource_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "read_at": {
- "name": "read_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_notifications_user_unread": {
- "name": "idx_notifications_user_unread",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "read_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notifications_user_id_user_profiles_id_fk": {
- "name": "notifications_user_id_user_profiles_id_fk",
- "tableFrom": "notifications",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.unconfirmed_users": {
- "name": "unconfirmed_users",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'guest'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "invite_sent_at": {
- "name": "invite_sent_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unconfirmed_users_email_unique": {
- "name": "unconfirmed_users_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.user_profiles": {
- "name": "user_profiles",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "avatar_url": {
- "name": "avatar_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'member'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {},
- "schemas": {},
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-}
diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json
deleted file mode 100644
index e2cd3574..00000000
--- a/drizzle/meta/0005_snapshot.json
+++ /dev/null
@@ -1,908 +0,0 @@
-{
- "id": "5dc5c966-cf77-418e-9fc8-cdfadc8eb47f",
- "prevId": "1cdfad92-de66-436c-a84c-15c7366f6d45",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "auth.users": {
- "name": "users",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_comments": {
- "name": "issue_comments",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "author_id": {
- "name": "author_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "content": {
- "name": "content",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "is_system": {
- "name": "is_system",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issue_comments_issue_id": {
- "name": "idx_issue_comments_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issue_comments_author_id": {
- "name": "idx_issue_comments_author_id",
- "columns": [
- {
- "expression": "author_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_comments_issue_id_issues_id_fk": {
- "name": "issue_comments_issue_id_issues_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_comments_author_id_user_profiles_id_fk": {
- "name": "issue_comments_author_id_user_profiles_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "user_profiles",
- "columnsFrom": ["author_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_watchers": {
- "name": "issue_watchers",
- "schema": "",
- "columns": {
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {
- "idx_issue_watchers_user_id": {
- "name": "idx_issue_watchers_user_id",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_watchers_issue_id_issues_id_fk": {
- "name": "issue_watchers_issue_id_issues_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_watchers_user_id_user_profiles_id_fk": {
- "name": "issue_watchers_user_id_user_profiles_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "issue_watchers_issue_id_user_id_pk": {
- "name": "issue_watchers_issue_id_user_id_pk",
- "columns": ["issue_id", "user_id"]
- }
- },
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issues": {
- "name": "issues",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "machine_initials": {
- "name": "machine_initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "issue_number": {
- "name": "issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "status": {
- "name": "status",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'new'"
- },
- "severity": {
- "name": "severity",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'playable'"
- },
- "priority": {
- "name": "priority",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'low'"
- },
- "reported_by": {
- "name": "reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "unconfirmed_reported_by": {
- "name": "unconfirmed_reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "assigned_to": {
- "name": "assigned_to",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "resolved_at": {
- "name": "resolved_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issues_assigned_to": {
- "name": "idx_issues_assigned_to",
- "columns": [
- {
- "expression": "assigned_to",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_reported_by": {
- "name": "idx_issues_reported_by",
- "columns": [
- {
- "expression": "reported_by",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_status": {
- "name": "idx_issues_status",
- "columns": [
- {
- "expression": "status",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issues_machine_initials_machines_initials_fk": {
- "name": "issues_machine_initials_machines_initials_fk",
- "tableFrom": "issues",
- "tableTo": "machines",
- "columnsFrom": ["machine_initials"],
- "columnsTo": ["initials"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issues_reported_by_user_profiles_id_fk": {
- "name": "issues_reported_by_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_unconfirmed_reported_by_unconfirmed_users_id_fk": {
- "name": "issues_unconfirmed_reported_by_unconfirmed_users_id_fk",
- "tableFrom": "issues",
- "tableTo": "unconfirmed_users",
- "columnsFrom": ["unconfirmed_reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_assigned_to_user_profiles_id_fk": {
- "name": "issues_assigned_to_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["assigned_to"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unique_issue_number": {
- "name": "unique_issue_number",
- "nullsNotDistinct": false,
- "columns": ["machine_initials", "issue_number"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "reporter_check": {
- "name": "reporter_check",
- "value": "(reported_by IS NULL OR unconfirmed_reported_by IS NULL)"
- }
- },
- "isRLSEnabled": false
- },
- "public.machines": {
- "name": "machines",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "initials": {
- "name": "initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "next_issue_number": {
- "name": "next_issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 1
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "owner_id": {
- "name": "owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "unconfirmed_owner_id": {
- "name": "unconfirmed_owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_machines_owner_id": {
- "name": "idx_machines_owner_id",
- "columns": [
- {
- "expression": "owner_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "machines_owner_id_user_profiles_id_fk": {
- "name": "machines_owner_id_user_profiles_id_fk",
- "tableFrom": "machines",
- "tableTo": "user_profiles",
- "columnsFrom": ["owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "machines_unconfirmed_owner_id_unconfirmed_users_id_fk": {
- "name": "machines_unconfirmed_owner_id_unconfirmed_users_id_fk",
- "tableFrom": "machines",
- "tableTo": "unconfirmed_users",
- "columnsFrom": ["unconfirmed_owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "machines_initials_unique": {
- "name": "machines_initials_unique",
- "nullsNotDistinct": false,
- "columns": ["initials"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "initials_check": {
- "name": "initials_check",
- "value": "initials ~ '^[A-Z0-9]{2,6}$'"
- },
- "owner_check": {
- "name": "owner_check",
- "value": "(owner_id IS NULL OR unconfirmed_owner_id IS NULL)"
- }
- },
- "isRLSEnabled": false
- },
- "public.notification_preferences": {
- "name": "notification_preferences",
- "schema": "",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email_enabled": {
- "name": "email_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_enabled": {
- "name": "in_app_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_assigned": {
- "name": "email_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_assigned": {
- "name": "in_app_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_status_change": {
- "name": "email_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_status_change": {
- "name": "in_app_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_comment": {
- "name": "email_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_comment": {
- "name": "in_app_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_issue": {
- "name": "email_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_issue": {
- "name": "in_app_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_watch_new_issues_global": {
- "name": "email_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "in_app_watch_new_issues_global": {
- "name": "in_app_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- }
- },
- "indexes": {
- "idx_notif_prefs_global_watch_email": {
- "name": "idx_notif_prefs_global_watch_email",
- "columns": [
- {
- "expression": "email_watch_new_issues_global",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notification_preferences_user_id_user_profiles_id_fk": {
- "name": "notification_preferences_user_id_user_profiles_id_fk",
- "tableFrom": "notification_preferences",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.notifications": {
- "name": "notifications",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "resource_id": {
- "name": "resource_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "resource_type": {
- "name": "resource_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "read_at": {
- "name": "read_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_notifications_user_unread": {
- "name": "idx_notifications_user_unread",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "read_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notifications_user_id_user_profiles_id_fk": {
- "name": "notifications_user_id_user_profiles_id_fk",
- "tableFrom": "notifications",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.unconfirmed_users": {
- "name": "unconfirmed_users",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'guest'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "invite_sent_at": {
- "name": "invite_sent_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unconfirmed_users_email_unique": {
- "name": "unconfirmed_users_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.user_profiles": {
- "name": "user_profiles",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "avatar_url": {
- "name": "avatar_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'member'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "user_profiles_email_unique": {
- "name": "user_profiles_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {},
- "schemas": {},
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-}
diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json
deleted file mode 100644
index b415137c..00000000
--- a/drizzle/meta/0006_snapshot.json
+++ /dev/null
@@ -1,953 +0,0 @@
-{
- "id": "454b291e-ad5f-4fd5-9dcf-c8b6f03a354c",
- "prevId": "5dc5c966-cf77-418e-9fc8-cdfadc8eb47f",
- "version": "7",
- "dialect": "postgresql",
- "tables": {
- "auth.users": {
- "name": "users",
- "schema": "auth",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_comments": {
- "name": "issue_comments",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "author_id": {
- "name": "author_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "content": {
- "name": "content",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "is_system": {
- "name": "is_system",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issue_comments_issue_id": {
- "name": "idx_issue_comments_issue_id",
- "columns": [
- {
- "expression": "issue_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issue_comments_author_id": {
- "name": "idx_issue_comments_author_id",
- "columns": [
- {
- "expression": "author_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_comments_issue_id_issues_id_fk": {
- "name": "issue_comments_issue_id_issues_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_comments_author_id_user_profiles_id_fk": {
- "name": "issue_comments_author_id_user_profiles_id_fk",
- "tableFrom": "issue_comments",
- "tableTo": "user_profiles",
- "columnsFrom": ["author_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issue_watchers": {
- "name": "issue_watchers",
- "schema": "",
- "columns": {
- "issue_id": {
- "name": "issue_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- }
- },
- "indexes": {
- "idx_issue_watchers_user_id": {
- "name": "idx_issue_watchers_user_id",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issue_watchers_issue_id_issues_id_fk": {
- "name": "issue_watchers_issue_id_issues_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "issues",
- "columnsFrom": ["issue_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issue_watchers_user_id_user_profiles_id_fk": {
- "name": "issue_watchers_user_id_user_profiles_id_fk",
- "tableFrom": "issue_watchers",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {
- "issue_watchers_issue_id_user_id_pk": {
- "name": "issue_watchers_issue_id_user_id_pk",
- "columns": ["issue_id", "user_id"]
- }
- },
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.issues": {
- "name": "issues",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "machine_initials": {
- "name": "machine_initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "issue_number": {
- "name": "issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true
- },
- "title": {
- "name": "title",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "description": {
- "name": "description",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "status": {
- "name": "status",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'new'"
- },
- "severity": {
- "name": "severity",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'playable'"
- },
- "priority": {
- "name": "priority",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'low'"
- },
- "reported_by": {
- "name": "reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "unconfirmed_reported_by": {
- "name": "unconfirmed_reported_by",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "assigned_to": {
- "name": "assigned_to",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "resolved_at": {
- "name": "resolved_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_issues_assigned_to": {
- "name": "idx_issues_assigned_to",
- "columns": [
- {
- "expression": "assigned_to",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_reported_by": {
- "name": "idx_issues_reported_by",
- "columns": [
- {
- "expression": "reported_by",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_status": {
- "name": "idx_issues_status",
- "columns": [
- {
- "expression": "status",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_created_at": {
- "name": "idx_issues_created_at",
- "columns": [
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_issues_unconfirmed_reported_by": {
- "name": "idx_issues_unconfirmed_reported_by",
- "columns": [
- {
- "expression": "unconfirmed_reported_by",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "issues_machine_initials_machines_initials_fk": {
- "name": "issues_machine_initials_machines_initials_fk",
- "tableFrom": "issues",
- "tableTo": "machines",
- "columnsFrom": ["machine_initials"],
- "columnsTo": ["initials"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- },
- "issues_reported_by_user_profiles_id_fk": {
- "name": "issues_reported_by_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_unconfirmed_reported_by_unconfirmed_users_id_fk": {
- "name": "issues_unconfirmed_reported_by_unconfirmed_users_id_fk",
- "tableFrom": "issues",
- "tableTo": "unconfirmed_users",
- "columnsFrom": ["unconfirmed_reported_by"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "issues_assigned_to_user_profiles_id_fk": {
- "name": "issues_assigned_to_user_profiles_id_fk",
- "tableFrom": "issues",
- "tableTo": "user_profiles",
- "columnsFrom": ["assigned_to"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unique_issue_number": {
- "name": "unique_issue_number",
- "nullsNotDistinct": false,
- "columns": ["machine_initials", "issue_number"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "reporter_check": {
- "name": "reporter_check",
- "value": "(reported_by IS NULL OR unconfirmed_reported_by IS NULL)"
- }
- },
- "isRLSEnabled": false
- },
- "public.machines": {
- "name": "machines",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "initials": {
- "name": "initials",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "next_issue_number": {
- "name": "next_issue_number",
- "type": "integer",
- "primaryKey": false,
- "notNull": true,
- "default": 1
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "owner_id": {
- "name": "owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "unconfirmed_owner_id": {
- "name": "unconfirmed_owner_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_machines_owner_id": {
- "name": "idx_machines_owner_id",
- "columns": [
- {
- "expression": "owner_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- },
- "idx_machines_unconfirmed_owner_id": {
- "name": "idx_machines_unconfirmed_owner_id",
- "columns": [
- {
- "expression": "unconfirmed_owner_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "machines_owner_id_user_profiles_id_fk": {
- "name": "machines_owner_id_user_profiles_id_fk",
- "tableFrom": "machines",
- "tableTo": "user_profiles",
- "columnsFrom": ["owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- },
- "machines_unconfirmed_owner_id_unconfirmed_users_id_fk": {
- "name": "machines_unconfirmed_owner_id_unconfirmed_users_id_fk",
- "tableFrom": "machines",
- "tableTo": "unconfirmed_users",
- "columnsFrom": ["unconfirmed_owner_id"],
- "columnsTo": ["id"],
- "onDelete": "no action",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "machines_initials_unique": {
- "name": "machines_initials_unique",
- "nullsNotDistinct": false,
- "columns": ["initials"]
- }
- },
- "policies": {},
- "checkConstraints": {
- "initials_check": {
- "name": "initials_check",
- "value": "initials ~ '^[A-Z0-9]{2,6}$'"
- },
- "owner_check": {
- "name": "owner_check",
- "value": "(owner_id IS NULL OR unconfirmed_owner_id IS NULL)"
- }
- },
- "isRLSEnabled": false
- },
- "public.notification_preferences": {
- "name": "notification_preferences",
- "schema": "",
- "columns": {
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email_enabled": {
- "name": "email_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_enabled": {
- "name": "in_app_enabled",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_assigned": {
- "name": "email_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_assigned": {
- "name": "in_app_notify_on_assigned",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_status_change": {
- "name": "email_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_status_change": {
- "name": "in_app_notify_on_status_change",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_comment": {
- "name": "email_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_comment": {
- "name": "in_app_notify_on_new_comment",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_notify_on_new_issue": {
- "name": "email_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "in_app_notify_on_new_issue": {
- "name": "in_app_notify_on_new_issue",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": true
- },
- "email_watch_new_issues_global": {
- "name": "email_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- },
- "in_app_watch_new_issues_global": {
- "name": "in_app_watch_new_issues_global",
- "type": "boolean",
- "primaryKey": false,
- "notNull": true,
- "default": false
- }
- },
- "indexes": {
- "idx_notif_prefs_global_watch_email": {
- "name": "idx_notif_prefs_global_watch_email",
- "columns": [
- {
- "expression": "email_watch_new_issues_global",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notification_preferences_user_id_user_profiles_id_fk": {
- "name": "notification_preferences_user_id_user_profiles_id_fk",
- "tableFrom": "notification_preferences",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.notifications": {
- "name": "notifications",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "user_id": {
- "name": "user_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "type": {
- "name": "type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "resource_id": {
- "name": "resource_id",
- "type": "uuid",
- "primaryKey": false,
- "notNull": true
- },
- "resource_type": {
- "name": "resource_type",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "read_at": {
- "name": "read_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {
- "idx_notifications_user_unread": {
- "name": "idx_notifications_user_unread",
- "columns": [
- {
- "expression": "user_id",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "read_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- },
- {
- "expression": "created_at",
- "isExpression": false,
- "asc": true,
- "nulls": "last"
- }
- ],
- "isUnique": false,
- "concurrently": false,
- "method": "btree",
- "with": {}
- }
- },
- "foreignKeys": {
- "notifications_user_id_user_profiles_id_fk": {
- "name": "notifications_user_id_user_profiles_id_fk",
- "tableFrom": "notifications",
- "tableTo": "user_profiles",
- "columnsFrom": ["user_id"],
- "columnsTo": ["id"],
- "onDelete": "cascade",
- "onUpdate": "no action"
- }
- },
- "compositePrimaryKeys": {},
- "uniqueConstraints": {},
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.unconfirmed_users": {
- "name": "unconfirmed_users",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true,
- "default": "gen_random_uuid()"
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'guest'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "invite_sent_at": {
- "name": "invite_sent_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": false
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "unconfirmed_users_email_unique": {
- "name": "unconfirmed_users_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- },
- "public.user_profiles": {
- "name": "user_profiles",
- "schema": "",
- "columns": {
- "id": {
- "name": "id",
- "type": "uuid",
- "primaryKey": true,
- "notNull": true
- },
- "email": {
- "name": "email",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "first_name": {
- "name": "first_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "last_name": {
- "name": "last_name",
- "type": "text",
- "primaryKey": false,
- "notNull": true
- },
- "name": {
- "name": "name",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "generated": {
- "as": "first_name || ' ' || last_name",
- "type": "stored"
- }
- },
- "avatar_url": {
- "name": "avatar_url",
- "type": "text",
- "primaryKey": false,
- "notNull": false
- },
- "role": {
- "name": "role",
- "type": "text",
- "primaryKey": false,
- "notNull": true,
- "default": "'member'"
- },
- "created_at": {
- "name": "created_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- },
- "updated_at": {
- "name": "updated_at",
- "type": "timestamp with time zone",
- "primaryKey": false,
- "notNull": true,
- "default": "now()"
- }
- },
- "indexes": {},
- "foreignKeys": {},
- "compositePrimaryKeys": {},
- "uniqueConstraints": {
- "user_profiles_email_unique": {
- "name": "user_profiles_email_unique",
- "nullsNotDistinct": false,
- "columns": ["email"]
- }
- },
- "policies": {},
- "checkConstraints": {},
- "isRLSEnabled": false
- }
- },
- "enums": {},
- "schemas": {},
- "sequences": {},
- "roles": {},
- "policies": {},
- "views": {},
- "_meta": {
- "columns": {},
- "schemas": {},
- "tables": {}
- }
-}
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 3decc63b..af811aa8 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -5,50 +5,8 @@
{
"idx": 0,
"version": "7",
- "when": 1765036190971,
- "tag": "0000_init-schema",
- "breakpoints": true
- },
- {
- "idx": 1,
- "version": "7",
- "when": 1766428516608,
- "tag": "0001_add_performance_indexes",
- "breakpoints": true
- },
- {
- "idx": 2,
- "version": "7",
- "when": 1766770560460,
- "tag": "0002_add_more_performance_indexes",
- "breakpoints": true
- },
- {
- "idx": 3,
- "version": "7",
- "when": 1766950334432,
- "tag": "0003_add_unconfirmed_users",
- "breakpoints": true
- },
- {
- "idx": 4,
- "version": "7",
- "when": 1767119720539,
- "tag": "0004_whole_kate_bishop",
- "breakpoints": true
- },
- {
- "idx": 5,
- "version": "7",
- "when": 1767195135624,
- "tag": "0005_add_email_to_profiles",
- "breakpoints": true
- },
- {
- "idx": 6,
- "version": "7",
- "when": 1767581078756,
- "tag": "0006_add_performance_indexes",
+ "when": 1767922624191,
+ "tag": "0000_initv2",
"breakpoints": true
}
]
diff --git a/e2e/full/email-notifications.spec.ts b/e2e/full/email-notifications.spec.ts
index c4fce4a1..43075a54 100644
--- a/e2e/full/email-notifications.spec.ts
+++ b/e2e/full/email-notifications.spec.ts
@@ -50,8 +50,8 @@ test.describe("Email Notifications", () => {
await page.goto("/report?machine=MM");
await page.getByLabel("Issue Title *").fill("Test Issue for Email");
await page.getByLabel("Description").fill("Testing email notifications");
- await page.getByLabel("Severity *").selectOption("playable");
- await page.getByLabel("Priority *").selectOption("low");
+ await page.getByLabel("Select Severity").selectOption("minor");
+ await page.getByLabel("Select Priority").selectOption("low");
// Submit form and wait for Server Action redirect (Safari-defensive)
await submitFormAndWaitForRedirect(
@@ -104,8 +104,8 @@ test.describe("Email Notifications", () => {
// Create issue for a specific machine (e.g., MM)
await page.goto("/report?machine=MM");
await page.getByLabel("Issue Title *").fill("Status Change Test");
- await page.getByLabel("Severity *").selectOption("playable");
- await page.getByLabel("Priority *").selectOption("low");
+ await page.getByLabel("Select Severity").selectOption("minor");
+ await page.getByLabel("Select Priority").selectOption("low");
// Submit form and wait for Server Action redirect (Safari-defensive)
await submitFormAndWaitForRedirect(
@@ -129,7 +129,6 @@ test.describe("Email Notifications", () => {
// Update status
await page.getByTestId("issue-status-select").selectOption("in_progress");
- await page.getByRole("button", { name: "Update Status" }).click();
await expect(page.getByTestId("status-update-success")).toBeVisible();
const url = page.url();
diff --git a/e2e/full/notifications.spec.ts b/e2e/full/notifications.spec.ts
index 57962d90..214c0a7a 100644
--- a/e2e/full/notifications.spec.ts
+++ b/e2e/full/notifications.spec.ts
@@ -70,8 +70,8 @@ test.describe("Notifications", () => {
await expect(publicPage.getByLabel("Issue Title *")).toHaveValue(
issueTitle
);
- await publicPage.getByLabel("Severity *").selectOption("minor");
- await expect(publicPage.getByLabel("Severity *")).toHaveValue("minor");
+ await publicPage.getByLabel("Select Severity").selectOption("minor");
+ await expect(publicPage.getByLabel("Select Severity")).toHaveValue("minor");
await publicPage
.getByRole("button", { name: "Submit Issue Report" })
@@ -134,9 +134,9 @@ test.describe("Notifications", () => {
const issueTitle = `Status Change ${timestamp}`;
await page.getByLabel("Issue Title *").fill(issueTitle);
// Explicitly select severity (required)
- await page.getByLabel("Severity *").selectOption("minor");
+ await page.getByLabel("Select Severity").selectOption("minor");
// Explicitly select priority (required for logged-in users)
- await page.getByLabel("Priority *").selectOption("low");
+ await page.getByLabel("Select Priority").selectOption("low");
await page.getByRole("button", { name: "Submit Issue Report" }).click();
@@ -170,7 +170,6 @@ test.describe("Notifications", () => {
await adminPage
.getByTestId("issue-status-select")
.selectOption("in_progress");
- await adminPage.getByRole("button", { name: "Update Status" }).click();
await expect(adminPage.getByTestId("status-update-success")).toBeVisible();
@@ -237,8 +236,10 @@ test.describe("Notifications", () => {
issueTitle
);
- await publicPage.getByLabel("Severity *").selectOption("unplayable");
- await expect(publicPage.getByLabel("Severity *")).toHaveValue("unplayable");
+ await publicPage.getByLabel("Select Severity").selectOption("unplayable");
+ await expect(publicPage.getByLabel("Select Severity")).toHaveValue(
+ "unplayable"
+ );
await publicPage
.getByRole("button", { name: "Submit Issue Report" })
@@ -299,8 +300,8 @@ test.describe("Notifications", () => {
);
// Explicitly select severity (required)
- await publicPage.getByLabel("Severity *").selectOption("minor");
- await expect(publicPage.getByLabel("Severity *")).toHaveValue("minor");
+ await publicPage.getByLabel("Select Severity").selectOption("minor");
+ await expect(publicPage.getByLabel("Select Severity")).toHaveValue("minor");
await publicPage
.getByRole("button", { name: "Submit Issue Report" })
@@ -353,7 +354,7 @@ test.describe("Notifications", () => {
await memberPage.getByLabel("Issue Title *").fill("Email Test Issue");
// Explicitly select severity (required)
- await memberPage.getByLabel("Severity *").selectOption("minor");
+ await memberPage.getByLabel("Select Severity").selectOption("minor");
await expect(memberPage.getByTestId("machine-select")).toHaveValue(
machine.id
@@ -361,7 +362,7 @@ test.describe("Notifications", () => {
await expect(memberPage.getByLabel("Issue Title *")).toHaveValue(
"Email Test Issue"
);
- await expect(memberPage.getByLabel("Severity *")).toHaveValue("minor");
+ await expect(memberPage.getByLabel("Select Severity")).toHaveValue("minor");
await memberPage
.getByRole("button", { name: "Submit Issue Report" })
diff --git a/e2e/smoke/issues-crud.spec.ts b/e2e/smoke/issues-crud.spec.ts
index a46659e8..3eba6ee3 100644
--- a/e2e/smoke/issues-crud.spec.ts
+++ b/e2e/smoke/issues-crud.spec.ts
@@ -49,8 +49,8 @@ test.describe("Issues System", () => {
await page
.getByLabel("Description")
.fill("The right flipper does not respond when button is pressed");
- await page.getByLabel("Severity *").selectOption("playable");
- await page.getByLabel("Priority *").selectOption("medium");
+ await page.getByTestId("severity-select").selectOption("minor");
+ await page.getByTestId("priority-select").selectOption("medium");
// Submit form
await page.getByRole("button", { name: "Submit Issue Report" }).click();
@@ -64,7 +64,6 @@ test.describe("Issues System", () => {
.getByRole("main")
.getByRole("heading", { level: 1, name: /Test flipper not working/ })
).toBeVisible();
- await expect(page.getByText("Issue created")).toBeVisible();
rememberIssueId(page);
});
@@ -101,8 +100,8 @@ test.describe("Issues System", () => {
// Fill out remaining fields
await page.getByLabel("Issue Title *").fill("Display flickering");
- await page.getByLabel("Severity *").selectOption("minor");
- await page.getByLabel("Priority *").selectOption("low");
+ await page.getByTestId("severity-select").selectOption("minor");
+ await page.getByTestId("priority-select").selectOption("low");
// Submit
await page.getByRole("button", { name: "Submit Issue Report" }).click();
@@ -132,8 +131,8 @@ test.describe("Issues System", () => {
issueTitle = `Test Issue for Details ${Date.now()}`;
await page.goto(`/report?machine=${machineInitials}`);
await page.getByLabel("Issue Title *").fill(issueTitle);
- await page.locator("#severity").selectOption("playable");
- await page.locator("#priority").selectOption("medium");
+ await page.getByTestId("severity-select").selectOption("minor");
+ await page.getByTestId("priority-select").selectOption("medium");
await page.getByRole("button", { name: "Submit Issue Report" }).click();
await expect(page).toHaveURL(/\/m\/[A-Z0-9]{2,6}\/i\/[0-9]+/);
@@ -174,14 +173,13 @@ test.describe("Issues System", () => {
// Should show status and severity badges
await expect(page.getByTestId("issue-status-badge")).toHaveText(/New/i);
await expect(page.getByTestId("issue-severity-badge")).toHaveText(
- /Playable/i
+ /Minor/i
);
// Should show timeline
await expect(
page.getByRole("heading", { name: "Activity" })
).toBeVisible();
- await expect(page.getByText("Issue created")).toBeVisible();
});
// Update tests moved to integration/full suite
diff --git a/e2e/smoke/navigation.spec.ts b/e2e/smoke/navigation.spec.ts
index 5eb2de7b..c305cbca 100644
--- a/e2e/smoke/navigation.spec.ts
+++ b/e2e/smoke/navigation.spec.ts
@@ -23,9 +23,6 @@ test.describe.serial("Navigation", () => {
// Verify Report Issue shortcut is available to guests
await expect(page.getByTestId("nav-report-issue")).toBeVisible();
-
- // Verify Pre-Beta Banner is present
- await expect(page.getByText("Pre-Beta Notice")).toBeVisible();
});
test("authenticated navigation - show quick links and user menu", async ({
diff --git a/e2e/smoke/public-reporting.spec.ts b/e2e/smoke/public-reporting.spec.ts
index 6dac8470..7bc1b2d0 100644
--- a/e2e/smoke/public-reporting.spec.ts
+++ b/e2e/smoke/public-reporting.spec.ts
@@ -26,7 +26,7 @@ test.describe("Public Issue Reporting", () => {
})
).toBeVisible();
- const select = page.getByLabel("Machine *");
+ const select = page.getByTestId("machine-select");
await expect(select).toBeVisible();
await select.selectOption({ index: 1 });
// Wait for URL refresh (router.push) to prevent race conditions on Mobile Safari
@@ -37,7 +37,7 @@ test.describe("Public Issue Reporting", () => {
await page
.getByLabel("Description")
.fill("Playfield gets stuck during multiball.");
- await page.getByLabel("Severity *").selectOption("playable");
+ await page.getByTestId("severity-select").selectOption("minor");
await page.getByRole("button", { name: "Submit Issue Report" }).click();
await expect(page).toHaveURL("/report/success");
@@ -58,12 +58,12 @@ test.describe("Public Issue Reporting", () => {
const email = `newuser-${timestamp}@example.com`;
await page.goto("/report");
- await page.getByLabel("Machine *").selectOption({ index: 1 });
+ await page.getByTestId("machine-select").selectOption({ index: 1 });
// Wait for URL refresh (router.push) to prevent race conditions on Mobile Safari
await expect(page).toHaveURL(/machine=/);
await page.getByLabel("Issue Title *").fill(`${PUBLIC_PREFIX} with Email`);
- await page.getByLabel("Severity *").selectOption("minor");
+ await page.getByTestId("severity-select").selectOption("minor");
await page.getByLabel("First Name").fill("Test");
await page.getByLabel("Last Name").fill("User");
diff --git a/e2e/smoke/status-overhaul.spec.ts b/e2e/smoke/status-overhaul.spec.ts
new file mode 100644
index 00000000..66b2e395
--- /dev/null
+++ b/e2e/smoke/status-overhaul.spec.ts
@@ -0,0 +1,51 @@
+import { test, expect } from "@playwright/test";
+import { loginAs } from "../support/actions";
+import { cleanupTestEntities } from "../support/cleanup";
+import { seededMachines } from "../support/constants";
+
+test.describe("Status Overhaul E2E", () => {
+ test.beforeEach(async ({ page }, testInfo) => {
+ test.setTimeout(60000);
+ await loginAs(page, testInfo);
+ });
+
+ test.afterEach(async ({ request }) => {
+ await cleanupTestEntities(request, {
+ issueTitlePrefix: "E2E Status Overhaul Test",
+ });
+ });
+
+ test("should create issue and verify all 4 badges", async ({ page }) => {
+ const machine = seededMachines.addamsFamily;
+
+ // 1. Create Issue
+ await page.goto(`/report?machine=${machine.initials}`);
+ await page.getByTestId("machine-select").selectOption(machine.id);
+ await page.getByLabel("Issue Title *").fill("E2E Status Overhaul Test");
+ await page.getByTestId("severity-select").selectOption("major");
+ await page.getByTestId("priority-select").selectOption("high");
+ await page.getByTestId("consistency-select").selectOption("frequent");
+ await page.getByRole("button", { name: "Submit Issue Report" }).click();
+
+ // 2. Verify redirect and badges
+ await expect(page).toHaveURL(/\/m\/TAF\/i\/[0-9]+/);
+
+ await expect(page.getByTestId("issue-status-badge")).toHaveText(/New/i);
+ await expect(page.getByTestId("issue-severity-badge")).toHaveText(/Major/i);
+ await expect(page.getByTestId("issue-priority-badge")).toHaveText(/High/i);
+ await expect(page.getByTestId("issue-consistency-badge")).toHaveText(
+ /Frequent/i
+ );
+
+ // 3. Update Status
+ await page.getByLabel("Update Issue Status").selectOption("in_progress");
+
+ // 4. Verify status change in badge and timeline
+ await expect(page.getByTestId("issue-status-badge")).toHaveText(
+ /In Progress/i
+ );
+ await expect(
+ page.getByText("Status changed from New to In Progress")
+ ).toBeVisible();
+ });
+});
diff --git a/next.config.ts b/next.config.ts
index e49e4b56..59230716 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -62,8 +62,8 @@ export default withSentryConfig(nextConfig, {
tunnelRoute: "/monitoring",
// Automatically tree-shake Sentry logger statements to reduce bundle size
- disableLogger: true,
-
- // Enables automatic instrumentation of Vercel Cron Monitors.
- automaticVercelMonitors: true,
+ // @ts-ignore - treeshake is a valid but potentially untyped property in some versions
+ treeshake: {
+ removeDebugLogging: true,
+ },
});
diff --git a/package.json b/package.json
index cc5211cb..75c7b0c0 100644
--- a/package.json
+++ b/package.json
@@ -9,19 +9,19 @@
"build": "next build",
"start": "next start",
"typecheck": "tsc --noEmit -p tsconfig.json",
- "lint": "eslint src/",
- "lint:fix": "eslint src/ --fix",
- "format": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
- "format:fix": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"",
- "test": "vitest run --project unit --silent --no-color",
- "test:integration": "vitest run --project integration --silent --no-color",
- "test:integration:supabase": "vitest run --project integration-supabase --silent --no-color",
+ "lint": "eslint src/ --quiet",
+ "lint:fix": "eslint src/ --fix --quiet",
+ "format": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,md}\" --log-level error",
+ "format:fix": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\" --log-level error",
+ "test": "vitest run --project unit --silent --no-color --reporter=dot",
+ "test:integration": "vitest run --project integration --silent --no-color --reporter=dot",
+ "test:integration:supabase": "vitest run --project integration-supabase --silent --no-color --reporter=dot",
"test:watch": "vitest watch --project unit",
- "test:coverage": "vitest run --project unit --coverage --silent --no-color",
+ "test:coverage": "vitest run --project unit --coverage --silent --no-color --reporter=dot",
"test:_generate-schema": "drizzle-kit export --dialect=postgresql --schema=./src/server/db/schema.ts > src/test/setup/schema.sql",
- "smoke": "playwright test --config=playwright.config.smoke.ts --project=chromium --project='Mobile Chrome'",
+ "smoke": "playwright test --config=playwright.config.smoke.ts --project=chromium --project='Mobile Chrome' --quiet",
"smoke:headed": "playwright test --config=playwright.config.smoke.ts --headed --project=chromium --project='Mobile Chrome'",
- "e2e:full": "playwright test --config=playwright.config.full.ts",
+ "e2e:full": "playwright test --config=playwright.config.full.ts --quiet",
"e2e:full:headed": "playwright test --config=playwright.config.full.ts --headed",
"e2e:mobile": "playwright test --project='Mobile Chrome'",
"db:reset": "npm-run-all db:_restart db:_drop_tables db:migrate test:_generate-schema db:_seed db:_seed-users",
@@ -33,8 +33,8 @@
"db:generate": "drizzle-kit generate",
"db:_seed": "node --env-file=.env.local -e 'require(\"child_process\").execSync(\"psql \" + process.env.DATABASE_URL + \" -f supabase/seed.sql\", {stdio: \"inherit\"})'",
"db:_seed-users": "node --env-file=.env.local supabase/seed-users.mjs",
- "db:fast-reset": "node --env-file=.env.local scripts/db-fast-reset.mjs",
- "check:config": "python3 scripts/check-config-drift.py",
+ "db:fast-reset": "node --env-file=.env.local scripts/db-fast-reset.mjs > /dev/null",
+ "check:config": "python3 scripts/check-config-drift.py > /dev/null",
"check": "npm-run-all --silent --parallel typecheck lint test format:fix",
"preflight": "npm-run-all --silent --parallel typecheck lint:fix format:fix test check:config --sequential db:fast-reset --parallel build test:integration test:integration:supabase --sequential smoke",
"supa:ci": "bash scripts/supa-ci.sh",
diff --git a/playwright.config.ts b/playwright.config.ts
index b6d2185c..4ef098ad 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -130,7 +130,7 @@ export default defineConfig({
// Reporters: print progress locally; keep CI quiet; never block on HTML
reporter: process.env["CI"]
? [["dot"], ["html", { open: "never" }]]
- : [["list"], ["html", { open: "never" }]],
+ : [["line"], ["html", { open: "never" }]],
// Shared settings for all the projects below
use: {
diff --git a/scripts/db-fast-reset.mjs b/scripts/db-fast-reset.mjs
index b2bcd1b1..763e9504 100644
--- a/scripts/db-fast-reset.mjs
+++ b/scripts/db-fast-reset.mjs
@@ -2,10 +2,10 @@ import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import { execSync } from "child_process";
-const databaseUrl = process.env.DATABASE_URL;
+const databaseUrl = process.env.DIRECT_URL || process.env.DATABASE_URL;
if (!databaseUrl) {
- console.error("❌ DATABASE_URL is not defined in .env.local");
+ console.error("❌ DATABASE_URL or DIRECT_URL is not defined");
process.exit(1);
}
@@ -20,13 +20,13 @@ async function fastReset() {
// Truncate all tables except migrations/schema-related if any
// We cascade to handle foreign keys
await client`
- TRUNCATE TABLE
- "issue_comments",
- "issue_watchers",
- "issues",
- "machines",
- "notifications",
- "notification_preferences",
+ TRUNCATE TABLE
+ "issue_comments",
+ "issue_watchers",
+ "issues",
+ "machines",
+ "notifications",
+ "notification_preferences",
"user_profiles",
"auth"."users"
CASCADE;
diff --git a/scripts/force-db-reset.mjs b/scripts/force-db-reset.mjs
index c9f97bd1..7dc909b9 100644
--- a/scripts/force-db-reset.mjs
+++ b/scripts/force-db-reset.mjs
@@ -8,10 +8,10 @@
import postgres from "postgres";
-const databaseUrl = process.env.DATABASE_URL;
+const databaseUrl = process.env.DIRECT_URL || process.env.DATABASE_URL;
if (!databaseUrl) {
- console.error("❌ DATABASE_URL is not defined in .env.local");
+ console.error("❌ DATABASE_URL or DIRECT_URL is not defined");
process.exit(1);
}
diff --git a/src/app/(app)/dashboard/page.tsx b/src/app/(app)/dashboard/page.tsx
index d7b75a96..2accbe67 100644
--- a/src/app/(app)/dashboard/page.tsx
+++ b/src/app/(app)/dashboard/page.tsx
@@ -8,22 +8,14 @@ import {
Wrench,
XCircle,
} from "lucide-react";
-import { cn } from "~/lib/utils";
import { createClient } from "~/lib/supabase/server";
import { db } from "~/server/db";
import { issues, machines, userProfiles } from "~/server/db/schema";
-import { desc, eq, sql, and, ne } from "drizzle-orm";
+import { desc, eq, sql, and, notInArray } from "drizzle-orm";
+import { CLOSED_STATUSES } from "~/lib/issues/status";
+import type { Issue } from "~/lib/types";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card";
-import { Badge } from "~/components/ui/badge";
-import {
- getIssueStatusStyles,
- getIssueSeverityStyles,
- getIssueStatusLabel,
- getIssueSeverityLabel,
- isIssueStatus,
- isIssueSeverity,
-} from "~/lib/issues/status";
-import { formatIssueId } from "~/lib/issues/utils";
+import { IssueCard } from "~/components/issues/IssueCard";
/**
* Cached dashboard data fetcher (CORE-PERF-001)
@@ -48,7 +40,7 @@ const getDashboardData = cache(async (userId?: string) => {
? db.query.issues.findMany({
where: and(
eq(issues.assignedTo, userId),
- ne(issues.status, "resolved")
+ notInArray(issues.status, [...CLOSED_STATUSES])
),
orderBy: desc(issues.createdAt),
limit: 10,
@@ -66,6 +58,8 @@ const getDashboardData = cache(async (userId?: string) => {
title: true,
status: true,
severity: true,
+ priority: true,
+ consistency: true,
machineInitials: true,
issueNumber: true,
createdAt: true,
@@ -79,7 +73,10 @@ const getDashboardData = cache(async (userId?: string) => {
.select({ count: sql`count(*)::int` })
.from(issues)
.where(
- and(eq(issues.assignedTo, userId), ne(issues.status, "resolved"))
+ and(
+ eq(issues.assignedTo, userId),
+ notInArray(issues.status, [...CLOSED_STATUSES])
+ )
)
: Promise.resolve([{ count: 0 }]);
@@ -107,6 +104,8 @@ const getDashboardData = cache(async (userId?: string) => {
title: true,
status: true,
severity: true,
+ priority: true,
+ consistency: true,
machineInitials: true,
issueNumber: true,
createdAt: true,
@@ -125,7 +124,10 @@ const getDashboardData = cache(async (userId?: string) => {
.from(machines)
.innerJoin(issues, eq(issues.machineInitials, machines.initials))
.where(
- and(eq(issues.severity, "unplayable"), ne(issues.status, "resolved"))
+ and(
+ eq(issues.severity, "unplayable"),
+ notInArray(issues.status, [...CLOSED_STATUSES])
+ )
)
.groupBy(machines.id, machines.name, machines.initials);
@@ -133,7 +135,7 @@ const getDashboardData = cache(async (userId?: string) => {
const totalOpenIssuesPromise = db
.select({ count: sql`count(*)::int` })
.from(issues)
- .where(ne(issues.status, "resolved"));
+ .where(notInArray(issues.status, [...CLOSED_STATUSES]));
// Query 6: Machines needing service (machines with at least one open issue)
// Optimized to use count(distinct) instead of fetching all IDs
@@ -141,7 +143,7 @@ const getDashboardData = cache(async (userId?: string) => {
.select({ count: sql`count(distinct ${machines.id})::int` })
.from(machines)
.innerJoin(issues, eq(issues.machineInitials, machines.initials))
- .where(ne(issues.status, "resolved"));
+ .where(notInArray(issues.status, [...CLOSED_STATUSES]));
// Execute all queries in parallel
const [
@@ -296,63 +298,13 @@ export default async function DashboardPage(): Promise {
) : (
{assignedIssues.map((issue) => (
-
-
-
-
-
-
-
- {formatIssueId(
- issue.machineInitials,
- issue.issueNumber
- )}
- {" "}
- {issue.title}
-
-
- {issue.machine.name}
-
-
-
- {/* Status Badge */}
- {isIssueStatus(issue.status) && (
-
- {getIssueStatusLabel(issue.status)}
-
- )}
- {/* Severity Badge */}
- {isIssueSeverity(issue.severity) && (
-
- {getIssueSeverityLabel(issue.severity)}
-
- )}
-
-
-
-
-
+ issue={issue as unknown as Issue}
+ machine={{ name: issue.machine.name }}
+ variant="compact"
+ dataTestId="assigned-issue-card"
+ />
))}
)}
@@ -424,69 +376,16 @@ export default async function DashboardPage(): Promise {
data-testid="recent-issues-list"
>
{recentIssues.map((issue) => (
-
-
-
-
-
-
-
- {formatIssueId(
- issue.machineInitials,
- issue.issueNumber
- )}
- {" "}
- {issue.title}
-
-
- {issue.machine.name}
-
- Reported by{" "}
- {issue.reportedByUser?.name ??
- "Anonymous Reporter"}{" "}
- • {new Date(issue.createdAt).toLocaleDateString()}
-
-
-
-
- {/* Status Badge */}
- {isIssueStatus(issue.status) && (
-
- {getIssueStatusLabel(issue.status)}
-
- )}
- {/* Severity Badge */}
- {isIssueSeverity(issue.severity) && (
-
- {getIssueSeverityLabel(issue.severity)}
-
- )}
-
-
-
-
-
+ issue={issue as unknown as Issue}
+ machine={{ name: issue.machine.name }}
+ showReporter={true}
+ reporterName={
+ issue.reportedByUser?.name ?? "Anonymous Reporter"
+ }
+ dataTestId="recent-issue-card"
+ />
))}
)}
diff --git a/src/app/(app)/debug/badges/page.tsx b/src/app/(app)/debug/badges/page.tsx
new file mode 100644
index 00000000..33b78b18
--- /dev/null
+++ b/src/app/(app)/debug/badges/page.tsx
@@ -0,0 +1,126 @@
+import type React from "react";
+import { IssueBadge } from "~/components/issues/IssueBadge";
+import { ISSUE_STATUS_VALUES } from "~/lib/issues/status";
+import type {
+ IssueSeverity,
+ IssuePriority,
+ IssueConsistency,
+} from "~/lib/types";
+
+export default function BadgeDebugPage(): React.JSX.Element {
+ const severities: IssueSeverity[] = [
+ "cosmetic",
+ "minor",
+ "major",
+ "unplayable",
+ ];
+ const priorities: IssuePriority[] = ["low", "medium", "high"];
+ const consistencies: IssueConsistency[] = [
+ "intermittent",
+ "frequent",
+ "constant",
+ ];
+
+ return (
+
+
+
Issue Badge System
+
+ Standardized badges with fixed width (120px) and vibrant colors.
+
+
+
+
+ {/* Statuses Section */}
+
+
+ Status Variations
+
+
+ {ISSUE_STATUS_VALUES.map((status) => {
+ return (
+
+
+
+ {status}
+
+
+ );
+ })}
+
+
+
+
+ {/* Severities Section */}
+
+
+ Severity Variations
+
+
+ {severities.map((severity) => {
+ return (
+
+
+
+ {severity}
+
+
+ );
+ })}
+
+
+
+ {/* Priorities Section */}
+
+
+ Priority Variations
+
+
+ {priorities.map((priority) => {
+ return (
+
+
+
+ {priority}
+
+
+ );
+ })}
+
+
+
+ {/* Consistencies Section */}
+
+
+ Consistency Variations
+
+
+ {consistencies.map((consistency) => {
+ return (
+
+
+
+ {consistency}
+
+
+ );
+ })}
+
+
+
+
+
+ );
+}
diff --git a/src/app/(app)/help/page.tsx b/src/app/(app)/help/page.tsx
index 9967af65..3fe136bd 100644
--- a/src/app/(app)/help/page.tsx
+++ b/src/app/(app)/help/page.tsx
@@ -46,12 +46,17 @@ export default function HelpPage(): React.JSX.Element {
- minor – cosmetic or small
- issues that do not change how the game plays.
+ cosmetic – very minor issues
+ that do not affect gameplay at all (dirty playfield, minor bulb
+ out).
- playable – the game plays,
- but something is clearly wrong (shots not registering, features
+ minor – small issues that do
+ not change how the game plays, but might be noticeable.
+
+
+ major – the game plays, but
+ something significant is wrong (shots not registering, features
disabled, audio glitches).
@@ -76,8 +81,8 @@ export default function HelpPage(): React.JSX.Element {
Comments can be added to track troubleshooting steps and fixes.
- When the issue is resolved, the machine's status updates based
- on its remaining open issues.
+ When the issue is fixed , the machine's status
+ updates based on its remaining open issues.
diff --git a/src/app/(app)/issues/actions.ts b/src/app/(app)/issues/actions.ts
index 279738ec..7db6596a 100644
--- a/src/app/(app)/issues/actions.ts
+++ b/src/app/(app)/issues/actions.ts
@@ -19,6 +19,7 @@ import {
updateIssueStatusSchema,
updateIssueSeveritySchema,
updateIssuePrioritySchema,
+ updateIssueConsistencySchema,
assignIssueSchema,
addCommentSchema,
} from "./schemas";
@@ -30,6 +31,7 @@ import {
assignIssue,
updateIssueSeverity,
updateIssuePriority,
+ updateIssueConsistency,
} from "~/services/issues";
import { canUpdateIssue } from "~/lib/permissions";
import { userProfiles } from "~/server/db/schema";
@@ -70,6 +72,11 @@ export type UpdateIssuePriorityResult = Result<
"VALIDATION" | "UNAUTHORIZED" | "NOT_FOUND" | "SERVER"
>;
+export type UpdateIssueConsistencyResult = Result<
+ { issueId: string },
+ "VALIDATION" | "UNAUTHORIZED" | "NOT_FOUND" | "SERVER"
+>;
+
export type AssignIssueResult = Result<
{ issueId: string },
"VALIDATION" | "UNAUTHORIZED" | "NOT_FOUND" | "SERVER"
@@ -111,6 +118,7 @@ export async function createIssueAction(
machineInitials: toOptionalString(formData.get("machineInitials")),
severity: toOptionalString(formData.get("severity")),
priority: toOptionalString(formData.get("priority")),
+ consistency: toOptionalString(formData.get("consistency")),
};
const validation = createIssueSchema.safeParse(rawData);
@@ -127,8 +135,14 @@ export async function createIssueAction(
return err("VALIDATION", firstError?.message ?? "Invalid input");
}
- const { title, description, machineInitials, severity, priority } =
- validation.data;
+ const {
+ title,
+ description,
+ machineInitials,
+ severity,
+ priority,
+ consistency,
+ } = validation.data;
// Create issue via service
try {
@@ -138,6 +152,7 @@ export async function createIssueAction(
machineInitials,
severity,
priority,
+ consistency,
reportedBy: user.id,
});
@@ -148,7 +163,7 @@ export async function createIssueAction(
// Redirect to new issue page
redirect(`/m/${machineInitials}/i/${issue.issueNumber}`);
- } catch (error) {
+ } catch (error: unknown) {
if (isNextRedirectError(error)) {
throw error;
}
@@ -369,6 +384,93 @@ export async function updateIssueSeverityAction(
}
}
+/**
+ * Update Issue Consistency Action
+ */
+export async function updateIssueConsistencyAction(
+ _prevState: UpdateIssueConsistencyResult | undefined,
+ formData: FormData
+): Promise {
+ const supabase = await createClient();
+ const {
+ data: { user },
+ } = await supabase.auth.getUser();
+ if (!user) return err("UNAUTHORIZED", "Unauthorized");
+
+ const rawData = {
+ issueId: toOptionalString(formData.get("issueId")),
+ consistency: toOptionalString(formData.get("consistency")),
+ };
+
+ const validation = updateIssueConsistencySchema.safeParse(rawData);
+ if (!validation.success) {
+ return err(
+ "VALIDATION",
+ validation.error.issues[0]?.message ?? "Invalid input"
+ );
+ }
+
+ const { issueId, consistency } = validation.data;
+
+ try {
+ const currentIssue = await db.query.issues.findFirst({
+ where: eq(issues.id, issueId),
+ columns: {
+ machineInitials: true,
+ issueNumber: true,
+ reportedBy: true,
+ assignedTo: true,
+ },
+ with: {
+ machine: {
+ columns: { ownerId: true },
+ },
+ },
+ });
+
+ if (!currentIssue) return err("NOT_FOUND", "Issue not found");
+
+ // Permission check
+ const userProfile = await db.query.userProfiles.findFirst({
+ where: eq(userProfiles.id, user.id),
+ columns: { role: true },
+ });
+
+ if (
+ !canUpdateIssue(
+ { id: user.id, role: userProfile?.role ?? "guest" },
+ currentIssue,
+ currentIssue.machine
+ )
+ ) {
+ return err(
+ "UNAUTHORIZED",
+ "You do not have permission to update this issue"
+ );
+ }
+
+ // Update consistency
+ await updateIssueConsistency({
+ issueId,
+ consistency,
+ });
+
+ revalidatePath(
+ `/m/${currentIssue.machineInitials}/i/${currentIssue.issueNumber}`
+ );
+ return ok({ issueId });
+ } catch (error: unknown) {
+ log.error(
+ {
+ error: error instanceof Error ? error.message : "Unknown",
+ action: "updateIssueConsistency",
+ },
+ "Update issue consistency error"
+ );
+ return err("SERVER", "Failed to update consistency");
+ }
+}
+
/**
* Update Issue Priority Action
*
diff --git a/src/app/(app)/issues/page.tsx b/src/app/(app)/issues/page.tsx
index c546dbbf..bd893506 100644
--- a/src/app/(app)/issues/page.tsx
+++ b/src/app/(app)/issues/page.tsx
@@ -8,6 +8,13 @@ import { IssueRow } from "~/components/issues/IssueRow";
import { AlertTriangle } from "lucide-react";
import { createClient } from "~/lib/supabase/server";
import { redirect } from "next/navigation";
+import type {
+ Issue,
+ IssueStatus,
+ IssueSeverity,
+ IssuePriority,
+} from "~/lib/types";
+import { OPEN_STATUSES, CLOSED_STATUSES } from "~/lib/issues/status";
export const metadata: Metadata = {
title: "Issues | PinPoint",
@@ -44,38 +51,40 @@ export default async function IssuesPage({
columns: { initials: true, name: true },
});
- // Safe type casting for filters
- // Default to Open issues (new + in_progress) if no status is specified
- // If status is 'resolved', show resolved issues
- // If specific status (new/in_progress) is requested, respect it
- let statusFilter: string[] | undefined;
+ // Safe type casting for filters using imported constants from single source of truth
+ // Based on _issue-status-redesign/README.md - Final design with 11 statuses
+ let statusFilter: IssueStatus[];
- if (status === "resolved") {
- statusFilter = ["resolved"];
- } else if (status === "new" || status === "in_progress") {
- statusFilter = [status];
+ if (status === "closed") {
+ statusFilter = [...CLOSED_STATUSES];
+ } else if (
+ (OPEN_STATUSES as readonly IssueStatus[]).includes(status as IssueStatus)
+ ) {
+ statusFilter = [status as IssueStatus];
+ } else if (
+ (CLOSED_STATUSES as readonly IssueStatus[]).includes(status as IssueStatus)
+ ) {
+ statusFilter = [status as IssueStatus];
} else {
// Default case: Show all Open issues
- statusFilter = ["new", "in_progress"];
+ statusFilter = [...OPEN_STATUSES];
}
- const severityFilter =
- severity && ["minor", "playable", "unplayable"].includes(severity)
- ? (severity as "minor" | "playable" | "unplayable")
+ const severityFilter: IssueSeverity | undefined =
+ severity && ["cosmetic", "minor", "major", "unplayable"].includes(severity)
+ ? (severity as IssueSeverity)
: undefined;
- const priorityFilter =
- priority && ["low", "medium", "high", "critical"].includes(priority)
- ? (priority as "low" | "medium" | "high" | "critical")
+ const priorityFilter: IssuePriority | undefined =
+ priority && ["low", "medium", "high"].includes(priority)
+ ? (priority as IssuePriority)
: undefined;
// Fetch Issues based on filters
- const issuesList = await db.query.issues.findMany({
+ // Type assertion needed because Drizzle infers status as string, not IssueStatus
+ const issuesList = (await db.query.issues.findMany({
where: and(
- inArray(
- issues.status,
- statusFilter as ("new" | "in_progress" | "resolved")[]
- ),
+ inArray(issues.status, statusFilter),
severityFilter ? eq(issues.severity, severityFilter) : undefined,
priorityFilter ? eq(issues.priority, priorityFilter) : undefined,
machine ? eq(issues.machineInitials, machine) : undefined
@@ -90,7 +99,21 @@ export default async function IssuesPage({
},
},
limit: 100, // Reasonable limit for now
- });
+ })) as (Pick<
+ Issue,
+ | "id"
+ | "createdAt"
+ | "machineInitials"
+ | "issueNumber"
+ | "title"
+ | "status"
+ | "severity"
+ | "priority"
+ | "consistency"
+ > & {
+ machine: { name: string } | null;
+ reportedByUser: { name: string } | null;
+ })[];
return (
diff --git a/src/app/(app)/issues/schemas.ts b/src/app/(app)/issues/schemas.ts
index 54a8b2ec..82c98a19 100644
--- a/src/app/(app)/issues/schemas.ts
+++ b/src/app/(app)/issues/schemas.ts
@@ -6,6 +6,7 @@
*/
import { z } from "zod";
+import { ISSUE_STATUS_VALUES } from "~/lib/issues/status";
const uuidish = z
.string()
@@ -39,20 +40,25 @@ export const createIssueSchema = z.object({
.min(2, "Machine initials invalid")
.max(6, "Machine initials invalid")
.regex(/^[A-Z0-9]+$/, "Machine initials invalid"),
- severity: z.enum(["minor", "playable", "unplayable"], {
+ severity: z.enum(["cosmetic", "minor", "major", "unplayable"], {
message: "Invalid severity level",
}),
priority: z.enum(["low", "medium", "high"], {
message: "Invalid priority level",
}),
+ consistency: z.enum(["intermittent", "frequent", "constant"], {
+ message: "Invalid consistency level",
+ }),
});
/**
* Schema for updating issue status
+ * Based on _issue-status-redesign/README.md - Final design with 11 statuses
+ * Status values imported from single source of truth
*/
export const updateIssueStatusSchema = z.object({
issueId: uuidish,
- status: z.enum(["new", "in_progress", "resolved"], {
+ status: z.enum(ISSUE_STATUS_VALUES, {
message: "Invalid status",
}),
});
@@ -62,7 +68,7 @@ export const updateIssueStatusSchema = z.object({
*/
export const updateIssueSeveritySchema = z.object({
issueId: uuidish,
- severity: z.enum(["minor", "playable", "unplayable"], {
+ severity: z.enum(["cosmetic", "minor", "major", "unplayable"], {
message: "Invalid severity level",
}),
});
@@ -77,6 +83,16 @@ export const updateIssuePrioritySchema = z.object({
}),
});
+/**
+ * Schema for updating issue consistency
+ */
+export const updateIssueConsistencySchema = z.object({
+ issueId: uuidish,
+ consistency: z.enum(["intermittent", "frequent", "constant"], {
+ message: "Invalid consistency level",
+ }),
+});
+
/**
* Schema for assigning issue to user
*/
diff --git a/src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx b/src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx
index c292ebda..83b3ee94 100644
--- a/src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx
+++ b/src/app/(app)/m/[initials]/i/[issueNumber]/page.tsx
@@ -1,36 +1,17 @@
import type React from "react";
import { redirect } from "next/navigation";
-import { cn } from "~/lib/utils";
import Link from "next/link";
import { ArrowLeft } from "lucide-react";
import { createClient } from "~/lib/supabase/server";
import { db } from "~/server/db";
import { issues, userProfiles, authUsers } from "~/server/db/schema";
import { eq, asc, and } from "drizzle-orm";
-import { Badge } from "~/components/ui/badge";
import { PageShell } from "~/components/layout/PageShell";
import { IssueTimeline } from "~/components/issues/IssueTimeline";
import { IssueSidebar } from "~/components/issues/IssueSidebar";
-import {
- getIssueStatusStyles,
- getIssueSeverityStyles,
- getIssuePriorityStyles,
-} from "~/lib/issues/status";
-import { type IssueSeverity, type IssuePriority } from "~/lib/types";
+import { IssueBadgeGrid } from "~/components/issues/IssueBadgeGrid";
import { formatIssueId } from "~/lib/issues/utils";
-
-const severityCopy: Record
= {
- minor: "Minor",
- playable: "Playable",
- unplayable: "Unplayable",
-};
-
-const priorityCopy: Record = {
- low: "Low",
- medium: "Medium",
- high: "High",
- critical: "Critical",
-};
+import type { Issue, IssueWithAllRelations } from "~/lib/types";
/**
* Issue Detail Page (Protected Route)
@@ -150,37 +131,16 @@ export default async function IssueDetailPage({
{issue.title}
-
- {issue.status === "in_progress"
- ? "In Progress"
- : issue.status.charAt(0).toUpperCase() + issue.status.slice(1)}
-
-
-
- {severityCopy[issue.severity]}
-
-
-
- {priorityCopy[issue.priority]}
-
+
+ }
+ variant="strip"
+ size="lg"
+ />
@@ -190,12 +150,12 @@ export default async function IssueDetailPage({
Activity
-
+
{/* Sticky Sidebar */}
diff --git a/src/app/(app)/m/[initials]/i/[issueNumber]/update-issue-consistency-form.tsx b/src/app/(app)/m/[initials]/i/[issueNumber]/update-issue-consistency-form.tsx
new file mode 100644
index 00000000..b5787381
--- /dev/null
+++ b/src/app/(app)/m/[initials]/i/[issueNumber]/update-issue-consistency-form.tsx
@@ -0,0 +1,66 @@
+"use client";
+
+import type React from "react";
+import { useActionState } from "react";
+import { Loader2 } from "lucide-react";
+import {
+ updateIssueConsistencyAction,
+ type UpdateIssueConsistencyResult,
+} from "~/app/(app)/issues/actions";
+import { type IssueConsistency } from "~/lib/types";
+
+interface UpdateIssueConsistencyFormProps {
+ issueId: string;
+ currentConsistency: IssueConsistency;
+}
+
+const consistencyOptions: { value: IssueConsistency; label: string }[] = [
+ { value: "intermittent", label: "Intermittent" },
+ { value: "frequent", label: "Frequent" },
+ { value: "constant", label: "Constant" },
+];
+
+export function UpdateIssueConsistencyForm({
+ issueId,
+ currentConsistency,
+}: UpdateIssueConsistencyFormProps): React.JSX.Element {
+ const [state, formAction, isPending] = useActionState<
+ UpdateIssueConsistencyResult | undefined,
+ FormData
+ >(updateIssueConsistencyAction, undefined);
+
+ return (
+
+ );
+}
diff --git a/src/app/(app)/m/[initials]/i/[issueNumber]/update-issue-priority-form.tsx b/src/app/(app)/m/[initials]/i/[issueNumber]/update-issue-priority-form.tsx
index dd0bab79..3ab67708 100644
--- a/src/app/(app)/m/[initials]/i/[issueNumber]/update-issue-priority-form.tsx
+++ b/src/app/(app)/m/[initials]/i/[issueNumber]/update-issue-priority-form.tsx
@@ -2,7 +2,7 @@
import type React from "react";
import { useActionState } from "react";
-import { Button } from "~/components/ui/button";
+import { Loader2 } from "lucide-react";
import {
updateIssuePriorityAction,
type UpdateIssuePriorityResult,
@@ -32,25 +32,32 @@ export function UpdateIssuePriorityForm({
return (