diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml new file mode 100644 index 0000000..dc42fe0 --- /dev/null +++ b/.github/workflows/desktop-release.yml @@ -0,0 +1,200 @@ +name: Desktop Release + +on: + push: + tags: + - "v*" + +env: + CARGO_TERM_COLOR: always + +jobs: + build: + name: Build ${{ matrix.platform.os-name }} (${{ matrix.target }}) + runs-on: ${{ matrix.platform.os }} + strategy: + fail-fast: false + matrix: + include: + # Linux x64 + - platform: + os: ubuntu-latest + os-name: linux + target: x86_64-unknown-linux-gnu + bundles: deb,rpm,appimage + # Linux ARM64 + - platform: + os: ubuntu-latest + os-name: linux + target: aarch64-unknown-linux-gnu + bundles: deb,rpm,appimage + # macOS (universal binary) + - platform: + os: macos-latest + os-name: macos + target: universal-apple-darwin + bundles: dmg + # Windows + - platform: + os: windows-latest + os-name: windows + target: x86_64-pc-windows-msvc + bundles: nsis + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Cache Cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Cache Bun dependencies + uses: actions/cache@v4 + with: + path: ~/.bun/install/cache + key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }} + restore-keys: | + ${{ runner.os }}-bun- + + - name: Cache Rust target + uses: actions/cache@v4 + with: + path: | + src-tauri/target + key: ${{ runner.os }}-${{ matrix.target }}-rust-target-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target }}-rust-target- + + - name: Install Linux dependencies + if: matrix.platform.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y \ + libwebkit2gtk-4.1-dev \ + libgtk-3-dev \ + libayatana-appindicator3-dev \ + librsvg2-dev \ + libsoup-3.0-dev \ + libjavascriptcoregtk-4.1-dev \ + patchelf + + # Cross-compilation support for ARM64 + if [ "${{ matrix.target }}" = "aarch64-unknown-linux-gnu" ]; then + sudo apt-get install -y \ + gcc-aarch64-linux-gnu \ + g++-aarch64-linux-gnu + fi + + - name: Setup macOS keychain (for signing) + if: matrix.platform.os == 'macos-latest' + env: + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + run: | + if [ -n "$APPLE_CERTIFICATE" ]; then + # Create temporary keychain + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + KEYCHAIN_PASSWORD=$(openssl rand -base64 32) + + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + # Import certificate + CERTIFICATE_PATH=$RUNNER_TEMP/certificate.p12 + echo -n "$APPLE_CERTIFICATE" | base64 --decode -o "$CERTIFICATE_PATH" + security import "$CERTIFICATE_PATH" \ + -P "$APPLE_CERTIFICATE_PASSWORD" \ + -A -t cert -f pkcs12 \ + -k "$KEYCHAIN_PATH" + + security list-keychain -d user -s "$KEYCHAIN_PATH" + fi + + - name: Install frontend dependencies + run: bun install --frozen-lockfile + + - name: Build frontend + run: bun run build + + - name: Build Tauri app + uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # macOS signing + APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} + APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} + APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} + APPLE_ID: ${{ secrets.APPLE_ID }} + APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }} + APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} + # Windows signing + WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }} + WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }} + # Cross-compilation for Linux ARM64 + CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc + CC_aarch64_unknown_linux_gnu: aarch64-linux-gnu-gcc + CXX_aarch64_unknown_linux_gnu: aarch64-linux-gnu-g++ + with: + tagName: ${{ github.ref_name }} + releaseName: "Compass Desktop ${{ github.ref_name }}" + releaseBody: "See CHANGELOG.md for details." + releaseDraft: true + prerelease: false + args: | + --target ${{ matrix.target }} + --bundles ${{ matrix.bundles }} + + release: + name: Create Release + needs: build + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Generate Release Notes + id: release-notes + run: | + # Extract version from tag (controlled by GitHub, not user input) + VERSION="${GITHUB_REF#refs/tags/}" + + # Check if CHANGELOG.md exists and extract notes for this version + if [ -f "CHANGELOG.md" ]; then + # Extract section for current version + NOTES=$(sed -n "/^## \[$VERSION\]/,/^## \[/p" CHANGELOG.md | sed '1d;$d') + if [ -z "$NOTES" ]; then + NOTES="Release $VERSION" + fi + else + NOTES="Release $VERSION" + fi + + # Save to file for multiline output + echo "$NOTES" > /tmp/release-notes.md + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + body_path: /tmp/release-notes.md + draft: true + generate_release_notes: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..c56a184 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,128 @@ +name: Tests + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + # Unit and integration tests with Vitest + unit-tests: + name: Unit Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run unit tests + run: bun run test + + - name: Run integration tests + run: bun run test:integration + + # E2E tests for web browsers + e2e-web: + name: E2E Web (${{ matrix.browser }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + browser: [chromium, firefox, webkit] + + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Install Playwright browsers + run: bunx playwright install --with-deps ${{ matrix.browser }} + + - name: Build application + run: bun run build + + - name: Run E2E tests + run: bunx playwright test --project=${{ matrix.browser }} + + - uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report-${{ matrix.browser }} + path: test-results/ + retention-days: 7 + + # Desktop E2E tests (Tauri) - only on main branch + e2e-desktop: + name: E2E Desktop + runs-on: ${{ matrix.os }} + if: github.ref == 'refs/heads/main' + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Install Playwright browsers + run: bunx playwright install chromium + + - name: Setup Rust (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + rustup update stable + rustup default stable + + - name: Setup Rust (macOS/Windows) + if: matrix.os != 'ubuntu-latest' + uses: dtolnay/rust-toolchain@stable + + - name: Install Tauri dependencies (Ubuntu) + if: matrix.os == 'ubuntu-latest' + run: | + sudo apt-get update + sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + + - name: Build Tauri app + run: bun run tauri:build + continue-on-error: true + + - name: Run desktop E2E tests + run: bun run test:e2e:desktop + continue-on-error: true + env: + TAURI: "true" + + # Coverage report + coverage: + name: Coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Generate coverage + run: bun run test:coverage + continue-on-error: true + + - name: Upload coverage + uses: codecov/codecov-action@v4 + if: always() + with: + fail_ci_if_error: false diff --git a/.gitignore b/.gitignore index 2a1cfef..57d6e1b 100755 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,7 @@ mobile-ui-references/ # directories tmp/ references/ +conversations-interface-references/ # capacitor native builds ios/App/Pods/ @@ -38,6 +39,7 @@ ios/App/build/ android/.gradle/ android/build/ android/app/build/ + # Local auth bypass (dev only) src/lib/auth-bypass.ts src/lib/cloudflare-context.ts diff --git a/__tests__/integration/sync.test.ts b/__tests__/integration/sync.test.ts new file mode 100644 index 0000000..76ad269 --- /dev/null +++ b/__tests__/integration/sync.test.ts @@ -0,0 +1,402 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from "vitest" +import { createMemoryProvider } from "@/db/provider/memory-provider" +import type { DatabaseProvider } from "@/db/provider/interface" +import { SyncEngine, createSyncEngine } from "@/lib/sync/engine" +import { ConflictStrategy } from "@/lib/sync/conflict" +import type { RemoteRecord } from "@/lib/sync/engine" + +// Sync schema table definitions for in-memory database +const SYNC_SCHEMA = ` + CREATE TABLE IF NOT EXISTS local_sync_metadata ( + id TEXT PRIMARY KEY, + table_name TEXT NOT NULL, + record_id TEXT NOT NULL, + vector_clock TEXT NOT NULL, + last_modified_at TEXT NOT NULL, + sync_status TEXT NOT NULL DEFAULT 'pending_sync', + conflict_data TEXT, + created_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS mutation_queue ( + id TEXT PRIMARY KEY, + operation TEXT NOT NULL, + table_name TEXT NOT NULL, + record_id TEXT NOT NULL, + payload TEXT, + vector_clock TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + retry_count INTEGER NOT NULL DEFAULT 0, + error_message TEXT, + created_at TEXT NOT NULL, + process_after TEXT + ); + + CREATE TABLE IF NOT EXISTS sync_checkpoint ( + id TEXT PRIMARY KEY, + table_name TEXT NOT NULL UNIQUE, + last_sync_cursor TEXT, + local_vector_clock TEXT, + synced_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS sync_tombstone ( + id TEXT PRIMARY KEY, + table_name TEXT NOT NULL, + record_id TEXT NOT NULL, + vector_clock TEXT NOT NULL, + deleted_at TEXT NOT NULL, + synced INTEGER NOT NULL DEFAULT 0 + ); +` + +// Helper to set up sync tables +async function setupSyncTables(provider: DatabaseProvider) { + const statements = SYNC_SCHEMA.split(";").filter((s) => s.trim()) + for (const stmt of statements) { + await provider.execute(stmt) + } +} + +// Integration tests for local-to-cloud sync using two MemoryProviders +// Simulates offline behavior and sync recovery + +describe("Sync Integration", () => { + let localProvider: DatabaseProvider + let cloudProvider: DatabaseProvider + let localEngine: SyncEngine + + // Simulated cloud data store + let cloudStore: Map + let cloudClocks: Map + + beforeEach(async () => { + localProvider = createMemoryProvider() + cloudProvider = createMemoryProvider() + await setupSyncTables(localProvider) + await setupSyncTables(cloudProvider) + cloudStore = new Map() + cloudClocks = new Map() + + localEngine = createSyncEngine(localProvider, { + clientId: "local-client", + conflictStrategy: ConflictStrategy.NEWEST_WINS, + tables: ["records"], + }) + await localEngine.initialize() + }) + + afterEach(async () => { + await localProvider.close?.() + await cloudProvider.close?.() + }) + + describe("local-to-cloud sync", () => { + it("syncs new local record to cloud", async () => { + // Create local record + await localEngine.recordMutation("records", "insert", "rec-1", { + id: "rec-1", + name: "Local Record", + value: 100, + }) + + // Mock cloud push that stores in cloudStore + const pushMutation = vi.fn().mockImplementation(async (_table, _op, id, payload, clock) => { + cloudStore.set(id, { + id, + ...payload, + updatedAt: new Date().toISOString(), + vectorClock: JSON.stringify(clock), + } as RemoteRecord) + return true + }) + + const result = await localEngine.push( + "records", + async () => ({}), + pushMutation + ) + + expect(result.pushed).toBe(1) + expect(cloudStore.has("rec-1")).toBe(true) + expect(cloudStore.get("rec-1")?.name).toBe("Local Record") + }) + + it("syncs new cloud record to local", async () => { + // Seed cloud with a record + cloudStore.set("rec-1", { + id: "rec-1", + name: "Cloud Record", + value: 200, + updatedAt: "2024-01-01T10:00:00Z", + vectorClock: JSON.stringify({ server: 1 }), + }) + + const fetchRemote = vi.fn().mockImplementation(async () => ({ + records: Array.from(cloudStore.values()), + nextCursor: "cursor-1", + })) + + const localRecords: Map = new Map() + const upsertLocal = vi.fn().mockImplementation(async (_id, data) => { + const rec = data as { id: string } + localRecords.set(rec.id, data) + return rec.id + }) + + const result = await localEngine.pull("records", fetchRemote, upsertLocal) + + expect(result.created).toBe(1) + expect(localRecords.has("rec-1")).toBe(true) + }) + + it("performs bidirectional sync", async () => { + // Seed cloud + cloudStore.set("cloud-1", { + id: "cloud-1", + name: "From Cloud", + value: 1, + updatedAt: "2024-01-01T10:00:00Z", + vectorClock: JSON.stringify({ server: 1 }), + }) + + // Create local + await localEngine.recordMutation("records", "insert", "local-1", { + id: "local-1", + name: "From Local", + value: 2, + }) + + const localRecords: Map = new Map() + + const fetchRemote = vi.fn().mockImplementation(async () => ({ + records: Array.from(cloudStore.values()), + nextCursor: "cursor-1", + })) + + const upsertLocal = vi.fn().mockImplementation(async (_id, data) => { + const rec = data as { id: string } + localRecords.set(rec.id, data) + return rec.id + }) + + const pushMutation = vi.fn().mockImplementation(async (_table, _op, id, payload, clock) => { + cloudStore.set(id, { + id, + ...payload, + updatedAt: new Date().toISOString(), + vectorClock: JSON.stringify(clock), + } as RemoteRecord) + return true + }) + + const result = await localEngine.sync( + "records", + fetchRemote, + upsertLocal, + async () => ({}), + pushMutation + ) + + // Pull: 1 record from cloud + // Push: 1 record to cloud + expect(result.pulled).toBe(1) + expect(result.pushed).toBe(1) + + // Verify both sides have both records + expect(localRecords.has("cloud-1")).toBe(true) + expect(cloudStore.has("local-1")).toBe(true) + }) + }) + + describe("offline queue behavior", () => { + it("queues mutations when offline (push fails)", async () => { + // Create multiple local records + for (let i = 0; i < 3; i++) { + await localEngine.recordMutation("records", "insert", `rec-${i}`, { + id: `rec-${i}`, + index: i, + }) + } + + // Simulate offline - push always fails + const pushMutation = vi.fn().mockRejectedValue(new Error("Network unavailable")) + + const result = await localEngine.push( + "records", + async () => ({}), + pushMutation + ) + + expect(result.failed).toBe(3) + expect(result.pushed).toBe(0) + expect(result.errors).toHaveLength(3) + }) + + it("maintains queue order (FIFO)", async () => { + const order: string[] = [] + + // Create records in order + await localEngine.recordMutation("records", "insert", "first", { id: "first" }) + await localEngine.recordMutation("records", "insert", "second", { id: "second" }) + await localEngine.recordMutation("records", "insert", "third", { id: "third" }) + + const pushMutation = vi.fn().mockImplementation(async (_table, _op, id) => { + order.push(id) + return true + }) + + await localEngine.push("records", async () => ({}), pushMutation) + + expect(order).toEqual(["first", "second", "third"]) + }) + + it("retries failed mutations", async () => { + await localEngine.recordMutation("records", "insert", "rec-1", { id: "rec-1" }) + + let attempts = 0 + const pushMutation = vi.fn().mockImplementation(async () => { + attempts++ + if (attempts < 3) { + throw new Error("Temporary failure") + } + return true + }) + + // First push fails + const result1 = await localEngine.push("records", async () => ({}), pushMutation) + expect(result1.failed).toBe(1) + + // Second push also fails (retry 1) + const result2 = await localEngine.push("records", async () => ({}), pushMutation) + expect(result2.failed).toBe(1) + + // Third push succeeds (retry 2) + const result3 = await localEngine.push("records", async () => ({}), pushMutation) + expect(result3.pushed).toBe(1) + + expect(attempts).toBe(3) + }) + }) + + describe("sync recovery after reconnect", () => { + it("recovers from offline state", async () => { + // Create local records while "offline" + await localEngine.recordMutation("records", "insert", "offline-1", { id: "offline-1" }) + await localEngine.recordMutation("records", "insert", "offline-2", { id: "offline-2" }) + + // First sync attempt - simulate offline + let isOnline = false + const pushMutation = vi.fn().mockImplementation(async () => { + if (!isOnline) { + throw new Error("Offline") + } + return true + }) + + const result1 = await localEngine.push("records", async () => ({}), pushMutation) + expect(result1.failed).toBe(2) + + // "Go online" + isOnline = true + + // Retry - should succeed now + const result2 = await localEngine.push("records", async () => ({}), pushMutation) + expect(result2.pushed).toBe(2) + }) + + it("handles partial sync (some succeed, some fail)", async () => { + // Create multiple records + await localEngine.recordMutation("records", "insert", "good-1", { id: "good-1" }) + await localEngine.recordMutation("records", "insert", "bad", { id: "bad" }) + await localEngine.recordMutation("records", "insert", "good-2", { id: "good-2" }) + + const pushMutation = vi.fn().mockImplementation(async (_table, _op, id) => { + if (id === "bad") { + throw new Error("Bad record") + } + return true + }) + + const result = await localEngine.push("records", async () => ({}), pushMutation) + + expect(result.pushed).toBe(2) + expect(result.failed).toBe(1) + expect(result.errors[0]?.recordId).toBe("bad") + }) + }) + + describe("conflict scenarios", () => { + it("detects concurrent modifications", async () => { + // Create local record + await localEngine.recordMutation("records", "insert", "shared", { + id: "shared", + name: "Local Version", + }) + + // Get local status + const localStatus = await localEngine.getRecordStatus("records", "shared") + expect(localStatus?.syncStatus).toBe("pending_sync") + + // Cloud has different version with concurrent clock + cloudStore.set("shared", { + id: "shared", + name: "Cloud Version", + updatedAt: "2024-01-01T10:00:00Z", + vectorClock: JSON.stringify({ otherClient: 1 }), // Concurrent with local + }) + + const fetchRemote = vi.fn().mockImplementation(async () => ({ + records: Array.from(cloudStore.values()), + nextCursor: "cursor-1", + })) + + const upsertLocal = vi.fn().mockResolvedValue("shared") + + // Pull with conflict detection + // Since local clock is { localClient: 1 } and remote is { otherClient: 1 } + // These are concurrent - conflict should be detected if local is pending + const result = await localEngine.pull("records", fetchRemote, upsertLocal) + + // Conflict detection depends on local sync status + // If pending_sync, concurrent clocks trigger conflict + expect(result.created + result.updated + result.conflicts).toBeGreaterThanOrEqual(1) + }) + + it("resolves conflict using configured strategy", async () => { + // Create engine with LOCAL_WINS strategy + const localWinsEngine = createSyncEngine(localProvider, { + clientId: "test-client", + conflictStrategy: ConflictStrategy.LOCAL_WINS, + tables: ["records"], + }) + await localWinsEngine.initialize() + + // Create local record + await localWinsEngine.recordMutation("records", "insert", "conflict-rec", { + id: "conflict-rec", + name: "Local Value", + value: 100, + }) + + // Cloud has concurrent version + cloudStore.set("conflict-rec", { + id: "conflict-rec", + name: "Cloud Value", + value: 200, + updatedAt: "2024-01-01T10:00:00Z", + vectorClock: JSON.stringify({ server: 5 }), // After local would be { "test-client": 1 } + }) + + // With LOCAL_WINS, conflicts should resolve using local data + // This is a high-level test - detailed conflict tests are in conflict.test.ts + await localWinsEngine.pull( + "records", + async () => ({ records: Array.from(cloudStore.values()), nextCursor: "c1" }), + async () => "id" + ) + + // Engine should handle the conflict according to LOCAL_WINS strategy + }) + }) +}) diff --git a/bun.lock b/bun.lock index 7ee7edf..9c5b4ae 100755 --- a/bun.lock +++ b/bun.lock @@ -59,20 +59,36 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tabler/icons-react": "^3.36.1", "@tanstack/react-table": "^8.21.3", + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-global-shortcut": "^2.0.0", + "@tauri-apps/plugin-http": "^2.0.0", + "@tauri-apps/plugin-sql": "^2.0.0", + "@tauri-apps/plugin-updater": "^2.0.0", + "@tauri-apps/plugin-window-state": "^2.0.0", + "@tiptap/extension-link": "^3.19.0", + "@tiptap/extension-mention": "^3.19.0", + "@tiptap/extension-placeholder": "^3.19.0", + "@tiptap/pm": "^3.19.0", + "@tiptap/react": "^3.19.0", + "@tiptap/starter-kit": "^3.19.0", "@workos-inc/authkit-nextjs": "^2.13.0", "@workos-inc/node": "^8.1.0", "@xyflow/react": "^12.10.0", "ai": "^6.0.73", + "better-sqlite3": "^11.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", + "dompurify": "^3.3.1", "drizzle-orm": "^0.45.1", "embla-carousel-react": "^8.6.0", "framer-motion": "11", "frappe-gantt": "^1.0.4", "input-otp": "^1.4.2", + "isomorphic-dompurify": "^3.0.0-rc.2", "lucide-react": "^0.563.0", + "marked": "^17.0.2", "motion": "^12.33.0", "nanoid": "^5.1.6", "next": "15.5.9", @@ -99,7 +115,12 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@playwright/test": "^1.41.0", "@tailwindcss/postcss": "^4", + "@tauri-apps/cli": "^2.0.0", + "@types/better-sqlite3": "^7.6.11", + "@types/dompurify": "^3.2.0", + "@types/jsdom": "^27.0.0", "@types/node": "^25.0.10", "@types/react": "^19", "@types/react-dom": "^19", @@ -116,6 +137,8 @@ }, }, "packages": { + "@acemir/cssom": ["@acemir/cssom@0.9.31", "", {}, "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.36", "", { "dependencies": { "@ai-sdk/provider": "3.0.7", "@ai-sdk/provider-utils": "4.0.13", "@vercel/oidc": "3.1.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2r1Q6azvqMYxQ1hqfWZmWg4+8MajoldD/ty65XdhCaCoBfvDu7trcvxXDfTSU+3/wZ1JIDky46SWYFOHnTbsBw=="], "@ai-sdk/provider": ["@ai-sdk/provider@3.0.7", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-VkPLrutM6VdA924/mG8OS+5frbVTcu6e046D2bgDo00tehBANR1QBJ/mPcZ9tXMFOsVcm6SQArOregxePzTFPw=="], @@ -126,6 +149,12 @@ "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], + "@asamuzakjp/css-color": ["@asamuzakjp/css-color@4.1.2", "", { "dependencies": { "@csstools/css-calc": "^3.0.0", "@csstools/css-color-parser": "^4.0.1", "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0", "lru-cache": "^11.2.5" } }, "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg=="], + + "@asamuzakjp/dom-selector": ["@asamuzakjp/dom-selector@6.7.8", "", { "dependencies": { "@asamuzakjp/nwsapi": "^2.3.9", "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", "lru-cache": "^11.2.5" } }, "sha512-stisC1nULNc9oH5lakAj8MH88ZxeGxzyWNDfbdCxvJSJIvDsHNZqYvscGTgy/ysgXWLJPt6K/4t0/GjvtKcFJQ=="], + + "@asamuzakjp/nwsapi": ["@asamuzakjp/nwsapi@2.3.9", "", {}, "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q=="], + "@ast-grep/napi": ["@ast-grep/napi@0.40.0", "", { "optionalDependencies": { "@ast-grep/napi-darwin-arm64": "0.40.0", "@ast-grep/napi-darwin-x64": "0.40.0", "@ast-grep/napi-linux-arm64-gnu": "0.40.0", "@ast-grep/napi-linux-arm64-musl": "0.40.0", "@ast-grep/napi-linux-x64-gnu": "0.40.0", "@ast-grep/napi-linux-x64-musl": "0.40.0", "@ast-grep/napi-win32-arm64-msvc": "0.40.0", "@ast-grep/napi-win32-ia32-msvc": "0.40.0", "@ast-grep/napi-win32-x64-msvc": "0.40.0" } }, "sha512-tq6nO/8KwUF/mHuk1ECaAOSOlz2OB/PmygnvprJzyAHGRVzdcffblaOOWe90M9sGz5MAasXoF+PTcayQj9TKKA=="], "@ast-grep/napi-darwin-arm64": ["@ast-grep/napi-darwin-arm64@0.40.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZMjl5yLhKjxdwbqEEdMizgQdWH2NrWsM6Px+JuGErgCDe6Aedq9yurEPV7veybGdLVJQhOah6htlSflXxjHnYA=="], @@ -306,6 +335,18 @@ "@cspotcode/source-map-support": ["@cspotcode/source-map-support@0.8.1", "", { "dependencies": { "@jridgewell/trace-mapping": "0.3.9" } }, "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw=="], + "@csstools/color-helpers": ["@csstools/color-helpers@6.0.1", "", {}, "sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ=="], + + "@csstools/css-calc": ["@csstools/css-calc@3.1.0", "", { "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-JWouqB5za07FUA2iXZWq4gPXNGWXjRwlfwEXNr7cSsGr7OKgzhDVwkJjlsrbqSyFmDGSi1Rt7zs8ln87jX9yRg=="], + + "@csstools/css-color-parser": ["@csstools/css-color-parser@4.0.1", "", { "dependencies": { "@csstools/color-helpers": "^6.0.1", "@csstools/css-calc": "^3.0.0" }, "peerDependencies": { "@csstools/css-parser-algorithms": "^4.0.0", "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw=="], + + "@csstools/css-parser-algorithms": ["@csstools/css-parser-algorithms@4.0.0", "", { "peerDependencies": { "@csstools/css-tokenizer": "^4.0.0" } }, "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w=="], + + "@csstools/css-syntax-patches-for-csstree": ["@csstools/css-syntax-patches-for-csstree@1.0.27", "", {}, "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow=="], + + "@csstools/css-tokenizer": ["@csstools/css-tokenizer@4.0.0", "", {}, "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA=="], + "@date-fns/tz": ["@date-fns/tz@1.4.1", "", {}, "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA=="], "@dnd-kit/accessibility": ["@dnd-kit/accessibility@3.1.1", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw=="], @@ -404,6 +445,8 @@ "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], + "@exodus/bytes": ["@exodus/bytes@1.14.0", "", { "peerDependencies": { "@noble/hashes": "^1.8.0 || ^2.0.0" }, "optionalPeers": ["@noble/hashes"] }, "sha512-YiY1OmY6Qhkvmly8vZiD8wZRpW/npGZNg+0Sk8mstxirRHCg6lolHt5tSODCfuNPE/fBsAqRwDJE417x7jDDHA=="], + "@floating-ui/core": ["@floating-ui/core@1.7.3", "", { "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w=="], "@floating-ui/dom": ["@floating-ui/dom@1.7.4", "", { "dependencies": { "@floating-ui/core": "^1.7.3", "@floating-ui/utils": "^0.2.10" } }, "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA=="], @@ -568,6 +611,8 @@ "@peculiar/webcrypto": ["@peculiar/webcrypto@1.5.0", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.8", "@peculiar/json-schema": "^1.1.12", "pvtsutils": "^1.3.5", "tslib": "^2.6.2", "webcrypto-core": "^1.8.0" } }, "sha512-BRs5XUAwiyCDQMsVA9IDvDa7UBR9gAvPHgugOeGng3YN6vJ9JYonyDc0lNczErgtCWtucjR5N7VtaonboD/ezg=="], + "@playwright/test": ["@playwright/test@1.58.2", "", { "dependencies": { "playwright": "1.58.2" }, "bin": { "playwright": "cli.js" } }, "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA=="], + "@poppinss/colors": ["@poppinss/colors@4.1.6", "", { "dependencies": { "kleur": "^4.1.5" } }, "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg=="], "@poppinss/dumper": ["@poppinss/dumper@0.6.5", "", { "dependencies": { "@poppinss/colors": "^4.1.5", "@sindresorhus/is": "^7.0.2", "supports-color": "^10.0.0" } }, "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw=="], @@ -694,6 +739,8 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@remirror/core-constants": ["@remirror/core-constants@3.0.0", "", {}, "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], @@ -912,6 +959,104 @@ "@tanstack/table-core": ["@tanstack/table-core@8.21.3", "", {}, "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg=="], + "@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="], + + "@tauri-apps/cli": ["@tauri-apps/cli@2.10.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.0", "@tauri-apps/cli-darwin-x64": "2.10.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", "@tauri-apps/cli-linux-arm64-musl": "2.10.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-musl": "2.10.0", "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", "@tauri-apps/cli-win32-x64-msvc": "2.10.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q=="], + + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA=="], + + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q=="], + + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.0", "", { "os": "linux", "cpu": "arm" }, "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g=="], + + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA=="], + + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA=="], + + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.0", "", { "os": "linux", "cpu": "none" }, "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw=="], + + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng=="], + + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q=="], + + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g=="], + + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A=="], + + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ=="], + + "@tauri-apps/plugin-global-shortcut": ["@tauri-apps/plugin-global-shortcut@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-vr40W2N6G63dmBPaha1TsBQLLURXG538RQbH5vAm0G/ovVZyXJrmZR1HF1W+WneNloQvwn4dm8xzwpEXRW560g=="], + + "@tauri-apps/plugin-http": ["@tauri-apps/plugin-http@2.5.7", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-+F2lEH/c9b0zSsOXKq+5hZNcd9F4IIKCK1T17RqMwpCmVnx2aoqY8yIBccCd25HTYUb3j6NPVbRax/m00hKG8A=="], + + "@tauri-apps/plugin-sql": ["@tauri-apps/plugin-sql@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-4VDXhcKXVpyh5KKpnTGAn6q2DikPHH+TXGh9ZDQzULmG/JEz1RDvzQStgBJKddiukRbYEZ8CGIA2kskx+T+PpA=="], + + "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ=="], + + "@tauri-apps/plugin-window-state": ["@tauri-apps/plugin-window-state@2.4.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-OuvdrzyY8Q5Dbzpj+GcrnV1iCeoZbcFdzMjanZMMcAEUNy/6PH5pxZPXpaZLOR7whlzXiuzx0L9EKZbH7zpdRw=="], + + "@tiptap/core": ["@tiptap/core@3.19.0", "", { "peerDependencies": { "@tiptap/pm": "^3.19.0" } }, "sha512-bpqELwPW+DG8gWiD8iiFtSl4vIBooG5uVJod92Qxn3rA9nFatyXRr4kNbMJmOZ66ezUvmCjXVe/5/G4i5cyzKA=="], + + "@tiptap/extension-blockquote": ["@tiptap/extension-blockquote@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-y3UfqY9KD5XwWz3ndiiJ089Ij2QKeiXy/g1/tlAN/F1AaWsnkHEHMLxCP1BIqmMpwsX7rZjMLN7G5Lp7c9682A=="], + + "@tiptap/extension-bold": ["@tiptap/extension-bold@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-UZgb1d0XK4J/JRIZ7jW+s4S6KjuEDT2z1PPM6ugcgofgJkWQvRZelCPbmtSFd3kwsD+zr9UPVgTh9YIuGQ8t+Q=="], + + "@tiptap/extension-bubble-menu": ["@tiptap/extension-bubble-menu@3.19.0", "", { "dependencies": { "@floating-ui/dom": "^1.0.0" }, "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-klNVIYGCdznhFkrRokzGd6cwzoi8J7E5KbuOfZBwFwhMKZhlz/gJfKmYg9TJopeUhrr2Z9yHgWTk8dh/YIJCdQ=="], + + "@tiptap/extension-bullet-list": ["@tiptap/extension-bullet-list@3.19.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.19.0" } }, "sha512-F9uNnqd0xkJbMmRxVI5RuVxwB9JaCH/xtRqOUNQZnRBt7IdAElCY+Dvb4hMCtiNv+enGM/RFGJuFHR9TxmI7rw=="], + + "@tiptap/extension-code": ["@tiptap/extension-code@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-2kqqQIXBXj2Or+4qeY3WoE7msK+XaHKL6EKOcKlOP2BW8eYqNTPzNSL+PfBDQ3snA7ljZQkTs/j4GYDj90vR1A=="], + + "@tiptap/extension-code-block": ["@tiptap/extension-code-block@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-b/2qR+tMn8MQb+eaFYgVk4qXnLNkkRYmwELQ8LEtEDQPxa5Vl7J3eu8+4OyoIFhZrNDZvvoEp80kHMCP8sI6rg=="], + + "@tiptap/extension-document": ["@tiptap/extension-document@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-AOf0kHKSFO0ymjVgYSYDncRXTITdTcrj1tqxVazrmO60KNl1Rc2dAggDvIVTEBy5NvceF0scc7q3sE/5ZtVV7A=="], + + "@tiptap/extension-dropcursor": ["@tiptap/extension-dropcursor@3.19.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.19.0" } }, "sha512-sf3dEZXiLvsGqVK2maUIzXY6qtYYCvBumag7+VPTMGQ0D4hiZ1X/4ukt4+6VXDg5R2WP1CoIt/QvUetUjWNhbQ=="], + + "@tiptap/extension-floating-menu": ["@tiptap/extension-floating-menu@3.19.0", "", { "peerDependencies": { "@floating-ui/dom": "^1.0.0", "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-JaoEkVRkt+Slq3tySlIsxnMnCjS0L5n1CA1hctjLy0iah8edetj3XD5mVv5iKqDzE+LIjF4nwLRRVKJPc8hFBg=="], + + "@tiptap/extension-gapcursor": ["@tiptap/extension-gapcursor@3.19.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.19.0" } }, "sha512-w7DACS4oSZaDWjz7gropZHPc9oXqC9yERZTcjWxyORuuIh1JFf0TRYspleK+OK28plK/IftojD/yUDn1MTRhvA=="], + + "@tiptap/extension-hard-break": ["@tiptap/extension-hard-break@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-lAmQraYhPS5hafvCl74xDB5+bLuNwBKIEsVoim35I0sDJj5nTrfhaZgMJ91VamMvT+6FF5f1dvBlxBxAWa8jew=="], + + "@tiptap/extension-heading": ["@tiptap/extension-heading@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-uLpLlfyp086WYNOc0ekm1gIZNlEDfmzOhKzB0Hbyi6jDagTS+p9mxUNYeYOn9jPUxpFov43+Wm/4E24oY6B+TQ=="], + + "@tiptap/extension-horizontal-rule": ["@tiptap/extension-horizontal-rule@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-iqUHmgMGhMgYGwG6L/4JdelVQ5Mstb4qHcgTGd/4dkcUOepILvhdxajPle7OEdf9sRgjQO6uoAU5BVZVC26+ng=="], + + "@tiptap/extension-italic": ["@tiptap/extension-italic@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-6GffxOnS/tWyCbDkirWNZITiXRta9wrCmrfa4rh+v32wfaOL1RRQNyqo9qN6Wjyl1R42Js+yXTzTTzZsOaLMYA=="], + + "@tiptap/extension-link": ["@tiptap/extension-link@3.19.0", "", { "dependencies": { "linkifyjs": "^4.3.2" }, "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-HEGDJnnCPfr7KWu7Dsq+eRRe/mBCsv6DuI+7fhOCLDJjjKzNgrX2abbo/zG3D/4lCVFaVb+qawgJubgqXR/Smw=="], + + "@tiptap/extension-list": ["@tiptap/extension-list@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-N6nKbFB2VwMsPlCw67RlAtYSK48TAsAUgjnD+vd3ieSlIufdQnLXDFUP6hFKx9mwoUVUgZGz02RA6bkxOdYyTw=="], + + "@tiptap/extension-list-item": ["@tiptap/extension-list-item@3.19.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.19.0" } }, "sha512-VsSKuJz4/Tb6ZmFkXqWpDYkRzmaLTyE6dNSEpNmUpmZ32sMqo58mt11/huADNwfBFB0Ve7siH/VnFNIJYY3xvg=="], + + "@tiptap/extension-list-keymap": ["@tiptap/extension-list-keymap@3.19.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.19.0" } }, "sha512-bxgmAgA3RzBGA0GyTwS2CC1c+QjkJJq9hC+S6PSOWELGRiTbwDN3MANksFXLjntkTa0N5fOnL27vBHtMStURqw=="], + + "@tiptap/extension-mention": ["@tiptap/extension-mention@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0", "@tiptap/suggestion": "^3.19.0" } }, "sha512-iBWX6mUouvDe9F75C2fJnFzvBFYVF8fcOa7UvzqWHRSCt8WxqSIp6C1B9Y0npP4TbIZySHzPV4NQQJhtmWwKww=="], + + "@tiptap/extension-ordered-list": ["@tiptap/extension-ordered-list@3.19.0", "", { "peerDependencies": { "@tiptap/extension-list": "^3.19.0" } }, "sha512-cxGsINquwHYE1kmhAcLNLHAofmoDEG6jbesR5ybl7tU5JwtKVO7S/xZatll2DU1dsDAXWPWEeeMl4e/9svYjCg=="], + + "@tiptap/extension-paragraph": ["@tiptap/extension-paragraph@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-xWa6gj82l5+AzdYyrSk9P4ynySaDzg/SlR1FarXE5yPXibYzpS95IWaVR0m2Qaz7Rrk+IiYOTGxGRxcHLOelNg=="], + + "@tiptap/extension-placeholder": ["@tiptap/extension-placeholder@3.19.0", "", { "peerDependencies": { "@tiptap/extensions": "^3.19.0" } }, "sha512-i15OfgyI4IDCYAcYSKUMnuZkYuUInfanjf9zquH8J2BETiomf/jZldVCp/QycMJ8DOXZ38fXDc99wOygnSNySg=="], + + "@tiptap/extension-strike": ["@tiptap/extension-strike@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-xYpabHsv7PccLUBQaP8AYiFCnYbx6P93RHPd0lgNwhdOjYFd931Zy38RyoxPHAgbYVmhf1iyx7lpuLtBnhS5dA=="], + + "@tiptap/extension-text": ["@tiptap/extension-text@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-K95+SnbZy0h6hNFtfy23n8t/nOcTFEf69In9TSFVVmwn/Nwlke+IfiESAkqbt1/7sKJeegRXYO7WzFEmFl9Q/g=="], + + "@tiptap/extension-underline": ["@tiptap/extension-underline@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0" } }, "sha512-800MGEWfG49j10wQzAFiW/ele1HT04MamcL8iyuPNu7ZbjbGN2yknvdrJlRy7hZlzIrVkZMr/1tz62KN33VHIw=="], + + "@tiptap/extensions": ["@tiptap/extensions@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-ZmGUhLbMWaGqnJh2Bry+6V4M6gMpUDYo4D1xNux5Gng/E/eYtc+PMxMZ/6F7tNTAuujLBOQKj6D+4SsSm457jw=="], + + "@tiptap/pm": ["@tiptap/pm@3.19.0", "", { "dependencies": { "prosemirror-changeset": "^2.3.0", "prosemirror-collab": "^1.3.1", "prosemirror-commands": "^1.6.2", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", "prosemirror-inputrules": "^1.4.0", "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.1", "prosemirror-menu": "^1.2.4", "prosemirror-model": "^1.24.1", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.5.0", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.6.4", "prosemirror-trailing-node": "^3.0.0", "prosemirror-transform": "^1.10.2", "prosemirror-view": "^1.38.1" } }, "sha512-789zcnM4a8OWzvbD2DL31d0wbSm9BVeO/R7PLQwLIGysDI3qzrcclyZ8yhqOEVuvPitRRwYLq+mY14jz7kY4cw=="], + + "@tiptap/react": ["@tiptap/react@3.19.0", "", { "dependencies": { "@types/use-sync-external-store": "^0.0.6", "fast-equals": "^5.3.3", "use-sync-external-store": "^1.4.0" }, "optionalDependencies": { "@tiptap/extension-bubble-menu": "^3.19.0", "@tiptap/extension-floating-menu": "^3.19.0" }, "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0", "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", "react": "^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-GQQMUUXMpNd8tRjc1jDK3tDRXFugJO7C928EqmeBcBzTKDrFIJ3QUoZKEPxUNb6HWhZ2WL7q00fiMzsv4DNSmg=="], + + "@tiptap/starter-kit": ["@tiptap/starter-kit@3.19.0", "", { "dependencies": { "@tiptap/core": "^3.19.0", "@tiptap/extension-blockquote": "^3.19.0", "@tiptap/extension-bold": "^3.19.0", "@tiptap/extension-bullet-list": "^3.19.0", "@tiptap/extension-code": "^3.19.0", "@tiptap/extension-code-block": "^3.19.0", "@tiptap/extension-document": "^3.19.0", "@tiptap/extension-dropcursor": "^3.19.0", "@tiptap/extension-gapcursor": "^3.19.0", "@tiptap/extension-hard-break": "^3.19.0", "@tiptap/extension-heading": "^3.19.0", "@tiptap/extension-horizontal-rule": "^3.19.0", "@tiptap/extension-italic": "^3.19.0", "@tiptap/extension-link": "^3.19.0", "@tiptap/extension-list": "^3.19.0", "@tiptap/extension-list-item": "^3.19.0", "@tiptap/extension-list-keymap": "^3.19.0", "@tiptap/extension-ordered-list": "^3.19.0", "@tiptap/extension-paragraph": "^3.19.0", "@tiptap/extension-strike": "^3.19.0", "@tiptap/extension-text": "^3.19.0", "@tiptap/extension-underline": "^3.19.0", "@tiptap/extensions": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-dTCkHEz+Y8ADxX7h+xvl6caAj+3nII/wMB1rTQchSuNKqJTOrzyUsCWm094+IoZmLT738wANE0fRIgziNHs/ug=="], + + "@tiptap/suggestion": ["@tiptap/suggestion@3.19.0", "", { "peerDependencies": { "@tiptap/core": "^3.19.0", "@tiptap/pm": "^3.19.0" } }, "sha512-tUZwMRFqTVPIo566ZmHNRteyZxJy2EE4FA+S3IeIUOOvY6AW0h1imhbpBO7sXV8CeEQvpa+2DWwLvy7L3vmstA=="], + "@tokenlens/core": ["@tokenlens/core@1.3.0", "", {}, "sha512-d8YNHNC+q10bVpi95fELJwJyPVf1HfvBEI18eFQxRSZTdByXrP+f/ZtlhSzkx0Jl0aEmYVeBA5tPeeYRioLViQ=="], "@tokenlens/fetch": ["@tokenlens/fetch@1.3.0", "", { "dependencies": { "@tokenlens/core": "1.3.0" } }, "sha512-RONDRmETYly9xO8XMKblmrZjKSwCva4s5ebJwQNfNlChZoA5kplPoCgnWceHnn1J1iRjLVlrCNB43ichfmGBKQ=="], @@ -926,6 +1071,8 @@ "@types/accepts": ["@types/accepts@1.3.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ=="], + "@types/better-sqlite3": ["@types/better-sqlite3@7.6.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA=="], + "@types/body-parser": ["@types/body-parser@1.19.6", "", { "dependencies": { "@types/connect": "*", "@types/node": "*" } }, "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g=="], "@types/chai": ["@types/chai@5.2.3", "", { "dependencies": { "@types/deep-eql": "*", "assertion-error": "^2.0.1" } }, "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA=="], @@ -968,6 +1115,8 @@ "@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="], + "@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="], + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], "@types/estree-jsx": ["@types/estree-jsx@1.0.5", "", { "dependencies": { "@types/estree": "*" } }, "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg=="], @@ -984,6 +1133,8 @@ "@types/http-errors": ["@types/http-errors@2.0.5", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="], + "@types/jsdom": ["@types/jsdom@27.0.0", "", { "dependencies": { "@types/node": "*", "@types/tough-cookie": "*", "parse5": "^7.0.0" } }, "sha512-NZyFl/PViwKzdEkQg96gtnB8wm+1ljhdDay9ahn4hgb+SfVtPCbm3TlmDUFXTA+MGN3CijicnMhG18SI5H3rFw=="], + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], @@ -994,8 +1145,14 @@ "@types/koa-compose": ["@types/koa-compose@3.2.9", "", { "dependencies": { "@types/koa": "*" } }, "sha512-BroAZ9FTvPiCy0Pi8tjD1OfJ7bgU1gQf0eR6e1Vm+JJATy9eKOG3hQMFtMciMawiSOVnLMdmUOC46s7HBhSTsA=="], + "@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="], + + "@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], + "@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="], + "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], @@ -1018,8 +1175,14 @@ "@types/slice-ansi": ["@types/slice-ansi@4.0.0", "", {}, "sha512-+OpjSaq85gvlZAYINyzKpLeiFkSC4EsC6IIiT6v6TLSU5k5U83fHGj9Lel8oKEXM0HqgrMVCjXPDPVICtxF7EQ=="], + "@types/tough-cookie": ["@types/tough-cookie@4.0.5", "", {}, "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA=="], + + "@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="], + "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], + "@types/use-sync-external-store": ["@types/use-sync-external-store@0.0.6", "", {}, "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.53.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/type-utils": "8.53.1", "@typescript-eslint/utils": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.53.1", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag=="], "@typescript-eslint/parser": ["@typescript-eslint/parser@8.53.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.53.1", "@typescript-eslint/types": "8.53.1", "@typescript-eslint/typescript-estree": "8.53.1", "@typescript-eslint/visitor-keys": "8.53.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg=="], @@ -1114,6 +1277,8 @@ "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], "ai": ["ai@6.0.73", "", { "dependencies": { "@ai-sdk/gateway": "3.0.36", "@ai-sdk/provider": "3.0.7", "@ai-sdk/provider-utils": "4.0.13", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-p2/ICXIjAM4+bIFHEkAB+l58zq+aTmxAkotsb6doNt/CEms72zt6gxv2ky1fQDwU4ecMOcmMh78VJUSEKECzlg=="], @@ -1176,8 +1341,16 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + "better-sqlite3": ["better-sqlite3@11.10.0", "", { "dependencies": { "bindings": "^1.5.0", "prebuild-install": "^7.1.1" } }, "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ=="], + + "bidi-js": ["bidi-js@1.0.3", "", { "dependencies": { "require-from-string": "^2.0.2" } }, "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw=="], + "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="], + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "blake3-wasm": ["blake3-wasm@2.1.5", "", {}, "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g=="], "body-parser": ["body-parser@2.2.2", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.3", "http-errors": "^2.0.0", "iconv-lite": "^0.7.0", "on-finished": "^2.4.1", "qs": "^6.14.1", "raw-body": "^3.0.1", "type-is": "^2.0.1" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], @@ -1260,8 +1433,14 @@ "cookie-signature": ["cookie-signature@1.2.2", "", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + "crelt": ["crelt@1.0.6", "", {}, "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g=="], + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="], + + "cssstyle": ["cssstyle@5.3.7", "", { "dependencies": { "@asamuzakjp/css-color": "^4.1.1", "@csstools/css-syntax-patches-for-csstree": "^1.0.21", "css-tree": "^3.1.0", "lru-cache": "^11.2.4" } }, "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ=="], + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], "d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="], @@ -1298,6 +1477,8 @@ "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], + "data-urls": ["data-urls@7.0.0", "", { "dependencies": { "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0" } }, "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA=="], + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], @@ -1310,10 +1491,16 @@ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "decimal.js": ["decimal.js@10.6.0", "", {}, "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg=="], + "decimal.js-light": ["decimal.js-light@2.5.1", "", {}, "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="], "decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="], + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], @@ -1338,6 +1525,8 @@ "dom-helpers": ["dom-helpers@5.2.1", "", { "dependencies": { "@babel/runtime": "^7.8.7", "csstype": "^3.0.2" } }, "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA=="], + "dompurify": ["dompurify@3.3.1", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q=="], + "dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="], "drizzle-kit": ["drizzle-kit@0.31.8", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "esbuild-register": "^3.5.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-O9EC/miwdnRDY10qRxM8P3Pg8hXe3LyU4ZipReKOgTwn4OqANmftj8XJz1UPUAS6NMHf0E2htjsbQujUTkncCg=="], @@ -1366,6 +1555,8 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + "enhanced-resolve": ["enhanced-resolve@5.18.4", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q=="], "enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="], @@ -1450,6 +1641,8 @@ "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="], + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + "expect-type": ["expect-type@1.3.0", "", {}, "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA=="], "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], @@ -1476,6 +1669,8 @@ "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], "finalhandler": ["finalhandler@2.1.1", "", { "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], @@ -1504,6 +1699,8 @@ "fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + "fs-extra": ["fs-extra@11.3.3", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], @@ -1534,6 +1731,8 @@ "get-tsconfig": ["get-tsconfig@4.13.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ=="], + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + "glob": ["glob@12.0.0", "", { "dependencies": { "foreground-child": "^3.3.1", "jackspeak": "^4.1.1", "minimatch": "^10.1.1", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" } }, "sha512-5Qcll1z7IKgHr5g485ePDdHcNQY0k2dtv/bjYy0iuyGxQw2qSOiiXUXJ+AYQpg3HNoUMHqAruX478Jeev7UULw=="], "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], @@ -1580,12 +1779,18 @@ "hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="], + "html-encoding-sniffer": ["html-encoding-sniffer@6.0.0", "", { "dependencies": { "@exodus/bytes": "^1.6.0" } }, "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg=="], + "html-url-attributes": ["html-url-attributes@3.0.1", "", {}, "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ=="], "html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="], "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], + + "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="], + "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="], "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], @@ -1670,6 +1875,8 @@ "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + "is-potential-custom-element-name": ["is-potential-custom-element-name@1.0.1", "", {}, "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ=="], + "is-promise": ["is-promise@4.0.0", "", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], @@ -1698,6 +1905,8 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "isomorphic-dompurify": ["isomorphic-dompurify@3.0.0-rc.2", "", { "dependencies": { "dompurify": "^3.3.1", "jsdom": "^28.0.0" } }, "sha512-krCa8psVRnfeJxZnCk+USqMKvcDOkQCGbAeFXQwaJiIcRFOGp9GDa2h06QzVIdVbM+onpro2Vg4uLe2RHI1McA=="], + "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="], "jackspeak": ["jackspeak@4.1.1", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" } }, "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ=="], @@ -1710,6 +1919,8 @@ "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "jsdom": ["jsdom@28.0.0", "", { "dependencies": { "@acemir/cssom": "^0.9.31", "@asamuzakjp/dom-selector": "^6.7.6", "@exodus/bytes": "^1.11.0", "cssstyle": "^5.3.7", "data-urls": "^7.0.0", "decimal.js": "^10.6.0", "html-encoding-sniffer": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "is-potential-custom-element-name": "^1.0.1", "parse5": "^8.0.0", "saxes": "^6.0.0", "symbol-tree": "^3.2.4", "tough-cookie": "^6.0.0", "undici": "^7.20.0", "w3c-xmlserializer": "^5.0.0", "webidl-conversions": "^8.0.1", "whatwg-mimetype": "^5.0.0", "whatwg-url": "^16.0.0", "xml-name-validator": "^5.0.0" }, "peerDependencies": { "canvas": "^3.0.0" }, "optionalPeers": ["canvas"] }, "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA=="], + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], @@ -1760,6 +1971,10 @@ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], + "linkify-it": ["linkify-it@5.0.0", "", { "dependencies": { "uc.micro": "^2.0.0" } }, "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ=="], + + "linkifyjs": ["linkifyjs@4.3.2", "", {}, "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA=="], + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], "lodash": ["lodash@4.17.23", "", {}, "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w=="], @@ -1770,15 +1985,17 @@ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], - "lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], + "lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="], "lucide-react": ["lucide-react@0.563.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-8dXPB2GI4dI8jV4MgUDGBeLdGk8ekfqVZ0BdLcrRzocGgG75ltNEmWS+gE7uokKF/0oSUuczNDT+g9hFJ23FkA=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "markdown-it": ["markdown-it@14.1.0", "", { "dependencies": { "argparse": "^2.0.1", "entities": "^4.4.0", "linkify-it": "^5.0.0", "mdurl": "^2.0.0", "punycode.js": "^2.3.1", "uc.micro": "^2.1.0" }, "bin": { "markdown-it": "bin/markdown-it.mjs" } }, "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg=="], + "markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="], - "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], + "marked": ["marked@17.0.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-s5HZGFQea7Huv5zZcAGhJLT3qLpAfnY7v7GWkICUr0+Wd5TFEtdlRR2XUL5Gg+RH7u2Df595ifrxR03mBaw7gA=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -1812,6 +2029,10 @@ "mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="], + "mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="], + + "mdurl": ["mdurl@2.0.0", "", {}, "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w=="], + "media-chrome": ["media-chrome@4.17.2", "", { "dependencies": { "ce-la-react": "^0.3.2" } }, "sha512-o/IgiHx0tdSVwRxxqF5H12FK31A/A8T71sv3KdAvh7b6XeBS9dXwqvIFwlR9kdEuqg3n7xpmRIuL83rmYq8FTg=="], "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], @@ -1886,6 +2107,8 @@ "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="], + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + "miniflare": ["miniflare@4.20260116.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.18.2", "workerd": "1.20260116.0", "ws": "8.18.0", "youch": "4.1.0-beta.10", "zod": "^3.25.76" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-fCU1thOdiKfcauYp/gAchhesOTqTPy3K7xY6g72RiJ2xkna18QJ3Mh5sgDmnqlOEqSW9vpmYeK8vd/aqkrtlUA=="], "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], @@ -1898,6 +2121,8 @@ "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + "mnemonist": ["mnemonist@0.38.3", "", { "dependencies": { "obliterator": "^1.6.1" } }, "sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw=="], "motion": ["motion@12.33.0", "", { "dependencies": { "framer-motion": "^12.33.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-TcND7PijsrTeIA9SRVUB8TOJQ+6mJnJ5K4a9oAJZvyI0Zy47Gq5oofU+VkTxbLcvDoKXnHspQcII2mnk3TbFsQ=="], @@ -1910,6 +2135,8 @@ "nanoid": ["nanoid@5.1.6", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg=="], + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + "napi-postinstall": ["napi-postinstall@0.3.4", "", { "bin": { "napi-postinstall": "lib/cli.js" } }, "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ=="], "native-run": ["native-run@2.0.3", "", { "dependencies": { "@ionic/utils-fs": "^3.1.7", "@ionic/utils-terminal": "^2.3.4", "bplist-parser": "^0.3.2", "debug": "^4.3.4", "elementtree": "^0.1.7", "ini": "^4.1.1", "plist": "^3.1.0", "split2": "^4.2.0", "through2": "^4.0.2", "tslib": "^2.6.2", "yauzl": "^2.10.0" }, "bin": { "native-run": "bin/native-run" } }, "sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q=="], @@ -1922,6 +2149,8 @@ "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], + "node-abi": ["node-abi@3.87.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ=="], + "node-domexception": ["node-domexception@1.0.0", "", {}, "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="], "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="], @@ -1964,6 +2193,8 @@ "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + "orderedmap": ["orderedmap@2.1.1", "", {}, "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g=="], + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], @@ -1998,12 +2229,18 @@ "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + "playwright": ["playwright@1.58.2", "", { "dependencies": { "playwright-core": "1.58.2" }, "optionalDependencies": { "fsevents": "2.3.2" }, "bin": { "playwright": "cli.js" } }, "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A=="], + + "playwright-core": ["playwright-core@1.58.2", "", { "bin": { "playwright-core": "cli.js" } }, "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg=="], + "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], @@ -2012,10 +2249,50 @@ "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], + "prosemirror-changeset": ["prosemirror-changeset@2.3.1", "", { "dependencies": { "prosemirror-transform": "^1.0.0" } }, "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ=="], + + "prosemirror-collab": ["prosemirror-collab@1.3.1", "", { "dependencies": { "prosemirror-state": "^1.0.0" } }, "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ=="], + + "prosemirror-commands": ["prosemirror-commands@1.7.1", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.10.2" } }, "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w=="], + + "prosemirror-dropcursor": ["prosemirror-dropcursor@1.8.2", "", { "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0", "prosemirror-view": "^1.1.0" } }, "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw=="], + + "prosemirror-gapcursor": ["prosemirror-gapcursor@1.4.0", "", { "dependencies": { "prosemirror-keymap": "^1.0.0", "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-view": "^1.0.0" } }, "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ=="], + + "prosemirror-history": ["prosemirror-history@1.5.0", "", { "dependencies": { "prosemirror-state": "^1.2.2", "prosemirror-transform": "^1.0.0", "prosemirror-view": "^1.31.0", "rope-sequence": "^1.3.0" } }, "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg=="], + + "prosemirror-inputrules": ["prosemirror-inputrules@1.5.1", "", { "dependencies": { "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.0.0" } }, "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw=="], + + "prosemirror-keymap": ["prosemirror-keymap@1.2.3", "", { "dependencies": { "prosemirror-state": "^1.0.0", "w3c-keyname": "^2.2.0" } }, "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw=="], + + "prosemirror-markdown": ["prosemirror-markdown@1.13.4", "", { "dependencies": { "@types/markdown-it": "^14.0.0", "markdown-it": "^14.0.0", "prosemirror-model": "^1.25.0" } }, "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw=="], + + "prosemirror-menu": ["prosemirror-menu@1.2.5", "", { "dependencies": { "crelt": "^1.0.0", "prosemirror-commands": "^1.0.0", "prosemirror-history": "^1.0.0", "prosemirror-state": "^1.0.0" } }, "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ=="], + + "prosemirror-model": ["prosemirror-model@1.25.4", "", { "dependencies": { "orderedmap": "^2.0.0" } }, "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA=="], + + "prosemirror-schema-basic": ["prosemirror-schema-basic@1.2.4", "", { "dependencies": { "prosemirror-model": "^1.25.0" } }, "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ=="], + + "prosemirror-schema-list": ["prosemirror-schema-list@1.5.1", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.7.3" } }, "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q=="], + + "prosemirror-state": ["prosemirror-state@1.4.4", "", { "dependencies": { "prosemirror-model": "^1.0.0", "prosemirror-transform": "^1.0.0", "prosemirror-view": "^1.27.0" } }, "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw=="], + + "prosemirror-tables": ["prosemirror-tables@1.8.5", "", { "dependencies": { "prosemirror-keymap": "^1.2.3", "prosemirror-model": "^1.25.4", "prosemirror-state": "^1.4.4", "prosemirror-transform": "^1.10.5", "prosemirror-view": "^1.41.4" } }, "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw=="], + + "prosemirror-trailing-node": ["prosemirror-trailing-node@3.0.0", "", { "dependencies": { "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { "prosemirror-model": "^1.22.1", "prosemirror-state": "^1.4.2", "prosemirror-view": "^1.33.8" } }, "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ=="], + + "prosemirror-transform": ["prosemirror-transform@1.11.0", "", { "dependencies": { "prosemirror-model": "^1.21.0" } }, "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw=="], + + "prosemirror-view": ["prosemirror-view@1.41.6", "", { "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", "prosemirror-transform": "^1.1.0" } }, "sha512-mxpcDG4hNQa/CPtzxjdlir5bJFDlm0/x5nGBbStB2BWX+XOQ9M8ekEG+ojqB5BcVu2Rc80/jssCMZzSstJuSYg=="], + "proxy-addr": ["proxy-addr@2.0.7", "", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + "pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="], + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + "punycode.js": ["punycode.js@2.3.1", "", {}, "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA=="], + "pvtsutils": ["pvtsutils@1.3.6", "", { "dependencies": { "tslib": "^2.8.1" } }, "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg=="], "pvutils": ["pvutils@1.1.5", "", {}, "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA=="], @@ -2030,6 +2307,8 @@ "raw-body": ["raw-body@3.0.2", "", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + "react": ["react@19.1.4", "", {}, "sha512-DHINL3PAmPUiK1uszfbKiXqfE03eszdt5BpVSuEAHb5nfmNPwnsy7g39h2t8aXFc/Bv99GH81s+j8dobtD+jOw=="], "react-day-picker": ["react-day-picker@9.13.0", "", { "dependencies": { "@date-fns/tz": "^1.4.1", "date-fns": "^4.1.0", "date-fns-jalali": "^4.1.0-0" }, "peerDependencies": { "react": ">=16.8.0" } }, "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ=="], @@ -2088,6 +2367,8 @@ "remend": ["remend@1.1.0", "", {}, "sha512-JENGyuIhTwzUfCarW43X4r9cehoqTo9QyYxfNDZSud2AmqeuWjZ5pfybasTa4q0dxTJAj5m8NB+wR+YueAFpxQ=="], + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="], "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], @@ -2100,6 +2381,8 @@ "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], + "rope-sequence": ["rope-sequence@1.3.4", "", {}, "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ=="], + "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], @@ -2116,6 +2399,8 @@ "sax": ["sax@1.4.4", "", {}, "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw=="], + "saxes": ["saxes@6.0.0", "", { "dependencies": { "xmlchars": "^2.2.0" } }, "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA=="], + "scheduler": ["scheduler@0.26.0", "", {}, "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA=="], "semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], @@ -2152,6 +2437,10 @@ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], "slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="], @@ -2224,6 +2513,8 @@ "swr": ["swr@2.4.0", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-sUlC20T8EOt1pHmDiqueUWMmRRX03W7w5YxovWX7VR2KHEPCTMly85x05vpkP5i6Bu4h44ePSMD9Tc+G2MItFw=="], + "symbol-tree": ["symbol-tree@3.2.4", "", {}, "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="], + "tailwind-merge": ["tailwind-merge@3.4.0", "", {}, "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g=="], "tailwindcss": ["tailwindcss@4.1.18", "", {}, "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw=="], @@ -2232,6 +2523,10 @@ "tar": ["tar@7.5.7", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ=="], + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + "terser": ["terser@5.16.9", "", { "dependencies": { "@jridgewell/source-map": "^0.3.2", "acorn": "^8.5.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-HPa/FdTB9XGI2H1/keLFZHxl6WNvAI4YalHGtDQTlMnJcoqSab1UwL4l1hGEhs6/GmLHBZIg/YgB++jcbzoOEg=="], "throttleit": ["throttleit@2.1.0", "", {}, "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw=="], @@ -2248,13 +2543,19 @@ "tinyrainbow": ["tinyrainbow@3.0.3", "", {}, "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q=="], + "tldts": ["tldts@7.0.23", "", { "dependencies": { "tldts-core": "^7.0.23" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw=="], + + "tldts-core": ["tldts-core@7.0.23", "", {}, "sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ=="], + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "tokenlens": ["tokenlens@1.3.1", "", { "dependencies": { "@tokenlens/core": "1.3.0", "@tokenlens/fetch": "1.3.0", "@tokenlens/helpers": "1.3.1", "@tokenlens/models": "1.3.0" } }, "sha512-7oxmsS5PNCX3z+b+z07hL5vCzlgHKkCGrEQjQmWl5l+v5cUrtL7S1cuST4XThaL1XyjbTX8J5hfP0cjDJRkaLA=="], - "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + "tough-cookie": ["tough-cookie@6.0.0", "", { "dependencies": { "tldts": "^7.0.5" } }, "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w=="], + + "tr46": ["tr46@6.0.0", "", { "dependencies": { "punycode": "^2.3.1" } }, "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw=="], "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], @@ -2270,6 +2571,8 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], @@ -2286,13 +2589,15 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "uc.micro": ["uc.micro@2.1.0", "", {}, "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A=="], + "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], "uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="], - "undici": ["undici@7.18.2", "", {}, "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw=="], + "undici": ["undici@7.21.0", "", {}, "sha512-Hn2tCQpoDt1wv23a68Ctc8Cr/BHpUSfaPYrkajTXOS9IKpxVRx/X5m1K2YkbK2ipgZgxXSgsUinl3x+2YdSSfg=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], @@ -2350,15 +2655,21 @@ "vitest": ["vitest@4.0.18", "", { "dependencies": { "@vitest/expect": "4.0.18", "@vitest/mocker": "4.0.18", "@vitest/pretty-format": "4.0.18", "@vitest/runner": "4.0.18", "@vitest/snapshot": "4.0.18", "@vitest/spy": "4.0.18", "@vitest/utils": "4.0.18", "es-module-lexer": "^1.7.0", "expect-type": "^1.2.2", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^3.10.0", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.0.3", "vite": "^6.0.0 || ^7.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.0.18", "@vitest/browser-preview": "4.0.18", "@vitest/browser-webdriverio": "4.0.18", "@vitest/ui": "4.0.18", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ=="], + "w3c-keyname": ["w3c-keyname@2.2.8", "", {}, "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ=="], + + "w3c-xmlserializer": ["w3c-xmlserializer@5.0.0", "", { "dependencies": { "xml-name-validator": "^5.0.0" } }, "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA=="], + "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-streams-polyfill": ["web-streams-polyfill@4.0.0-beta.3", "", {}, "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug=="], "webcrypto-core": ["webcrypto-core@1.8.1", "", { "dependencies": { "@peculiar/asn1-schema": "^2.3.13", "@peculiar/json-schema": "^1.1.12", "asn1js": "^3.0.5", "pvtsutils": "^1.3.5", "tslib": "^2.7.0" } }, "sha512-P+x1MvlNCXlKbLSOY4cYrdreqPG5hbzkmawbcXLKN/mf6DZW0SdNNkZ+sjwsqVkI4A4Ko2sPZmkZtCKY58w83A=="], - "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "webidl-conversions": ["webidl-conversions@8.0.1", "", {}, "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ=="], - "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "whatwg-mimetype": ["whatwg-mimetype@5.0.0", "", {}, "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw=="], + + "whatwg-url": ["whatwg-url@16.0.0", "", { "dependencies": { "@exodus/bytes": "^1.11.0", "tr46": "^6.0.0", "webidl-conversions": "^8.0.1" } }, "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -2386,10 +2697,14 @@ "ws": ["ws@8.18.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw=="], + "xml-name-validator": ["xml-name-validator@5.0.0", "", {}, "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg=="], + "xml2js": ["xml2js@0.6.2", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA=="], "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + "xmlchars": ["xmlchars@2.2.0", "", {}, "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="], + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="], @@ -3070,6 +3385,8 @@ "@workos-inc/node/jose": ["jose@6.1.3", "", {}, "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ=="], + "bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], "cliui/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], @@ -3080,6 +3397,8 @@ "cmdk/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + "cssstyle/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], + "elementtree/sax": ["sax@1.1.4", "", {}, "sha512-5f3k2PbGGp+YtKJjOItpg3P99IMD84E4HOvcfleTb5joCHNXYLsR9yWFPOYGgaeMPDubQILTCMdsFb2OMeOjtg=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -3108,18 +3427,30 @@ "iron-session/iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], + "jsdom/parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="], + + "markdown-it/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + "miniflare/undici": ["undici@7.18.2", "", {}, "sha512-y+8YjDFzWdQlSE9N5nzKMT3g4a5UBX1HKowfdXh0uvAnTaqqwqB92Jt4UXBAeKekDs5IaDKyJFR4X1gYVCgXcw=="], + "miniflare/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "motion/framer-motion": ["framer-motion@12.33.0", "", { "dependencies": { "motion-dom": "^12.33.0", "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-ca8d+rRPcDP5iIF+MoT3WNc0KHJMjIyFAbtVLvM9eA7joGSpeqDfiNH/kCs1t4CHi04njYvWyj0jS4QlEK/rJQ=="], "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], + "node-fetch/whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="], + "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="], + + "playwright/fsevents": ["fsevents@2.3.2", "", { "os": "darwin" }, "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA=="], + "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], @@ -3138,14 +3469,22 @@ "radix-ui/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "rc/strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + "rimraf/glob": ["glob@13.0.1", "", { "dependencies": { "minimatch": "^10.1.2", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-B7U/vJpE3DkJ5WXTgTpTRN63uV42DseiXXKMwG14LQBXmsdeIoHAPbU/MEo6II0k5ED74uc2ZGTC6MwHFQhF6w=="], "router/path-to-regexp": ["path-to-regexp@8.3.0", "", {}, "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA=="], + "streamdown/marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], + "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="], "vite/esbuild": ["esbuild@0.27.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.0", "@esbuild/android-arm": "0.27.0", "@esbuild/android-arm64": "0.27.0", "@esbuild/android-x64": "0.27.0", "@esbuild/darwin-arm64": "0.27.0", "@esbuild/darwin-x64": "0.27.0", "@esbuild/freebsd-arm64": "0.27.0", "@esbuild/freebsd-x64": "0.27.0", "@esbuild/linux-arm": "0.27.0", "@esbuild/linux-arm64": "0.27.0", "@esbuild/linux-ia32": "0.27.0", "@esbuild/linux-loong64": "0.27.0", "@esbuild/linux-mips64el": "0.27.0", "@esbuild/linux-ppc64": "0.27.0", "@esbuild/linux-riscv64": "0.27.0", "@esbuild/linux-s390x": "0.27.0", "@esbuild/linux-x64": "0.27.0", "@esbuild/netbsd-arm64": "0.27.0", "@esbuild/netbsd-x64": "0.27.0", "@esbuild/openbsd-arm64": "0.27.0", "@esbuild/openbsd-x64": "0.27.0", "@esbuild/openharmony-arm64": "0.27.0", "@esbuild/sunos-x64": "0.27.0", "@esbuild/win32-arm64": "0.27.0", "@esbuild/win32-ia32": "0.27.0", "@esbuild/win32-x64": "0.27.0" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA=="], @@ -3674,6 +4013,10 @@ "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "node-fetch/whatwg-url/tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="], + + "node-fetch/whatwg-url/webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="], + "rimraf/glob/minimatch": ["minimatch@10.1.2", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.1" } }, "sha512-fu656aJ0n2kcXwsnwnv9g24tkU5uSmOlTjd6WyyaKm2Z+h1qmY6bAjrcaIxF/BslFqbZ8UBtbJi7KgQOZD2PTw=="], "vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A=="], diff --git a/docs/README.md b/docs/README.md index 8728de5..487843c 100755 --- a/docs/README.md +++ b/docs/README.md @@ -31,6 +31,7 @@ The construction-specific modules that make up HPS Compass. - [scheduling](modules/scheduling.md) -- Gantt charts, critical path analysis, dependency management, baselines, workday exceptions - [financials](modules/financials.md) -- invoices, vendor bills, payments, credit memos, NetSuite sync tie-in - [mobile](modules/mobile.md) -- Capacitor native app, offline photo queue, push notifications, biometric auth +- [desktop](modules/desktop.md) -- Tauri desktop app, SQLite sync, Wayland/NVIDIA compatibility, offline-first - [claude code](modules/claude-code.md) -- local bridge daemon, own Anthropic API key, filesystem + terminal tools, WebSocket protocol @@ -56,6 +57,8 @@ bun deploy # build + deploy to cloudflare workers bun run db:generate # generate migrations from schema bun run db:migrate:local # apply migrations locally bun run db:migrate:prod # apply migrations to production +bun tauri:dev # desktop dev (auto-configures Wayland/NVIDIA) +bun tauri:build # desktop production build bun lint # eslint ``` diff --git a/docs/modules/conversations.md b/docs/modules/conversations.md new file mode 100644 index 0000000..b345891 --- /dev/null +++ b/docs/modules/conversations.md @@ -0,0 +1,173 @@ +# Conversations Module + +A Slack-like messaging system for project teams. This module provides real-time chat with threads, reactions, and presence tracking. + +## What's Built + +The conversations module is roughly 80% complete. The core messaging experience works end-to-end: channels, threads, search, and presence. What's missing is the periphery—attachments, voice, and some UI polish. + +### Working Features + +**Channels and Organization** +- Text and announcement channels (public or private) +- Channel categories that collapse in the sidebar +- Join/leave public channels +- Channel membership with roles (owner, moderator, member) + +**Messaging** +- Send messages with full markdown support (bold, italic, code, lists, links) +- Edit and soft-delete messages +- Threaded replies in a resizable side panel +- Pin important messages +- Message search with filters (by channel, user, date range) + +**Real-Time Updates** +- Polling-based message updates (2.5s when visible, 10s when hidden) +- Typing indicators with 5-second timeout +- User presence (online, idle, do-not-disturb, offline) +- Automatic idle detection after 5 minutes of inactivity + +**Database and Performance** +- 10 tables in `schema-conversations.ts` +- Indexed for production workloads (9 indexes added for common queries) +- Input validation (4000 char messages, 100 char status, emoji validation) +- LIKE query escaping to prevent pattern injection + +### What's Missing + +The gaps fall into three categories: schema without implementation, UI not connected, and features not started. + +**Schema exists, no implementation:** + +| Feature | Status | +|---------|--------| +| Message attachments | Table defined, no upload/download actions | +| Voice channels | Type in schema, stub component only | +| Announcement channels | Type exists, no posting restrictions | +| Notification levels | Field in channelMembers, not exposed | +| Custom status messages | Field in userPresence, no UI | + +**Actions exist, UI incomplete:** + +| Feature | Status | +|---------|--------| +| Message reactions | `addReaction`/`removeReaction` work, emoji picker disabled | +| Pinned messages panel | Panel built, header button not wired | +| Unread badges | Read state tracked in DB, sidebar not always accurate | + +**Not implemented:** + +- @mentions and notifications +- Channel settings (edit, archive, delete) +- Member management (add/remove, role changes) +- Private channel invitations +- Offline sync integration (sync engine exists, not connected) + +## Architecture + +### Server Actions + +Six action files handle all data mutations: + +``` +src/app/actions/ +├── conversations.ts # Channel CRUD, join/leave +├── chat-messages.ts # Send, edit, delete, reactions, threads +├── conversations-realtime.ts # Polling updates, typing indicators +├── channel-categories.ts # Category management, channel reordering +├── message-search.ts # Full-text search, pin/unpin +└── presence.ts # Status updates, member presence +``` + +All actions return `{ success: true, data }` or `{ success: false, error }`. Authorization checks verify channel membership before allowing reactions or message operations. + +### Components + +The UI is split between the sidebar navigation and the main channel view: + +``` +src/components/conversations/ +├── channel-header.tsx # Name, description, member count, action buttons +├── message-list.tsx # Paginated messages grouped by date +├── message-item.tsx # Single message with toolbar +├── message-composer.tsx # TipTap editor with formatting +├── thread-panel.tsx # Resizable reply panel +├── member-sidebar.tsx # Members grouped by status +├── pinned-messages-panel.tsx # Sheet for pinned messages +├── search-dialog.tsx # Command dialog with filters +├── typing-indicator.tsx # Animated dots +├── create-channel-dialog.tsx # Full creation form +└── voice-channel-stub.tsx # Placeholder +``` + +The channel view at `/dashboard/conversations/[channelId]` combines these into a three-panel layout: sidebar (optional), messages, and thread panel (when open). + +### Real-Time Strategy + +This module uses polling rather than WebSockets. The reasoning: + +1. Cloudflare Workers handles HTTP well; WebSocket support is newer +2. Polling is simpler to debug and deploy +3. 2.5s latency is acceptable for team chat +4. Automatic backoff when tab is hidden reduces server load + +If WebSocket requirements emerge (typing races, sub-second updates), the architecture can shift. The `useRealtimeChannel` hook abstracts the polling logic, so swapping implementations wouldn't require component changes. + +### Sync Infrastructure + +A complete offline-first sync engine exists in `src/lib/sync/` but isn't connected to conversations yet. The engine handles: + +- Vector clocks for conflict detection +- Mutation queues for offline edits +- Delta sync with checkpoints +- Tombstones for deletions + +This was built for the Tauri desktop app. When the mobile app needs offline messaging, this infrastructure is ready to connect. + +## Recent Fixes + +February 2026 brought a comprehensive code review with 38 issues addressed: + +**Critical (5):** +- Database indexes for production queries +- Edge runtime compatibility (replaced JSDOM with isomorphic-dompurify) +- Authorization bypasses in category/channel operations + +**Important (18):** +- React.memo for message items +- Throttled presence updates +- Message length limits and input validation +- Accessibility (aria-labels, keyboard navigation) + +**Polish (15):** +- Improved typing animation +- Sticky date separators +- Extracted duplicate query construction + +## What's Next + +Priority order for completing the module: + +1. **Wire the disabled UI** — Connect the emoji picker for reactions, wire the pinned messages button, fix unread badge accuracy. These are small changes with high user impact. + +2. **Attachments** — The hardest missing piece. Requires file upload to R2, thumbnail generation, permissions, and a storage quota system. Start with images only. + +3. **Voice channels** — Requires WebRTC or a third-party service. Consider LiveKit or Daily for the infrastructure layer. + +4. **Notifications** — @mentions need a notification table, push integration, and preference settings. The schema doesn't support this yet. + +5. **Offline sync** — Connect the existing sync engine to conversations. This unlocks the desktop app's full potential. + +## Files Reference + +| Category | Files | Lines | +|----------|-------|-------| +| Schema | `schema-conversations.ts` | 169 | +| Actions | 6 files in `app/actions/` | ~2,200 | +| Components | 12 in `components/conversations/` | ~2,300 | +| Pages | 3 in `app/dashboard/conversations/` | ~160 | +| Hooks | `use-realtime-channel.ts` | 170 | +| Contexts | `presence-context.tsx`, conversations layout | ~320 | +| Sync | 9 files in `lib/sync/` | ~1,800 | + +Total: approximately 7,350 lines diff --git a/docs/modules/desktop.md b/docs/modules/desktop.md new file mode 100644 index 0000000..a48701d --- /dev/null +++ b/docs/modules/desktop.md @@ -0,0 +1,332 @@ +Desktop Module +=== + +The desktop module wraps Compass in a native Windows, macOS, and Linux application using Tauri v2. Unlike the mobile app (which loads a remote URL in a WebView), the desktop app runs the entire Next.js application locally with a SQLite database. This means genuine offline support: you can create projects, edit schedules, and manage data without any network connection, then sync when connectivity returns. + +The design principle mirrors the mobile module: **the web app doesn't know or care that it's running on desktop.** Platform detection (`isTauri()`) gates native features, but the React components, server actions, and data layer work identically whether the app is served from Cloudflare or running from a local Tauri binary. + + +why local sqlite +--- + +Cloudflare D1 is excellent for the hosted version--edge-co-located, zero-config, single-digit-millisecond latency. But it requires a network connection. For a construction project management tool, this is a genuine limitation. Jobsites often have spotty or no connectivity. A superintendent walking a site needs to check schedules, update task status, and capture notes without wondering if the API will respond. + +The desktop app solves this by running SQLite locally via `@tauri-apps/plugin-sql`. The local database holds a subset of the remote data: the projects you've accessed, the schedules you're working on, the teams you belong to. When online, a sync engine pulls remote changes and pushes local mutations. When offline, the app works normally--the only difference is a small amber indicator showing pending sync count. + +This isn't a PWA with a service worker cache. It's a real database with real queries, conflict resolution, and durability guarantees. The tradeoff is complexity: we maintain two database providers (D1 and SQLite), a sync protocol, and conflict resolution logic. But for desktop users who need reliable offline access, the tradeoff is worth it. + + +architecture +--- + +``` +src-tauri/ +├── src/ +│ ├── lib.rs # App initialization, platform fixes +│ ├── commands/ # Rust commands exposed to frontend +│ │ ├── database.rs # db_query, db_execute, db_init +│ │ ├── sync.rs # get_sync_status, trigger_sync +│ │ └── platform.rs # get_platform_info, get_display_server +│ └── error.rs # AppError enum with From implementations +├── capabilities/ +│ └── default.json # Security permissions (HTTP, filesystem, SQL) +├── migrations/ +│ └── initial.sql # SQLite schema for local database +└── tauri.conf.json # Window config, CSP, bundle settings + +src/ +├── db/provider/ +│ ├── interface.ts # DatabaseProvider type, isTauri(), detectPlatform() +│ └── tauri-provider.ts # SQLite implementation via @tauri-apps/plugin-sql +├── lib/sync/ +│ ├── engine.ts # Pull/push sync, conflict resolution +│ ├── clock.ts # Vector clocks for causal ordering +│ ├── conflict.ts # Last-write-wins with clock comparison +│ ├── schema.ts # local_sync_metadata, mutation_queue tables +│ └── queue/ +│ ├── mutation-queue.ts # Local operation queue with persistence +│ └── processor.ts # Retry logic with exponential backoff +├── hooks/ +│ ├── use-desktop.ts # Platform detection hook +│ └── use-sync-status.ts # Sync state, pending count, trigger function +└── components/desktop/ + ├── desktop-shell.tsx # Context provider, beforeunload sync check + ├── sync-indicator.tsx # Badge showing sync status + └── offline-banner.tsx # Warning when offline with pending changes +``` + +The Rust side is thin by design. `lib.rs` initializes the Tauri plugins (SQL, HTTP, filesystem, window state), registers the command handlers, and applies platform-specific fixes at startup. The commands are simple pass-throughs--the real logic lives in TypeScript. This keeps the Rust surface area small and lets us share code between web and desktop. + + +platform detection +--- + +`src/db/provider/interface.ts` provides the detection layer: + +```typescript +export function isTauri(): boolean { + if (typeof window === "undefined") return false + return "__TAURI__" in window +} +``` + +The `__TAURI__` global is injected by the Tauri runtime before JavaScript executes. This means the check works after hydration but not during SSR--server-rendered HTML assumes web, and the desktop state is only known client-side. + +`detectPlatform()` returns `"tauri"`, `"d1"`, or `"memory"` based on the runtime environment. This determines which database provider the sync engine uses: + +```typescript +export function detectPlatform(): ProviderType { + if (isTauri()) return "tauri" + if (isCloudflareWorker()) return "d1" + return "memory" +} +``` + + +the database provider interface +--- + +Both D1 and Tauri SQLite implement the same `DatabaseProvider` interface: + +```typescript +export interface DatabaseProvider { + readonly type: ProviderType + getDb(): Promise + execute(sql: string, params?: unknown[]): Promise + transaction(fn: (db: DrizzleDB) => Promise): Promise + close?(): Promise +} +``` + +The Tauri provider uses `@tauri-apps/plugin-sql` which provides raw SQL execution: + +```typescript +async function initializeDatabase(config?: TauriProviderConfig): Promise { + if (dbInstance) return dbInstance + if (dbInitPromise) return dbInitPromise // Prevent concurrent init + + dbInitPromise = (async () => { + const { default: Database } = await import("@tauri-apps/plugin-sql") + dbInstance = await Database.load("sqlite:compass.db") + return dbInstance + })() + + return dbInitPromise +} +``` + +The dynamic import is important--`@tauri-apps/plugin-sql` doesn't exist outside Tauri. A top-level import would crash the web app at module evaluation time. The same pattern appears in the mobile module with Capacitor plugins. + +Transactions are handled manually since the Tauri SQL plugin doesn't provide a transaction API: + +```typescript +async transaction(fn: (db: DrizzleDB) => Promise): Promise { + const db = await initializeDatabase(config) + await db.execute("BEGIN TRANSACTION") + try { + const result = await fn({ _tauriDb: db } as unknown as DrizzleDB) + await db.execute("COMMIT") + return result + } catch (error) { + await db.execute("ROLLBACK") + throw error + } +} +``` + + +the sync engine +--- + +Sync is the hardest part of offline-first. The desktop app needs to: + +1. Pull remote changes since the last sync +2. Push local mutations created while offline +3. Detect and resolve conflicts when the same record was edited on both sides +4. Preserve causal ordering (if edit B depends on edit A, they must apply in order) + +The implementation uses vector clocks for causal ordering and last-write-wins for conflict resolution. Here's the conceptual model: + +**Vector clocks.** Each device maintains a map of `{ deviceId: counter }`. When a device makes a change, it increments its counter. The vector clock gets attached to every mutation. When comparing two versions of a record, the clocks tell you whether one causally precedes the other (all counters <=) or whether they're concurrent (neither dominates). + +**Conflict resolution.** If clocks show concurrent edits (true conflict), we use last-write-wins based on timestamp. This is a deliberate choice--it's simple and predictable, though it can lose data. Alternatives like operational transformation or CRDTs are more correct but significantly more complex. For a project management tool where edits are mostly to different fields, last-write-wins is usually fine. + +**Mutation queue.** Local mutations go into a queue table before being applied to the local database. The queue persists to localStorage as a backup--if the app crashes or is force-closed mid-sync, mutations aren't lost. On restart, the queue is restored and sync resumes. + +**Tombstones.** Deletions are tricky. If device A deletes a record and device B edits it offline, we need to know the deletion happened. The solution is tombstones--when a record is deleted, we keep a marker with its ID and the deletion timestamp. Delta sync includes tombstones, and the receiver respects them. + +The sync tables are defined in `src/lib/sync/schema.ts`: + +```typescript +export const localSyncMetadata = sqliteTable("local_sync_metadata", { + tableName: text("table_name").notNull(), + recordId: text("record_id").notNull(), + vectorClock: text("vector_clock").notNull(), + lastSyncedAt: text("last_synced_at"), + isDeleted: integer("is_deleted", { mode: "boolean" }).default(false), +}) + +export const mutationQueue = sqliteTable("mutation_queue", { + id: text("id").primaryKey(), + operation: text("operation", { enum: ["insert", "update", "delete"] }).notNull(), + tableName: text("table_name").notNull(), + recordId: text("record_id").notNull(), + payload: text("payload"), + vectorClock: text("vector_clock").notNull(), + status: text("status", { enum: ["pending", "processing", "completed", "failed"] }).notNull(), + retryCount: integer("retry_count").default(0), + processAfter: text("process_after"), // For non-blocking backoff +}) +``` + + +the beforeunload problem +--- + +A user with pending mutations could close the app before sync completes. The mutations would be lost even though they're in the queue--the queue is persisted, but the user might not reopen the app. + +The solution is a `beforeunload` handler that warns the user: + +```typescript +useEffect(() => { + const handleBeforeUnload = (e: BeforeUnloadEvent) => { + if (pendingCount > 0 || isSyncing) { + e.preventDefault() + e.returnValue = "Sync in progress. Close anyway?" + } + } + window.addEventListener("beforeunload", handleBeforeUnload) + return () => window.removeEventListener("beforeunload", handleBeforeUnload) +}, [pendingCount, isSyncing]) +``` + +This is implemented in `src/components/desktop/desktop-shell.tsx`. The handler only triggers when there's actual pending work--closing the app with a clean sync state is silent. + + +linux compatibility +--- + +Linux desktop support is where Tauri's WebKitGTK backend causes problems. The issue is specific to NVIDIA GPUs on Wayland: WebKitGTK's DMA-BUF renderer conflicts with NVIDIA's driver implementation, causing crashes or extreme slowness. + +The fix depends on driver version: + +- **NVIDIA 545+**: `__NV_DISABLE_EXPLICIT_SYNC=1` disables the new explicit sync protocol. This is the preferred fix--it maintains hardware acceleration. + +- **Older drivers**: `WEBKIT_DISABLE_DMABUF_RENDERER=1` forces software rendering. This is stable but slow. + +The app detects the configuration at startup in `lib.rs`: + +```rust +#[cfg(target_os = "linux")] +{ + let session_type = std::env::var("XDG_SESSION_TYPE").unwrap_or_default(); + let is_wayland = session_type == "wayland"; + let has_nvidia = std::path::Path::new("/proc/driver/nvidia").exists(); + + if is_wayland && has_nvidia { + if std::env::var("__NV_DISABLE_EXPLICIT_SYNC").is_err() { + std::env::set_var("__NV_DISABLE_EXPLICIT_SYNC", "1"); + } + } +} +``` + +We only set the explicit sync flag automatically. The DMABUF fallback (`WEBKIT_DISABLE_DMABUF_RENDERER=1`) forces software rendering which is noticeably slower--if a user needs it, they can set it manually. The automatic fix works for most modern setups; the manual fallback exists for edge cases. + +Wayland compositors also handle window decorations differently from X11. The app detects Wayland and disables CSD (client-side decorations): + +```rust +if is_wayland { + let _ = window.set_decorations(false); +} +``` + +This prevents duplicate title bars on compositors like Hyprland or Sway that draw their own decorations. + + +security model +--- + +Tauri uses a capability-based security model. The `src-tauri/capabilities/default.json` file defines what the frontend can access: + +**HTTP permissions.** Fetch requests are scoped to specific domains: + +```json +{ + "identifier": "http:default", + "allow": [ + { "url": "http://localhost:*" }, + { "url": "http://127.0.0.1:*" }, + { "url": "https://compass.work/**" }, + { "url": "https://*.cloudflare.com/**" }, + { "url": "https://api.openrouter.ai/**" }, + { "url": "https://api.workos.com/**" } + ] +} +``` + +The localhost entries are needed for dev mode (the frontend talks to `localhost:3000`). Production domains include the app itself, the Cloudflare deployment platform, and the AI/auth providers. + +**Filesystem permissions.** Access is limited to app-specific directories: + +```json +{ + "identifier": "fs:default", + "allow": [ + { "path": "$APPDATA/**" }, + { "path": "$APPCONFIG/**" }, + { "path": "$LOCALDATA/**" } + ] +} +``` + +The SQLite database lives in `$APPDATA/compass/`. The app can't read or write outside these directories. + +**Content Security Policy.** Configured in `tauri.conf.json`: + +``` +default-src 'self' +script-src 'self' 'unsafe-inline' 'unsafe-eval' +connect-src 'self' http://localhost:* https: wss: +``` + +The `unsafe-inline` and `unsafe-eval` are required for Next.js--the Turbopack dev server uses inline scripts and eval for hot module replacement. In a production build, these could potentially be removed, but dev mode needs them. + + +troubleshooting +--- + +**Blank window on Linux.** If the window opens but shows nothing: + +1. Check that the dev server is running (`http://localhost:3000` responds) +2. Check HTTP permissions include `localhost:*` in capabilities +3. Check CSP allows `connect-src 'self' http://localhost:*` + +**Crash on Wayland + NVIDIA.** The automatic fix should handle most cases. If it still crashes: + +```bash +# Force software rendering (stable but slow) +WEBKIT_DISABLE_DMABUF_RENDERER=1 bun tauri:dev + +# Or use X11 backend +GDK_BACKEND=x11 bun tauri:dev +``` + +**Slow performance in dev mode.** Dev mode is inherently slower than production: +- Turbopack compiles routes on first access (1-3s per route) +- Cloudflare bindings go through a wrangler proxy +- If DMABUF is disabled, rendering is software-only + +Production builds don't have these issues. The desktop app built with `bun tauri:build` is significantly snappier. + + +commands +--- + +```bash +bun tauri:dev # Development with hot reload +bun tauri:build # Production build (creates installer) +bun tauri:preview # Run built app without rebuilding +``` + +No environment variables needed--the app handles platform detection and applies fixes automatically. diff --git a/drizzle.config.ts b/drizzle.config.ts index e947193..4824ded 100755 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -11,6 +11,8 @@ export default defineConfig({ "./src/db/schema-google.ts", "./src/db/schema-dashboards.ts", "./src/db/schema-mcp.ts", + "./src/db/schema-conversations.ts", + "./src/lib/sync/schema.ts", ], out: "./drizzle", dialect: "sqlite", diff --git a/drizzle/0020_military_sebastian_shaw.sql b/drizzle/0020_military_sebastian_shaw.sql new file mode 100644 index 0000000..7d36811 --- /dev/null +++ b/drizzle/0020_military_sebastian_shaw.sql @@ -0,0 +1,82 @@ +CREATE TABLE `channel_members` ( + `id` text PRIMARY KEY NOT NULL, + `channel_id` text NOT NULL, + `user_id` text NOT NULL, + `role` text DEFAULT 'member' NOT NULL, + `notify_level` text DEFAULT 'all' NOT NULL, + `joined_at` text NOT NULL, + FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `channel_read_state` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text NOT NULL, + `channel_id` text NOT NULL, + `last_read_message_id` text, + `last_read_at` text NOT NULL, + `unread_count` integer DEFAULT 0 NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `channels` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `type` text DEFAULT 'text' NOT NULL, + `description` text, + `organization_id` text NOT NULL, + `project_id` text, + `is_private` integer DEFAULT false NOT NULL, + `created_by` text NOT NULL, + `sort_order` integer DEFAULT 0 NOT NULL, + `archived_at` text, + `created_at` text NOT NULL, + `updated_at` text NOT NULL, + FOREIGN KEY (`organization_id`) REFERENCES `organizations`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`project_id`) REFERENCES `projects`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`created_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE TABLE `message_attachments` ( + `id` text PRIMARY KEY NOT NULL, + `message_id` text NOT NULL, + `file_name` text NOT NULL, + `mime_type` text NOT NULL, + `file_size` integer NOT NULL, + `r2_path` text NOT NULL, + `width` integer, + `height` integer, + `uploaded_at` text NOT NULL, + FOREIGN KEY (`message_id`) REFERENCES `messages`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `message_reactions` ( + `id` text PRIMARY KEY NOT NULL, + `message_id` text NOT NULL, + `user_id` text NOT NULL, + `emoji` text NOT NULL, + `created_at` text NOT NULL, + FOREIGN KEY (`message_id`) REFERENCES `messages`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `messages` ( + `id` text PRIMARY KEY NOT NULL, + `channel_id` text NOT NULL, + `thread_id` text, + `user_id` text NOT NULL, + `content` text NOT NULL, + `content_html` text, + `edited_at` text, + `deleted_at` text, + `deleted_by` text, + `is_pinned` integer DEFAULT false NOT NULL, + `reply_count` integer DEFAULT 0 NOT NULL, + `last_reply_at` text, + `created_at` text NOT NULL, + FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`thread_id`) REFERENCES `messages`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`deleted_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action +); diff --git a/drizzle/0021_early_cerise.sql b/drizzle/0021_early_cerise.sql new file mode 100644 index 0000000..35155d0 --- /dev/null +++ b/drizzle/0021_early_cerise.sql @@ -0,0 +1,55 @@ +CREATE TABLE `channel_categories` ( + `id` text PRIMARY KEY NOT NULL, + `name` text NOT NULL, + `organization_id` text NOT NULL, + `position` integer DEFAULT 0 NOT NULL, + `collapsed_by_default` integer DEFAULT false, + `created_at` text NOT NULL, + FOREIGN KEY (`organization_id`) REFERENCES `organizations`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `typing_sessions` ( + `id` text PRIMARY KEY NOT NULL, + `channel_id` text NOT NULL, + `user_id` text NOT NULL, + `started_at` text NOT NULL, + `expires_at` text NOT NULL, + FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +CREATE TABLE `user_presence` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text NOT NULL, + `status` text DEFAULT 'offline' NOT NULL, + `status_message` text, + `last_seen_at` text NOT NULL, + `updated_at` text NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_messages` ( + `id` text PRIMARY KEY NOT NULL, + `channel_id` text NOT NULL, + `thread_id` text, + `user_id` text NOT NULL, + `content` text NOT NULL, + `content_html` text, + `edited_at` text, + `deleted_at` text, + `deleted_by` text, + `is_pinned` integer DEFAULT false NOT NULL, + `reply_count` integer DEFAULT 0 NOT NULL, + `last_reply_at` text, + `created_at` text NOT NULL, + FOREIGN KEY (`channel_id`) REFERENCES `channels`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`deleted_by`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_messages`("id", "channel_id", "thread_id", "user_id", "content", "content_html", "edited_at", "deleted_at", "deleted_by", "is_pinned", "reply_count", "last_reply_at", "created_at") SELECT "id", "channel_id", "thread_id", "user_id", "content", "content_html", "edited_at", "deleted_at", "deleted_by", "is_pinned", "reply_count", "last_reply_at", "created_at" FROM `messages`;--> statement-breakpoint +DROP TABLE `messages`;--> statement-breakpoint +ALTER TABLE `__new_messages` RENAME TO `messages`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +ALTER TABLE `channels` ADD `category_id` text REFERENCES channel_categories(id); \ No newline at end of file diff --git a/drizzle/0022_add_conversations_indexes.sql b/drizzle/0022_add_conversations_indexes.sql new file mode 100644 index 0000000..e53eb4e --- /dev/null +++ b/drizzle/0022_add_conversations_indexes.sql @@ -0,0 +1,17 @@ +CREATE INDEX `idx_messages_channel_created` ON `messages`(`channel_id`,`created_at` DESC); +--> statement-breakpoint +CREATE INDEX `idx_messages_thread` ON `messages`(`thread_id`); +--> statement-breakpoint +CREATE INDEX `idx_messages_channel_pinned` ON `messages`(`channel_id`,`is_pinned`) WHERE `is_pinned` = 1; +--> statement-breakpoint +CREATE INDEX `idx_channel_members_lookup` ON `channel_members`(`channel_id`,`user_id`); +--> statement-breakpoint +CREATE INDEX `idx_typing_sessions_channel_expires` ON `typing_sessions`(`channel_id`,`expires_at`); +--> statement-breakpoint +CREATE INDEX `idx_user_presence_user` ON `user_presence`(`user_id`); +--> statement-breakpoint +CREATE INDEX `idx_channel_read_state_lookup` ON `channel_read_state`(`channel_id`,`user_id`); +--> statement-breakpoint +CREATE INDEX `idx_message_reactions_message` ON `message_reactions`(`message_id`); +--> statement-breakpoint +CREATE INDEX `idx_channel_categories_org` ON `channel_categories`(`organization_id`); diff --git a/drizzle/0023_known_moon_knight.sql b/drizzle/0023_known_moon_knight.sql new file mode 100644 index 0000000..57d220d --- /dev/null +++ b/drizzle/0023_known_moon_knight.sql @@ -0,0 +1,9 @@ +DROP INDEX `idx_channel_categories_org`;--> statement-breakpoint +DROP INDEX `idx_channel_members_lookup`;--> statement-breakpoint +DROP INDEX `idx_channel_read_state_lookup`;--> statement-breakpoint +DROP INDEX `idx_message_reactions_message`;--> statement-breakpoint +DROP INDEX `idx_messages_channel_created`;--> statement-breakpoint +DROP INDEX `idx_messages_thread`;--> statement-breakpoint +DROP INDEX `idx_messages_channel_pinned`;--> statement-breakpoint +DROP INDEX `idx_typing_sessions_channel_expires`;--> statement-breakpoint +DROP INDEX `idx_user_presence_user`; \ No newline at end of file diff --git a/drizzle/0024_thankful_slayback.sql b/drizzle/0024_thankful_slayback.sql new file mode 100644 index 0000000..6e01ee6 --- /dev/null +++ b/drizzle/0024_thankful_slayback.sql @@ -0,0 +1,48 @@ +CREATE TABLE `local_sync_metadata` ( + `id` text PRIMARY KEY NOT NULL, + `table_name` text NOT NULL, + `record_id` text NOT NULL, + `vector_clock` text NOT NULL, + `last_modified_at` text NOT NULL, + `sync_status` text DEFAULT 'pending_sync' NOT NULL, + `conflict_data` text, + `created_at` text NOT NULL +); +--> statement-breakpoint +CREATE INDEX `local_sync_metadata_table_record_idx` ON `local_sync_metadata` (`table_name`,`record_id`);--> statement-breakpoint +CREATE TABLE `mutation_queue` ( + `id` text PRIMARY KEY NOT NULL, + `operation` text NOT NULL, + `table_name` text NOT NULL, + `record_id` text NOT NULL, + `payload` text, + `vector_clock` text NOT NULL, + `status` text DEFAULT 'pending' NOT NULL, + `retry_count` integer DEFAULT 0 NOT NULL, + `error_message` text, + `created_at` text NOT NULL, + `process_after` text +); +--> statement-breakpoint +CREATE INDEX `mutation_queue_status_created_idx` ON `mutation_queue` (`status`,`created_at`);--> statement-breakpoint +CREATE TABLE `sync_checkpoint` ( + `id` text PRIMARY KEY NOT NULL, + `table_name` text NOT NULL, + `last_sync_cursor` text, + `local_vector_clock` text, + `synced_at` text NOT NULL +); +--> statement-breakpoint +CREATE UNIQUE INDEX `sync_checkpoint_table_name_unique` ON `sync_checkpoint` (`table_name`);--> statement-breakpoint +CREATE INDEX `sync_checkpoint_table_name_idx` ON `sync_checkpoint` (`table_name`);--> statement-breakpoint +CREATE TABLE `sync_tombstone` ( + `id` text PRIMARY KEY NOT NULL, + `table_name` text NOT NULL, + `record_id` text NOT NULL, + `vector_clock` text NOT NULL, + `deleted_at` text NOT NULL, + `synced` integer DEFAULT false NOT NULL +); +--> statement-breakpoint +CREATE INDEX `sync_tombstone_table_record_idx` ON `sync_tombstone` (`table_name`,`record_id`);--> statement-breakpoint +CREATE INDEX `sync_tombstone_synced_idx` ON `sync_tombstone` (`synced`); \ No newline at end of file diff --git a/drizzle/meta/0020_snapshot.json b/drizzle/meta/0020_snapshot.json new file mode 100644 index 0000000..dbdf0a0 --- /dev/null +++ b/drizzle/meta/0020_snapshot.json @@ -0,0 +1,4405 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "3beb1b78-fd75-4351-841f-05b81e63fc6c", + "prevId": "83bd01d7-3d00-4895-9948-7f0dd7b500db", + "tables": { + "agent_conversations": { + "name": "agent_conversations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_conversations_user_id_users_id_fk": { + "name": "agent_conversations_user_id_users_id_fk", + "tableFrom": "agent_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_memories": { + "name": "agent_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "embedding": { + "name": "embedding", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_memories_conversation_id_agent_conversations_id_fk": { + "name": "agent_memories_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_memories", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memories_user_id_users_id_fk": { + "name": "agent_memories_user_id_users_id_fk", + "tableFrom": "agent_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "customers": { + "name": "customers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company": { + "name": "company", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback": { + "name": "feedback", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_url": { + "name": "page_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_width": { + "name": "viewport_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_height": { + "name": "viewport_height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_hash": { + "name": "ip_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback_interviews": { + "name": "feedback_interviews", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_role": { + "name": "user_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pain_points": { + "name": "pain_points", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feature_requests": { + "name": "feature_requests", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "overall_sentiment": { + "name": "overall_sentiment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "feedback_interviews_user_id_users_id_fk": { + "name": "feedback_interviews_user_id_users_id_fk", + "tableFrom": "feedback_interviews", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "group_members": { + "name": "group_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_members_group_id_groups_id_fk": { + "name": "group_members_group_id_groups_id_fk", + "tableFrom": "group_members", + "tableTo": "groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_members_user_id_users_id_fk": { + "name": "group_members_user_id_users_id_fk", + "tableFrom": "group_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "groups": { + "name": "groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groups_organization_id_organizations_id_fk": { + "name": "groups_organization_id_organizations_id_fk", + "tableFrom": "groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_members": { + "name": "project_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "project_members_project_id_projects_id_fk": { + "name": "project_members_project_id_projects_id_fk", + "tableFrom": "project_members", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_members_user_id_users_id_fk": { + "name": "project_members_user_id_users_id_fk", + "tableFrom": "project_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'OPEN'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_manager": { + "name": "project_manager", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_job_id": { + "name": "netsuite_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "push_tokens": { + "name": "push_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "push_tokens_user_id_users_id_fk": { + "name": "push_tokens_user_id_users_id_fk", + "tableFrom": "push_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_baselines": { + "name": "schedule_baselines", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_data": { + "name": "snapshot_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_baselines_project_id_projects_id_fk": { + "name": "schedule_baselines_project_id_projects_id_fk", + "tableFrom": "schedule_baselines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_tasks": { + "name": "schedule_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workdays": { + "name": "workdays", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date_calculated": { + "name": "end_date_calculated", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'PENDING'" + }, + "is_critical_path": { + "name": "is_critical_path", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_milestone": { + "name": "is_milestone", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "percent_complete": { + "name": "percent_complete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_tasks_project_id_projects_id_fk": { + "name": "schedule_tasks_project_id_projects_id_fk", + "tableFrom": "schedule_tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slab_memories": { + "name": "slab_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "memory_type": { + "name": "memory_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "importance": { + "name": "importance", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0.7 + }, + "pinned": { + "name": "pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "slab_memories_user_id_users_id_fk": { + "name": "slab_memories_user_id_users_id_fk", + "tableFrom": "slab_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_dependencies": { + "name": "task_dependencies", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "predecessor_id": { + "name": "predecessor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "successor_id": { + "name": "successor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'FS'" + }, + "lag_days": { + "name": "lag_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "task_dependencies_predecessor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_predecessor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "predecessor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_dependencies_successor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_successor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "successor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_members": { + "name": "team_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "teams": { + "name": "teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_organization_id_organizations_id_fk": { + "name": "teams_organization_id_organizations_id_fk", + "tableFrom": "teams", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'office'" + }, + "google_email": { + "name": "google_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendors": { + "name": "vendors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Subcontractor'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workday_exceptions": { + "name": "workday_exceptions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'non_working'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company_holiday'" + }, + "recurrence": { + "name": "recurrence", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'one_time'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "workday_exceptions_project_id_projects_id_fk": { + "name": "workday_exceptions_project_id_projects_id_fk", + "tableFrom": "workday_exceptions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credit_memos": { + "name": "credit_memos", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo_number": { + "name": "memo_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_applied": { + "name": "amount_applied", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_remaining": { + "name": "amount_remaining", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "credit_memos_customer_id_customers_id_fk": { + "name": "credit_memos_customer_id_customers_id_fk", + "tableFrom": "credit_memos", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "credit_memos_project_id_projects_id_fk": { + "name": "credit_memos_project_id_projects_id_fk", + "tableFrom": "credit_memos", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invoices_customer_id_customers_id_fk": { + "name": "invoices_customer_id_customers_id_fk", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_project_id_projects_id_fk": { + "name": "invoices_project_id_projects_id_fk", + "tableFrom": "invoices", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_auth": { + "name": "netsuite_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_in": { + "name": "expires_in", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issued_at": { + "name": "issued_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_log": { + "name": "netsuite_sync_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sync_type": { + "name": "sync_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_type": { + "name": "record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "records_processed": { + "name": "records_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "records_failed": { + "name": "records_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "error_summary": { + "name": "error_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_metadata": { + "name": "netsuite_sync_metadata", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "local_table": { + "name": "local_table", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_record_id": { + "name": "local_record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_record_type": { + "name": "netsuite_record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_internal_id": { + "name": "netsuite_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_local": { + "name": "last_modified_local", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_remote": { + "name": "last_modified_remote", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'synced'" + }, + "conflict_data": { + "name": "conflict_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payments": { + "name": "payments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_type": { + "name": "payment_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_date": { + "name": "payment_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "payments_customer_id_customers_id_fk": { + "name": "payments_customer_id_customers_id_fk", + "tableFrom": "payments", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_vendor_id_vendors_id_fk": { + "name": "payments_vendor_id_vendors_id_fk", + "tableFrom": "payments", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_project_id_projects_id_fk": { + "name": "payments_project_id_projects_id_fk", + "tableFrom": "payments", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendor_bills": { + "name": "vendor_bills", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bill_number": { + "name": "bill_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "bill_date": { + "name": "bill_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "vendor_bills_vendor_id_vendors_id_fk": { + "name": "vendor_bills_vendor_id_vendors_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "vendor_bills_project_id_projects_id_fk": { + "name": "vendor_bills_project_id_projects_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_config": { + "name": "plugin_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_encrypted": { + "name": "is_encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_events": { + "name": "plugin_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_events_plugin_id_plugins_id_fk": { + "name": "plugin_events_plugin_id_plugins_id_fk", + "tableFrom": "plugin_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_events_user_id_users_id_fk": { + "name": "plugin_events_user_id_users_id_fk", + "tableFrom": "plugin_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "required_env_vars": { + "name": "required_env_vars", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_by": { + "name": "enabled_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_at": { + "name": "enabled_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugins_enabled_by_users_id_fk": { + "name": "plugins_enabled_by_users_id_fk", + "tableFrom": "plugins", + "tableTo": "users", + "columnsFrom": [ + "enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_items": { + "name": "agent_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "done": { + "name": "done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_items_user_id_users_id_fk": { + "name": "agent_items_user_id_users_id_fk", + "tableFrom": "agent_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_config": { + "name": "agent_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_name": { + "name": "model_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_cost_per_million": { + "name": "max_cost_per_million", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_user_selection": { + "name": "allow_user_selection", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_config_updated_by_users_id_fk": { + "name": "agent_config_updated_by_users_id_fk", + "tableFrom": "agent_config", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_usage": { + "name": "agent_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_tokens": { + "name": "prompt_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "completion_tokens": { + "name": "completion_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_cost": { + "name": "estimated_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_usage_conversation_id_agent_conversations_id_fk": { + "name": "agent_usage_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_usage", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_usage_user_id_users_id_fk": { + "name": "agent_usage_user_id_users_id_fk", + "tableFrom": "agent_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_model_preference": { + "name": "user_model_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_model_preference_user_id_users_id_fk": { + "name": "user_model_preference_user_id_users_id_fk", + "tableFrom": "user_model_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_themes": { + "name": "custom_themes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "theme_data": { + "name": "theme_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_themes_user_id_users_id_fk": { + "name": "custom_themes_user_id_users_id_fk", + "tableFrom": "custom_themes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_theme_preference": { + "name": "user_theme_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "active_theme_id": { + "name": "active_theme_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_theme_preference_user_id_users_id_fk": { + "name": "user_theme_preference_user_id_users_id_fk", + "tableFrom": "user_theme_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_auth": { + "name": "google_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_account_key_encrypted": { + "name": "service_account_key_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_domain": { + "name": "workspace_domain", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shared_drive_id": { + "name": "shared_drive_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_drive_name": { + "name": "shared_drive_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connected_by": { + "name": "connected_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_auth_organization_id_organizations_id_fk": { + "name": "google_auth_organization_id_organizations_id_fk", + "tableFrom": "google_auth", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "google_auth_connected_by_users_id_fk": { + "name": "google_auth_connected_by_users_id_fk", + "tableFrom": "google_auth", + "tableTo": "users", + "columnsFrom": [ + "connected_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_starred_files": { + "name": "google_starred_files", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "google_file_id": { + "name": "google_file_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_starred_files_user_id_users_id_fk": { + "name": "google_starred_files_user_id_users_id_fk", + "tableFrom": "google_starred_files", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_dashboards": { + "name": "custom_dashboards", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "spec_data": { + "name": "spec_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "queries": { + "name": "queries", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "render_prompt": { + "name": "render_prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_dashboards_user_id_users_id_fk": { + "name": "custom_dashboards_user_id_users_id_fk", + "tableFrom": "custom_dashboards", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_api_keys": { + "name": "mcp_api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_api_keys_user_id_users_id_fk": { + "name": "mcp_api_keys_user_id_users_id_fk", + "tableFrom": "mcp_api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_usage": { + "name": "mcp_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "api_key_id": { + "name": "api_key_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_usage_api_key_id_mcp_api_keys_id_fk": { + "name": "mcp_usage_api_key_id_mcp_api_keys_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "mcp_api_keys", + "columnsFrom": [ + "api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_usage_user_id_users_id_fk": { + "name": "mcp_usage_user_id_users_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_members": { + "name": "channel_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "notify_level": { + "name": "notify_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channel_members_channel_id_channels_id_fk": { + "name": "channel_members_channel_id_channels_id_fk", + "tableFrom": "channel_members", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_members_user_id_users_id_fk": { + "name": "channel_members_user_id_users_id_fk", + "tableFrom": "channel_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_read_state": { + "name": "channel_read_state", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_read_message_id": { + "name": "last_read_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_read_at": { + "name": "last_read_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unread_count": { + "name": "unread_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "channel_read_state_user_id_users_id_fk": { + "name": "channel_read_state_user_id_users_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_read_state_channel_id_channels_id_fk": { + "name": "channel_read_state_channel_id_channels_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channels": { + "name": "channels", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channels_organization_id_organizations_id_fk": { + "name": "channels_organization_id_organizations_id_fk", + "tableFrom": "channels", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_project_id_projects_id_fk": { + "name": "channels_project_id_projects_id_fk", + "tableFrom": "channels", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_created_by_users_id_fk": { + "name": "channels_created_by_users_id_fk", + "tableFrom": "channels", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_attachments": { + "name": "message_attachments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_path": { + "name": "r2_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_attachments_message_id_messages_id_fk": { + "name": "message_attachments_message_id_messages_id_fk", + "tableFrom": "message_attachments", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_reactions": { + "name": "message_reactions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_reactions_message_id_messages_id_fk": { + "name": "message_reactions_message_id_messages_id_fk", + "tableFrom": "message_reactions", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_reactions_user_id_users_id_fk": { + "name": "message_reactions_user_id_users_id_fk", + "tableFrom": "message_reactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_html": { + "name": "content_html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edited_at": { + "name": "edited_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_reply_at": { + "name": "last_reply_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "messages_channel_id_channels_id_fk": { + "name": "messages_channel_id_channels_id_fk", + "tableFrom": "messages", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_thread_id_messages_id_fk": { + "name": "messages_thread_id_messages_id_fk", + "tableFrom": "messages", + "tableTo": "messages", + "columnsFrom": [ + "thread_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_deleted_by_users_id_fk": { + "name": "messages_deleted_by_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0021_snapshot.json b/drizzle/meta/0021_snapshot.json new file mode 100644 index 0000000..04b0200 --- /dev/null +++ b/drizzle/meta/0021_snapshot.json @@ -0,0 +1,4619 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "f755d52c-aa0a-4e17-984b-25b473513ac9", + "prevId": "3beb1b78-fd75-4351-841f-05b81e63fc6c", + "tables": { + "agent_conversations": { + "name": "agent_conversations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_conversations_user_id_users_id_fk": { + "name": "agent_conversations_user_id_users_id_fk", + "tableFrom": "agent_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_memories": { + "name": "agent_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "embedding": { + "name": "embedding", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_memories_conversation_id_agent_conversations_id_fk": { + "name": "agent_memories_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_memories", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memories_user_id_users_id_fk": { + "name": "agent_memories_user_id_users_id_fk", + "tableFrom": "agent_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "customers": { + "name": "customers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company": { + "name": "company", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback": { + "name": "feedback", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_url": { + "name": "page_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_width": { + "name": "viewport_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_height": { + "name": "viewport_height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_hash": { + "name": "ip_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback_interviews": { + "name": "feedback_interviews", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_role": { + "name": "user_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pain_points": { + "name": "pain_points", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feature_requests": { + "name": "feature_requests", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "overall_sentiment": { + "name": "overall_sentiment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "feedback_interviews_user_id_users_id_fk": { + "name": "feedback_interviews_user_id_users_id_fk", + "tableFrom": "feedback_interviews", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "group_members": { + "name": "group_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_members_group_id_groups_id_fk": { + "name": "group_members_group_id_groups_id_fk", + "tableFrom": "group_members", + "tableTo": "groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_members_user_id_users_id_fk": { + "name": "group_members_user_id_users_id_fk", + "tableFrom": "group_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "groups": { + "name": "groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groups_organization_id_organizations_id_fk": { + "name": "groups_organization_id_organizations_id_fk", + "tableFrom": "groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_members": { + "name": "project_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "project_members_project_id_projects_id_fk": { + "name": "project_members_project_id_projects_id_fk", + "tableFrom": "project_members", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_members_user_id_users_id_fk": { + "name": "project_members_user_id_users_id_fk", + "tableFrom": "project_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'OPEN'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_manager": { + "name": "project_manager", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_job_id": { + "name": "netsuite_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "push_tokens": { + "name": "push_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "push_tokens_user_id_users_id_fk": { + "name": "push_tokens_user_id_users_id_fk", + "tableFrom": "push_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_baselines": { + "name": "schedule_baselines", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_data": { + "name": "snapshot_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_baselines_project_id_projects_id_fk": { + "name": "schedule_baselines_project_id_projects_id_fk", + "tableFrom": "schedule_baselines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_tasks": { + "name": "schedule_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workdays": { + "name": "workdays", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date_calculated": { + "name": "end_date_calculated", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'PENDING'" + }, + "is_critical_path": { + "name": "is_critical_path", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_milestone": { + "name": "is_milestone", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "percent_complete": { + "name": "percent_complete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_tasks_project_id_projects_id_fk": { + "name": "schedule_tasks_project_id_projects_id_fk", + "tableFrom": "schedule_tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slab_memories": { + "name": "slab_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "memory_type": { + "name": "memory_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "importance": { + "name": "importance", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0.7 + }, + "pinned": { + "name": "pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "slab_memories_user_id_users_id_fk": { + "name": "slab_memories_user_id_users_id_fk", + "tableFrom": "slab_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_dependencies": { + "name": "task_dependencies", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "predecessor_id": { + "name": "predecessor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "successor_id": { + "name": "successor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'FS'" + }, + "lag_days": { + "name": "lag_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "task_dependencies_predecessor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_predecessor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "predecessor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_dependencies_successor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_successor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "successor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_members": { + "name": "team_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "teams": { + "name": "teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_organization_id_organizations_id_fk": { + "name": "teams_organization_id_organizations_id_fk", + "tableFrom": "teams", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'office'" + }, + "google_email": { + "name": "google_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendors": { + "name": "vendors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Subcontractor'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workday_exceptions": { + "name": "workday_exceptions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'non_working'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company_holiday'" + }, + "recurrence": { + "name": "recurrence", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'one_time'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "workday_exceptions_project_id_projects_id_fk": { + "name": "workday_exceptions_project_id_projects_id_fk", + "tableFrom": "workday_exceptions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credit_memos": { + "name": "credit_memos", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo_number": { + "name": "memo_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_applied": { + "name": "amount_applied", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_remaining": { + "name": "amount_remaining", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "credit_memos_customer_id_customers_id_fk": { + "name": "credit_memos_customer_id_customers_id_fk", + "tableFrom": "credit_memos", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "credit_memos_project_id_projects_id_fk": { + "name": "credit_memos_project_id_projects_id_fk", + "tableFrom": "credit_memos", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invoices_customer_id_customers_id_fk": { + "name": "invoices_customer_id_customers_id_fk", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_project_id_projects_id_fk": { + "name": "invoices_project_id_projects_id_fk", + "tableFrom": "invoices", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_auth": { + "name": "netsuite_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_in": { + "name": "expires_in", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issued_at": { + "name": "issued_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_log": { + "name": "netsuite_sync_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sync_type": { + "name": "sync_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_type": { + "name": "record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "records_processed": { + "name": "records_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "records_failed": { + "name": "records_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "error_summary": { + "name": "error_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_metadata": { + "name": "netsuite_sync_metadata", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "local_table": { + "name": "local_table", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_record_id": { + "name": "local_record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_record_type": { + "name": "netsuite_record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_internal_id": { + "name": "netsuite_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_local": { + "name": "last_modified_local", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_remote": { + "name": "last_modified_remote", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'synced'" + }, + "conflict_data": { + "name": "conflict_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payments": { + "name": "payments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_type": { + "name": "payment_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_date": { + "name": "payment_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "payments_customer_id_customers_id_fk": { + "name": "payments_customer_id_customers_id_fk", + "tableFrom": "payments", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_vendor_id_vendors_id_fk": { + "name": "payments_vendor_id_vendors_id_fk", + "tableFrom": "payments", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_project_id_projects_id_fk": { + "name": "payments_project_id_projects_id_fk", + "tableFrom": "payments", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendor_bills": { + "name": "vendor_bills", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bill_number": { + "name": "bill_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "bill_date": { + "name": "bill_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "vendor_bills_vendor_id_vendors_id_fk": { + "name": "vendor_bills_vendor_id_vendors_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "vendor_bills_project_id_projects_id_fk": { + "name": "vendor_bills_project_id_projects_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_config": { + "name": "plugin_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_encrypted": { + "name": "is_encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_events": { + "name": "plugin_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_events_plugin_id_plugins_id_fk": { + "name": "plugin_events_plugin_id_plugins_id_fk", + "tableFrom": "plugin_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_events_user_id_users_id_fk": { + "name": "plugin_events_user_id_users_id_fk", + "tableFrom": "plugin_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "required_env_vars": { + "name": "required_env_vars", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_by": { + "name": "enabled_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_at": { + "name": "enabled_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugins_enabled_by_users_id_fk": { + "name": "plugins_enabled_by_users_id_fk", + "tableFrom": "plugins", + "tableTo": "users", + "columnsFrom": [ + "enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_items": { + "name": "agent_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "done": { + "name": "done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_items_user_id_users_id_fk": { + "name": "agent_items_user_id_users_id_fk", + "tableFrom": "agent_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_config": { + "name": "agent_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_name": { + "name": "model_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_cost_per_million": { + "name": "max_cost_per_million", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_user_selection": { + "name": "allow_user_selection", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_config_updated_by_users_id_fk": { + "name": "agent_config_updated_by_users_id_fk", + "tableFrom": "agent_config", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_usage": { + "name": "agent_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_tokens": { + "name": "prompt_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "completion_tokens": { + "name": "completion_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_cost": { + "name": "estimated_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_usage_conversation_id_agent_conversations_id_fk": { + "name": "agent_usage_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_usage", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_usage_user_id_users_id_fk": { + "name": "agent_usage_user_id_users_id_fk", + "tableFrom": "agent_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_model_preference": { + "name": "user_model_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_model_preference_user_id_users_id_fk": { + "name": "user_model_preference_user_id_users_id_fk", + "tableFrom": "user_model_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_themes": { + "name": "custom_themes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "theme_data": { + "name": "theme_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_themes_user_id_users_id_fk": { + "name": "custom_themes_user_id_users_id_fk", + "tableFrom": "custom_themes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_theme_preference": { + "name": "user_theme_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "active_theme_id": { + "name": "active_theme_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_theme_preference_user_id_users_id_fk": { + "name": "user_theme_preference_user_id_users_id_fk", + "tableFrom": "user_theme_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_auth": { + "name": "google_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_account_key_encrypted": { + "name": "service_account_key_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_domain": { + "name": "workspace_domain", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shared_drive_id": { + "name": "shared_drive_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_drive_name": { + "name": "shared_drive_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connected_by": { + "name": "connected_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_auth_organization_id_organizations_id_fk": { + "name": "google_auth_organization_id_organizations_id_fk", + "tableFrom": "google_auth", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "google_auth_connected_by_users_id_fk": { + "name": "google_auth_connected_by_users_id_fk", + "tableFrom": "google_auth", + "tableTo": "users", + "columnsFrom": [ + "connected_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_starred_files": { + "name": "google_starred_files", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "google_file_id": { + "name": "google_file_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_starred_files_user_id_users_id_fk": { + "name": "google_starred_files_user_id_users_id_fk", + "tableFrom": "google_starred_files", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_dashboards": { + "name": "custom_dashboards", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "spec_data": { + "name": "spec_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "queries": { + "name": "queries", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "render_prompt": { + "name": "render_prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_dashboards_user_id_users_id_fk": { + "name": "custom_dashboards_user_id_users_id_fk", + "tableFrom": "custom_dashboards", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_api_keys": { + "name": "mcp_api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_api_keys_user_id_users_id_fk": { + "name": "mcp_api_keys_user_id_users_id_fk", + "tableFrom": "mcp_api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_usage": { + "name": "mcp_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "api_key_id": { + "name": "api_key_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_usage_api_key_id_mcp_api_keys_id_fk": { + "name": "mcp_usage_api_key_id_mcp_api_keys_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "mcp_api_keys", + "columnsFrom": [ + "api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_usage_user_id_users_id_fk": { + "name": "mcp_usage_user_id_users_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_categories": { + "name": "channel_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "collapsed_by_default": { + "name": "collapsed_by_default", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channel_categories_organization_id_organizations_id_fk": { + "name": "channel_categories_organization_id_organizations_id_fk", + "tableFrom": "channel_categories", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_members": { + "name": "channel_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "notify_level": { + "name": "notify_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channel_members_channel_id_channels_id_fk": { + "name": "channel_members_channel_id_channels_id_fk", + "tableFrom": "channel_members", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_members_user_id_users_id_fk": { + "name": "channel_members_user_id_users_id_fk", + "tableFrom": "channel_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_read_state": { + "name": "channel_read_state", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_read_message_id": { + "name": "last_read_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_read_at": { + "name": "last_read_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unread_count": { + "name": "unread_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "channel_read_state_user_id_users_id_fk": { + "name": "channel_read_state_user_id_users_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_read_state_channel_id_channels_id_fk": { + "name": "channel_read_state_channel_id_channels_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channels": { + "name": "channels", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channels_organization_id_organizations_id_fk": { + "name": "channels_organization_id_organizations_id_fk", + "tableFrom": "channels", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_project_id_projects_id_fk": { + "name": "channels_project_id_projects_id_fk", + "tableFrom": "channels", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_category_id_channel_categories_id_fk": { + "name": "channels_category_id_channel_categories_id_fk", + "tableFrom": "channels", + "tableTo": "channel_categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "channels_created_by_users_id_fk": { + "name": "channels_created_by_users_id_fk", + "tableFrom": "channels", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_attachments": { + "name": "message_attachments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_path": { + "name": "r2_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_attachments_message_id_messages_id_fk": { + "name": "message_attachments_message_id_messages_id_fk", + "tableFrom": "message_attachments", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_reactions": { + "name": "message_reactions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_reactions_message_id_messages_id_fk": { + "name": "message_reactions_message_id_messages_id_fk", + "tableFrom": "message_reactions", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_reactions_user_id_users_id_fk": { + "name": "message_reactions_user_id_users_id_fk", + "tableFrom": "message_reactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_html": { + "name": "content_html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edited_at": { + "name": "edited_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_reply_at": { + "name": "last_reply_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "messages_channel_id_channels_id_fk": { + "name": "messages_channel_id_channels_id_fk", + "tableFrom": "messages", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_deleted_by_users_id_fk": { + "name": "messages_deleted_by_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "typing_sessions": { + "name": "typing_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "typing_sessions_channel_id_channels_id_fk": { + "name": "typing_sessions_channel_id_channels_id_fk", + "tableFrom": "typing_sessions", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "typing_sessions_user_id_users_id_fk": { + "name": "typing_sessions_user_id_users_id_fk", + "tableFrom": "typing_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_presence": { + "name": "user_presence", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'offline'" + }, + "status_message": { + "name": "status_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_presence_user_id_users_id_fk": { + "name": "user_presence_user_id_users_id_fk", + "tableFrom": "user_presence", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0022_snapshot.json b/drizzle/meta/0022_snapshot.json new file mode 100644 index 0000000..1e1da4a --- /dev/null +++ b/drizzle/meta/0022_snapshot.json @@ -0,0 +1,4695 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "752455d1-7382-49ab-8cce-e2e777d3b89f", + "prevId": "f755d52c-aa0a-4e17-984b-25b473513ac9", + "tables": { + "agent_conversations": { + "name": "agent_conversations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_conversations_user_id_users_id_fk": { + "name": "agent_conversations_user_id_users_id_fk", + "tableFrom": "agent_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_memories": { + "name": "agent_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "embedding": { + "name": "embedding", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_memories_conversation_id_agent_conversations_id_fk": { + "name": "agent_memories_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_memories", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memories_user_id_users_id_fk": { + "name": "agent_memories_user_id_users_id_fk", + "tableFrom": "agent_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "customers": { + "name": "customers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company": { + "name": "company", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback": { + "name": "feedback", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_url": { + "name": "page_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_width": { + "name": "viewport_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_height": { + "name": "viewport_height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_hash": { + "name": "ip_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback_interviews": { + "name": "feedback_interviews", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_role": { + "name": "user_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pain_points": { + "name": "pain_points", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feature_requests": { + "name": "feature_requests", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "overall_sentiment": { + "name": "overall_sentiment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "feedback_interviews_user_id_users_id_fk": { + "name": "feedback_interviews_user_id_users_id_fk", + "tableFrom": "feedback_interviews", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "group_members": { + "name": "group_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_members_group_id_groups_id_fk": { + "name": "group_members_group_id_groups_id_fk", + "tableFrom": "group_members", + "tableTo": "groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_members_user_id_users_id_fk": { + "name": "group_members_user_id_users_id_fk", + "tableFrom": "group_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "groups": { + "name": "groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groups_organization_id_organizations_id_fk": { + "name": "groups_organization_id_organizations_id_fk", + "tableFrom": "groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_members": { + "name": "project_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "project_members_project_id_projects_id_fk": { + "name": "project_members_project_id_projects_id_fk", + "tableFrom": "project_members", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_members_user_id_users_id_fk": { + "name": "project_members_user_id_users_id_fk", + "tableFrom": "project_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'OPEN'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_manager": { + "name": "project_manager", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_job_id": { + "name": "netsuite_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "push_tokens": { + "name": "push_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "push_tokens_user_id_users_id_fk": { + "name": "push_tokens_user_id_users_id_fk", + "tableFrom": "push_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_baselines": { + "name": "schedule_baselines", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_data": { + "name": "snapshot_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_baselines_project_id_projects_id_fk": { + "name": "schedule_baselines_project_id_projects_id_fk", + "tableFrom": "schedule_baselines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_tasks": { + "name": "schedule_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workdays": { + "name": "workdays", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date_calculated": { + "name": "end_date_calculated", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'PENDING'" + }, + "is_critical_path": { + "name": "is_critical_path", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_milestone": { + "name": "is_milestone", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "percent_complete": { + "name": "percent_complete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_tasks_project_id_projects_id_fk": { + "name": "schedule_tasks_project_id_projects_id_fk", + "tableFrom": "schedule_tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slab_memories": { + "name": "slab_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "memory_type": { + "name": "memory_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "importance": { + "name": "importance", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0.7 + }, + "pinned": { + "name": "pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "slab_memories_user_id_users_id_fk": { + "name": "slab_memories_user_id_users_id_fk", + "tableFrom": "slab_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_dependencies": { + "name": "task_dependencies", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "predecessor_id": { + "name": "predecessor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "successor_id": { + "name": "successor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'FS'" + }, + "lag_days": { + "name": "lag_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "task_dependencies_predecessor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_predecessor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "predecessor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_dependencies_successor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_successor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "successor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_members": { + "name": "team_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "teams": { + "name": "teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_organization_id_organizations_id_fk": { + "name": "teams_organization_id_organizations_id_fk", + "tableFrom": "teams", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'office'" + }, + "google_email": { + "name": "google_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendors": { + "name": "vendors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Subcontractor'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workday_exceptions": { + "name": "workday_exceptions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'non_working'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company_holiday'" + }, + "recurrence": { + "name": "recurrence", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'one_time'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "workday_exceptions_project_id_projects_id_fk": { + "name": "workday_exceptions_project_id_projects_id_fk", + "tableFrom": "workday_exceptions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credit_memos": { + "name": "credit_memos", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo_number": { + "name": "memo_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_applied": { + "name": "amount_applied", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_remaining": { + "name": "amount_remaining", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "credit_memos_customer_id_customers_id_fk": { + "name": "credit_memos_customer_id_customers_id_fk", + "tableFrom": "credit_memos", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "credit_memos_project_id_projects_id_fk": { + "name": "credit_memos_project_id_projects_id_fk", + "tableFrom": "credit_memos", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invoices_customer_id_customers_id_fk": { + "name": "invoices_customer_id_customers_id_fk", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_project_id_projects_id_fk": { + "name": "invoices_project_id_projects_id_fk", + "tableFrom": "invoices", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_auth": { + "name": "netsuite_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_in": { + "name": "expires_in", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issued_at": { + "name": "issued_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_log": { + "name": "netsuite_sync_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sync_type": { + "name": "sync_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_type": { + "name": "record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "records_processed": { + "name": "records_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "records_failed": { + "name": "records_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "error_summary": { + "name": "error_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_metadata": { + "name": "netsuite_sync_metadata", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "local_table": { + "name": "local_table", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_record_id": { + "name": "local_record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_record_type": { + "name": "netsuite_record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_internal_id": { + "name": "netsuite_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_local": { + "name": "last_modified_local", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_remote": { + "name": "last_modified_remote", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'synced'" + }, + "conflict_data": { + "name": "conflict_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payments": { + "name": "payments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_type": { + "name": "payment_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_date": { + "name": "payment_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "payments_customer_id_customers_id_fk": { + "name": "payments_customer_id_customers_id_fk", + "tableFrom": "payments", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_vendor_id_vendors_id_fk": { + "name": "payments_vendor_id_vendors_id_fk", + "tableFrom": "payments", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_project_id_projects_id_fk": { + "name": "payments_project_id_projects_id_fk", + "tableFrom": "payments", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendor_bills": { + "name": "vendor_bills", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bill_number": { + "name": "bill_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "bill_date": { + "name": "bill_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "vendor_bills_vendor_id_vendors_id_fk": { + "name": "vendor_bills_vendor_id_vendors_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "vendor_bills_project_id_projects_id_fk": { + "name": "vendor_bills_project_id_projects_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_config": { + "name": "plugin_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_encrypted": { + "name": "is_encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_events": { + "name": "plugin_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_events_plugin_id_plugins_id_fk": { + "name": "plugin_events_plugin_id_plugins_id_fk", + "tableFrom": "plugin_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_events_user_id_users_id_fk": { + "name": "plugin_events_user_id_users_id_fk", + "tableFrom": "plugin_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "required_env_vars": { + "name": "required_env_vars", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_by": { + "name": "enabled_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_at": { + "name": "enabled_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugins_enabled_by_users_id_fk": { + "name": "plugins_enabled_by_users_id_fk", + "tableFrom": "plugins", + "tableTo": "users", + "columnsFrom": [ + "enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_items": { + "name": "agent_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "done": { + "name": "done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_items_user_id_users_id_fk": { + "name": "agent_items_user_id_users_id_fk", + "tableFrom": "agent_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_config": { + "name": "agent_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_name": { + "name": "model_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_cost_per_million": { + "name": "max_cost_per_million", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_user_selection": { + "name": "allow_user_selection", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_config_updated_by_users_id_fk": { + "name": "agent_config_updated_by_users_id_fk", + "tableFrom": "agent_config", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_usage": { + "name": "agent_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_tokens": { + "name": "prompt_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "completion_tokens": { + "name": "completion_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_cost": { + "name": "estimated_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_usage_conversation_id_agent_conversations_id_fk": { + "name": "agent_usage_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_usage", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_usage_user_id_users_id_fk": { + "name": "agent_usage_user_id_users_id_fk", + "tableFrom": "agent_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_model_preference": { + "name": "user_model_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_model_preference_user_id_users_id_fk": { + "name": "user_model_preference_user_id_users_id_fk", + "tableFrom": "user_model_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_themes": { + "name": "custom_themes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "theme_data": { + "name": "theme_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_themes_user_id_users_id_fk": { + "name": "custom_themes_user_id_users_id_fk", + "tableFrom": "custom_themes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_theme_preference": { + "name": "user_theme_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "active_theme_id": { + "name": "active_theme_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_theme_preference_user_id_users_id_fk": { + "name": "user_theme_preference_user_id_users_id_fk", + "tableFrom": "user_theme_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_auth": { + "name": "google_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_account_key_encrypted": { + "name": "service_account_key_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_domain": { + "name": "workspace_domain", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shared_drive_id": { + "name": "shared_drive_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_drive_name": { + "name": "shared_drive_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connected_by": { + "name": "connected_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_auth_organization_id_organizations_id_fk": { + "name": "google_auth_organization_id_organizations_id_fk", + "tableFrom": "google_auth", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "google_auth_connected_by_users_id_fk": { + "name": "google_auth_connected_by_users_id_fk", + "tableFrom": "google_auth", + "tableTo": "users", + "columnsFrom": [ + "connected_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_starred_files": { + "name": "google_starred_files", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "google_file_id": { + "name": "google_file_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_starred_files_user_id_users_id_fk": { + "name": "google_starred_files_user_id_users_id_fk", + "tableFrom": "google_starred_files", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_dashboards": { + "name": "custom_dashboards", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "spec_data": { + "name": "spec_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "queries": { + "name": "queries", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "render_prompt": { + "name": "render_prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_dashboards_user_id_users_id_fk": { + "name": "custom_dashboards_user_id_users_id_fk", + "tableFrom": "custom_dashboards", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_api_keys": { + "name": "mcp_api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_api_keys_user_id_users_id_fk": { + "name": "mcp_api_keys_user_id_users_id_fk", + "tableFrom": "mcp_api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_usage": { + "name": "mcp_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "api_key_id": { + "name": "api_key_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_usage_api_key_id_mcp_api_keys_id_fk": { + "name": "mcp_usage_api_key_id_mcp_api_keys_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "mcp_api_keys", + "columnsFrom": [ + "api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_usage_user_id_users_id_fk": { + "name": "mcp_usage_user_id_users_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_categories": { + "name": "channel_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "collapsed_by_default": { + "name": "collapsed_by_default", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_channel_categories_org": { + "name": "idx_channel_categories_org", + "columns": [ + "organization_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "channel_categories_organization_id_organizations_id_fk": { + "name": "channel_categories_organization_id_organizations_id_fk", + "tableFrom": "channel_categories", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_members": { + "name": "channel_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "notify_level": { + "name": "notify_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_channel_members_lookup": { + "name": "idx_channel_members_lookup", + "columns": [ + "channel_id", + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "channel_members_channel_id_channels_id_fk": { + "name": "channel_members_channel_id_channels_id_fk", + "tableFrom": "channel_members", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_members_user_id_users_id_fk": { + "name": "channel_members_user_id_users_id_fk", + "tableFrom": "channel_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_read_state": { + "name": "channel_read_state", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_read_message_id": { + "name": "last_read_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_read_at": { + "name": "last_read_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unread_count": { + "name": "unread_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": { + "idx_channel_read_state_lookup": { + "name": "idx_channel_read_state_lookup", + "columns": [ + "channel_id", + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "channel_read_state_user_id_users_id_fk": { + "name": "channel_read_state_user_id_users_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_read_state_channel_id_channels_id_fk": { + "name": "channel_read_state_channel_id_channels_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channels": { + "name": "channels", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channels_organization_id_organizations_id_fk": { + "name": "channels_organization_id_organizations_id_fk", + "tableFrom": "channels", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_project_id_projects_id_fk": { + "name": "channels_project_id_projects_id_fk", + "tableFrom": "channels", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_category_id_channel_categories_id_fk": { + "name": "channels_category_id_channel_categories_id_fk", + "tableFrom": "channels", + "tableTo": "channel_categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "channels_created_by_users_id_fk": { + "name": "channels_created_by_users_id_fk", + "tableFrom": "channels", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_attachments": { + "name": "message_attachments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_path": { + "name": "r2_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_attachments_message_id_messages_id_fk": { + "name": "message_attachments_message_id_messages_id_fk", + "tableFrom": "message_attachments", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_reactions": { + "name": "message_reactions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_message_reactions_message": { + "name": "idx_message_reactions_message", + "columns": [ + "message_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "message_reactions_message_id_messages_id_fk": { + "name": "message_reactions_message_id_messages_id_fk", + "tableFrom": "message_reactions", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_reactions_user_id_users_id_fk": { + "name": "message_reactions_user_id_users_id_fk", + "tableFrom": "message_reactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_html": { + "name": "content_html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edited_at": { + "name": "edited_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_reply_at": { + "name": "last_reply_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_messages_channel_created": { + "name": "idx_messages_channel_created", + "columns": [ + "channel_id", + "created_at" + ], + "isUnique": false + }, + "idx_messages_thread": { + "name": "idx_messages_thread", + "columns": [ + "thread_id" + ], + "isUnique": false + }, + "idx_messages_channel_pinned": { + "name": "idx_messages_channel_pinned", + "columns": [ + "channel_id", + "is_pinned" + ], + "isUnique": false, + "where": "is_pinned = 1" + } + }, + "foreignKeys": { + "messages_channel_id_channels_id_fk": { + "name": "messages_channel_id_channels_id_fk", + "tableFrom": "messages", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_deleted_by_users_id_fk": { + "name": "messages_deleted_by_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "typing_sessions": { + "name": "typing_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_typing_sessions_channel_expires": { + "name": "idx_typing_sessions_channel_expires", + "columns": [ + "channel_id", + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "typing_sessions_channel_id_channels_id_fk": { + "name": "typing_sessions_channel_id_channels_id_fk", + "tableFrom": "typing_sessions", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "typing_sessions_user_id_users_id_fk": { + "name": "typing_sessions_user_id_users_id_fk", + "tableFrom": "typing_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_presence": { + "name": "user_presence", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'offline'" + }, + "status_message": { + "name": "status_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "idx_user_presence_user": { + "name": "idx_user_presence_user", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "user_presence_user_id_users_id_fk": { + "name": "user_presence_user_id_users_id_fk", + "tableFrom": "user_presence", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0023_snapshot.json b/drizzle/meta/0023_snapshot.json new file mode 100644 index 0000000..fcd6f60 --- /dev/null +++ b/drizzle/meta/0023_snapshot.json @@ -0,0 +1,4619 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "d6c1ff93-3266-4e0b-a9cc-e97a660ac6f3", + "prevId": "752455d1-7382-49ab-8cce-e2e777d3b89f", + "tables": { + "agent_conversations": { + "name": "agent_conversations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_conversations_user_id_users_id_fk": { + "name": "agent_conversations_user_id_users_id_fk", + "tableFrom": "agent_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_memories": { + "name": "agent_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "embedding": { + "name": "embedding", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_memories_conversation_id_agent_conversations_id_fk": { + "name": "agent_memories_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_memories", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memories_user_id_users_id_fk": { + "name": "agent_memories_user_id_users_id_fk", + "tableFrom": "agent_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "customers": { + "name": "customers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company": { + "name": "company", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback": { + "name": "feedback", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_url": { + "name": "page_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_width": { + "name": "viewport_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_height": { + "name": "viewport_height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_hash": { + "name": "ip_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback_interviews": { + "name": "feedback_interviews", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_role": { + "name": "user_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pain_points": { + "name": "pain_points", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feature_requests": { + "name": "feature_requests", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "overall_sentiment": { + "name": "overall_sentiment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "feedback_interviews_user_id_users_id_fk": { + "name": "feedback_interviews_user_id_users_id_fk", + "tableFrom": "feedback_interviews", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "group_members": { + "name": "group_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_members_group_id_groups_id_fk": { + "name": "group_members_group_id_groups_id_fk", + "tableFrom": "group_members", + "tableTo": "groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_members_user_id_users_id_fk": { + "name": "group_members_user_id_users_id_fk", + "tableFrom": "group_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "groups": { + "name": "groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groups_organization_id_organizations_id_fk": { + "name": "groups_organization_id_organizations_id_fk", + "tableFrom": "groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_members": { + "name": "project_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "project_members_project_id_projects_id_fk": { + "name": "project_members_project_id_projects_id_fk", + "tableFrom": "project_members", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_members_user_id_users_id_fk": { + "name": "project_members_user_id_users_id_fk", + "tableFrom": "project_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'OPEN'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_manager": { + "name": "project_manager", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_job_id": { + "name": "netsuite_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "push_tokens": { + "name": "push_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "push_tokens_user_id_users_id_fk": { + "name": "push_tokens_user_id_users_id_fk", + "tableFrom": "push_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_baselines": { + "name": "schedule_baselines", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_data": { + "name": "snapshot_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_baselines_project_id_projects_id_fk": { + "name": "schedule_baselines_project_id_projects_id_fk", + "tableFrom": "schedule_baselines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_tasks": { + "name": "schedule_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workdays": { + "name": "workdays", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date_calculated": { + "name": "end_date_calculated", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'PENDING'" + }, + "is_critical_path": { + "name": "is_critical_path", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_milestone": { + "name": "is_milestone", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "percent_complete": { + "name": "percent_complete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_tasks_project_id_projects_id_fk": { + "name": "schedule_tasks_project_id_projects_id_fk", + "tableFrom": "schedule_tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slab_memories": { + "name": "slab_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "memory_type": { + "name": "memory_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "importance": { + "name": "importance", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0.7 + }, + "pinned": { + "name": "pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "slab_memories_user_id_users_id_fk": { + "name": "slab_memories_user_id_users_id_fk", + "tableFrom": "slab_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_dependencies": { + "name": "task_dependencies", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "predecessor_id": { + "name": "predecessor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "successor_id": { + "name": "successor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'FS'" + }, + "lag_days": { + "name": "lag_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "task_dependencies_predecessor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_predecessor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "predecessor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_dependencies_successor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_successor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "successor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_members": { + "name": "team_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "teams": { + "name": "teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_organization_id_organizations_id_fk": { + "name": "teams_organization_id_organizations_id_fk", + "tableFrom": "teams", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'office'" + }, + "google_email": { + "name": "google_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendors": { + "name": "vendors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Subcontractor'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workday_exceptions": { + "name": "workday_exceptions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'non_working'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company_holiday'" + }, + "recurrence": { + "name": "recurrence", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'one_time'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "workday_exceptions_project_id_projects_id_fk": { + "name": "workday_exceptions_project_id_projects_id_fk", + "tableFrom": "workday_exceptions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credit_memos": { + "name": "credit_memos", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo_number": { + "name": "memo_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_applied": { + "name": "amount_applied", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_remaining": { + "name": "amount_remaining", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "credit_memos_customer_id_customers_id_fk": { + "name": "credit_memos_customer_id_customers_id_fk", + "tableFrom": "credit_memos", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "credit_memos_project_id_projects_id_fk": { + "name": "credit_memos_project_id_projects_id_fk", + "tableFrom": "credit_memos", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invoices_customer_id_customers_id_fk": { + "name": "invoices_customer_id_customers_id_fk", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_project_id_projects_id_fk": { + "name": "invoices_project_id_projects_id_fk", + "tableFrom": "invoices", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_auth": { + "name": "netsuite_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_in": { + "name": "expires_in", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issued_at": { + "name": "issued_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_log": { + "name": "netsuite_sync_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sync_type": { + "name": "sync_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_type": { + "name": "record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "records_processed": { + "name": "records_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "records_failed": { + "name": "records_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "error_summary": { + "name": "error_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_metadata": { + "name": "netsuite_sync_metadata", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "local_table": { + "name": "local_table", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_record_id": { + "name": "local_record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_record_type": { + "name": "netsuite_record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_internal_id": { + "name": "netsuite_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_local": { + "name": "last_modified_local", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_remote": { + "name": "last_modified_remote", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'synced'" + }, + "conflict_data": { + "name": "conflict_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payments": { + "name": "payments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_type": { + "name": "payment_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_date": { + "name": "payment_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "payments_customer_id_customers_id_fk": { + "name": "payments_customer_id_customers_id_fk", + "tableFrom": "payments", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_vendor_id_vendors_id_fk": { + "name": "payments_vendor_id_vendors_id_fk", + "tableFrom": "payments", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_project_id_projects_id_fk": { + "name": "payments_project_id_projects_id_fk", + "tableFrom": "payments", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendor_bills": { + "name": "vendor_bills", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bill_number": { + "name": "bill_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "bill_date": { + "name": "bill_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "vendor_bills_vendor_id_vendors_id_fk": { + "name": "vendor_bills_vendor_id_vendors_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "vendor_bills_project_id_projects_id_fk": { + "name": "vendor_bills_project_id_projects_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_config": { + "name": "plugin_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_encrypted": { + "name": "is_encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_events": { + "name": "plugin_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_events_plugin_id_plugins_id_fk": { + "name": "plugin_events_plugin_id_plugins_id_fk", + "tableFrom": "plugin_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_events_user_id_users_id_fk": { + "name": "plugin_events_user_id_users_id_fk", + "tableFrom": "plugin_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "required_env_vars": { + "name": "required_env_vars", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_by": { + "name": "enabled_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_at": { + "name": "enabled_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugins_enabled_by_users_id_fk": { + "name": "plugins_enabled_by_users_id_fk", + "tableFrom": "plugins", + "tableTo": "users", + "columnsFrom": [ + "enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_items": { + "name": "agent_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "done": { + "name": "done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_items_user_id_users_id_fk": { + "name": "agent_items_user_id_users_id_fk", + "tableFrom": "agent_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_config": { + "name": "agent_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_name": { + "name": "model_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_cost_per_million": { + "name": "max_cost_per_million", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_user_selection": { + "name": "allow_user_selection", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_config_updated_by_users_id_fk": { + "name": "agent_config_updated_by_users_id_fk", + "tableFrom": "agent_config", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_usage": { + "name": "agent_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_tokens": { + "name": "prompt_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "completion_tokens": { + "name": "completion_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_cost": { + "name": "estimated_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_usage_conversation_id_agent_conversations_id_fk": { + "name": "agent_usage_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_usage", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_usage_user_id_users_id_fk": { + "name": "agent_usage_user_id_users_id_fk", + "tableFrom": "agent_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_model_preference": { + "name": "user_model_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_model_preference_user_id_users_id_fk": { + "name": "user_model_preference_user_id_users_id_fk", + "tableFrom": "user_model_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_themes": { + "name": "custom_themes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "theme_data": { + "name": "theme_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_themes_user_id_users_id_fk": { + "name": "custom_themes_user_id_users_id_fk", + "tableFrom": "custom_themes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_theme_preference": { + "name": "user_theme_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "active_theme_id": { + "name": "active_theme_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_theme_preference_user_id_users_id_fk": { + "name": "user_theme_preference_user_id_users_id_fk", + "tableFrom": "user_theme_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_auth": { + "name": "google_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_account_key_encrypted": { + "name": "service_account_key_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_domain": { + "name": "workspace_domain", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shared_drive_id": { + "name": "shared_drive_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_drive_name": { + "name": "shared_drive_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connected_by": { + "name": "connected_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_auth_organization_id_organizations_id_fk": { + "name": "google_auth_organization_id_organizations_id_fk", + "tableFrom": "google_auth", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "google_auth_connected_by_users_id_fk": { + "name": "google_auth_connected_by_users_id_fk", + "tableFrom": "google_auth", + "tableTo": "users", + "columnsFrom": [ + "connected_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_starred_files": { + "name": "google_starred_files", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "google_file_id": { + "name": "google_file_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_starred_files_user_id_users_id_fk": { + "name": "google_starred_files_user_id_users_id_fk", + "tableFrom": "google_starred_files", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_dashboards": { + "name": "custom_dashboards", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "spec_data": { + "name": "spec_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "queries": { + "name": "queries", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "render_prompt": { + "name": "render_prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_dashboards_user_id_users_id_fk": { + "name": "custom_dashboards_user_id_users_id_fk", + "tableFrom": "custom_dashboards", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_api_keys": { + "name": "mcp_api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_api_keys_user_id_users_id_fk": { + "name": "mcp_api_keys_user_id_users_id_fk", + "tableFrom": "mcp_api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_usage": { + "name": "mcp_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "api_key_id": { + "name": "api_key_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_usage_api_key_id_mcp_api_keys_id_fk": { + "name": "mcp_usage_api_key_id_mcp_api_keys_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "mcp_api_keys", + "columnsFrom": [ + "api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_usage_user_id_users_id_fk": { + "name": "mcp_usage_user_id_users_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_categories": { + "name": "channel_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "collapsed_by_default": { + "name": "collapsed_by_default", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channel_categories_organization_id_organizations_id_fk": { + "name": "channel_categories_organization_id_organizations_id_fk", + "tableFrom": "channel_categories", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_members": { + "name": "channel_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "notify_level": { + "name": "notify_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channel_members_channel_id_channels_id_fk": { + "name": "channel_members_channel_id_channels_id_fk", + "tableFrom": "channel_members", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_members_user_id_users_id_fk": { + "name": "channel_members_user_id_users_id_fk", + "tableFrom": "channel_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_read_state": { + "name": "channel_read_state", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_read_message_id": { + "name": "last_read_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_read_at": { + "name": "last_read_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unread_count": { + "name": "unread_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "channel_read_state_user_id_users_id_fk": { + "name": "channel_read_state_user_id_users_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_read_state_channel_id_channels_id_fk": { + "name": "channel_read_state_channel_id_channels_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channels": { + "name": "channels", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channels_organization_id_organizations_id_fk": { + "name": "channels_organization_id_organizations_id_fk", + "tableFrom": "channels", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_project_id_projects_id_fk": { + "name": "channels_project_id_projects_id_fk", + "tableFrom": "channels", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_category_id_channel_categories_id_fk": { + "name": "channels_category_id_channel_categories_id_fk", + "tableFrom": "channels", + "tableTo": "channel_categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "channels_created_by_users_id_fk": { + "name": "channels_created_by_users_id_fk", + "tableFrom": "channels", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_attachments": { + "name": "message_attachments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_path": { + "name": "r2_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_attachments_message_id_messages_id_fk": { + "name": "message_attachments_message_id_messages_id_fk", + "tableFrom": "message_attachments", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_reactions": { + "name": "message_reactions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_reactions_message_id_messages_id_fk": { + "name": "message_reactions_message_id_messages_id_fk", + "tableFrom": "message_reactions", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_reactions_user_id_users_id_fk": { + "name": "message_reactions_user_id_users_id_fk", + "tableFrom": "message_reactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_html": { + "name": "content_html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edited_at": { + "name": "edited_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_reply_at": { + "name": "last_reply_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "messages_channel_id_channels_id_fk": { + "name": "messages_channel_id_channels_id_fk", + "tableFrom": "messages", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_deleted_by_users_id_fk": { + "name": "messages_deleted_by_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "typing_sessions": { + "name": "typing_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "typing_sessions_channel_id_channels_id_fk": { + "name": "typing_sessions_channel_id_channels_id_fk", + "tableFrom": "typing_sessions", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "typing_sessions_user_id_users_id_fk": { + "name": "typing_sessions_user_id_users_id_fk", + "tableFrom": "typing_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_presence": { + "name": "user_presence", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'offline'" + }, + "status_message": { + "name": "status_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_presence_user_id_users_id_fk": { + "name": "user_presence_user_id_users_id_fk", + "tableFrom": "user_presence", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0024_snapshot.json b/drizzle/meta/0024_snapshot.json new file mode 100644 index 0000000..659802b --- /dev/null +++ b/drizzle/meta/0024_snapshot.json @@ -0,0 +1,4922 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "9d181cdd-820e-4d51-85dc-fd129df29f4c", + "prevId": "d6c1ff93-3266-4e0b-a9cc-e97a660ac6f3", + "tables": { + "agent_conversations": { + "name": "agent_conversations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_message_at": { + "name": "last_message_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_conversations_user_id_users_id_fk": { + "name": "agent_conversations_user_id_users_id_fk", + "tableFrom": "agent_conversations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_memories": { + "name": "agent_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "embedding": { + "name": "embedding", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_memories_conversation_id_agent_conversations_id_fk": { + "name": "agent_memories_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_memories", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_memories_user_id_users_id_fk": { + "name": "agent_memories_user_id_users_id_fk", + "tableFrom": "agent_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "customers": { + "name": "customers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "company": { + "name": "company", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback": { + "name": "feedback", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "page_url": { + "name": "page_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_width": { + "name": "viewport_width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "viewport_height": { + "name": "viewport_height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ip_hash": { + "name": "ip_hash", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "feedback_interviews": { + "name": "feedback_interviews", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_name": { + "name": "user_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_role": { + "name": "user_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "responses": { + "name": "responses", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "summary": { + "name": "summary", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "pain_points": { + "name": "pain_points", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "feature_requests": { + "name": "feature_requests", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "overall_sentiment": { + "name": "overall_sentiment", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_issue_url": { + "name": "github_issue_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "feedback_interviews_user_id_users_id_fk": { + "name": "feedback_interviews_user_id_users_id_fk", + "tableFrom": "feedback_interviews", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "group_members": { + "name": "group_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "group_id": { + "name": "group_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "group_members_group_id_groups_id_fk": { + "name": "group_members_group_id_groups_id_fk", + "tableFrom": "group_members", + "tableTo": "groups", + "columnsFrom": [ + "group_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "group_members_user_id_users_id_fk": { + "name": "group_members_user_id_users_id_fk", + "tableFrom": "group_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "groups": { + "name": "groups", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "groups_organization_id_organizations_id_fk": { + "name": "groups_organization_id_organizations_id_fk", + "tableFrom": "groups", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "logo_url": { + "name": "logo_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "project_members": { + "name": "project_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "assigned_at": { + "name": "assigned_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "project_members_project_id_projects_id_fk": { + "name": "project_members_project_id_projects_id_fk", + "tableFrom": "project_members", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "project_members_user_id_users_id_fk": { + "name": "project_members_user_id_users_id_fk", + "tableFrom": "project_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'OPEN'" + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_name": { + "name": "client_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_manager": { + "name": "project_manager", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_job_id": { + "name": "netsuite_job_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "projects_organization_id_organizations_id_fk": { + "name": "projects_organization_id_organizations_id_fk", + "tableFrom": "projects", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "push_tokens": { + "name": "push_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "platform": { + "name": "platform", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "push_tokens_user_id_users_id_fk": { + "name": "push_tokens_user_id_users_id_fk", + "tableFrom": "push_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_baselines": { + "name": "schedule_baselines", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "snapshot_data": { + "name": "snapshot_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_baselines_project_id_projects_id_fk": { + "name": "schedule_baselines_project_id_projects_id_fk", + "tableFrom": "schedule_baselines", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "schedule_tasks": { + "name": "schedule_tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workdays": { + "name": "workdays", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date_calculated": { + "name": "end_date_calculated", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "phase": { + "name": "phase", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'PENDING'" + }, + "is_critical_path": { + "name": "is_critical_path", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_milestone": { + "name": "is_milestone", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "percent_complete": { + "name": "percent_complete", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "assigned_to": { + "name": "assigned_to", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "schedule_tasks_project_id_projects_id_fk": { + "name": "schedule_tasks_project_id_projects_id_fk", + "tableFrom": "schedule_tasks", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "slab_memories": { + "name": "slab_memories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "memory_type": { + "name": "memory_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "importance": { + "name": "importance", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0.7 + }, + "pinned": { + "name": "pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "access_count": { + "name": "access_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_accessed_at": { + "name": "last_accessed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "slab_memories_user_id_users_id_fk": { + "name": "slab_memories_user_id_users_id_fk", + "tableFrom": "slab_memories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "task_dependencies": { + "name": "task_dependencies", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "predecessor_id": { + "name": "predecessor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "successor_id": { + "name": "successor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'FS'" + }, + "lag_days": { + "name": "lag_days", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "task_dependencies_predecessor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_predecessor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "predecessor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "task_dependencies_successor_id_schedule_tasks_id_fk": { + "name": "task_dependencies_successor_id_schedule_tasks_id_fk", + "tableFrom": "task_dependencies", + "tableTo": "schedule_tasks", + "columnsFrom": [ + "successor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_members": { + "name": "team_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "team_members_team_id_teams_id_fk": { + "name": "team_members_team_id_teams_id_fk", + "tableFrom": "team_members", + "tableTo": "teams", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_members_user_id_users_id_fk": { + "name": "team_members_user_id_users_id_fk", + "tableFrom": "team_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "teams": { + "name": "teams", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "teams_organization_id_organizations_id_fk": { + "name": "teams_organization_id_organizations_id_fk", + "tableFrom": "teams", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'office'" + }, + "google_email": { + "name": "google_email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "last_login_at": { + "name": "last_login_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendors": { + "name": "vendors", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'Subcontractor'" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "address": { + "name": "address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workday_exceptions": { + "name": "workday_exceptions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "start_date": { + "name": "start_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "end_date": { + "name": "end_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'non_working'" + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'company_holiday'" + }, + "recurrence": { + "name": "recurrence", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'one_time'" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "workday_exceptions_project_id_projects_id_fk": { + "name": "workday_exceptions_project_id_projects_id_fk", + "tableFrom": "workday_exceptions", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "credit_memos": { + "name": "credit_memos", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo_number": { + "name": "memo_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_applied": { + "name": "amount_applied", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_remaining": { + "name": "amount_remaining", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "credit_memos_customer_id_customers_id_fk": { + "name": "credit_memos_customer_id_customers_id_fk", + "tableFrom": "credit_memos", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "credit_memos_project_id_projects_id_fk": { + "name": "credit_memos_project_id_projects_id_fk", + "tableFrom": "credit_memos", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "invoices": { + "name": "invoices", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "invoice_number": { + "name": "invoice_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'draft'" + }, + "issue_date": { + "name": "issue_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "invoices_customer_id_customers_id_fk": { + "name": "invoices_customer_id_customers_id_fk", + "tableFrom": "invoices", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "invoices_project_id_projects_id_fk": { + "name": "invoices_project_id_projects_id_fk", + "tableFrom": "invoices", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_auth": { + "name": "netsuite_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token_encrypted": { + "name": "access_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token_encrypted": { + "name": "refresh_token_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_in": { + "name": "expires_in", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "issued_at": { + "name": "issued_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_log": { + "name": "netsuite_sync_log", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "sync_type": { + "name": "sync_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_type": { + "name": "record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "direction": { + "name": "direction", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "records_processed": { + "name": "records_processed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "records_failed": { + "name": "records_failed", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "error_summary": { + "name": "error_summary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "netsuite_sync_metadata": { + "name": "netsuite_sync_metadata", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "local_table": { + "name": "local_table", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "local_record_id": { + "name": "local_record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_record_type": { + "name": "netsuite_record_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "netsuite_internal_id": { + "name": "netsuite_internal_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_local": { + "name": "last_modified_local", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_modified_remote": { + "name": "last_modified_remote", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'synced'" + }, + "conflict_data": { + "name": "conflict_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "payments": { + "name": "payments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "customer_id": { + "name": "customer_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "payment_type": { + "name": "payment_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "amount": { + "name": "amount", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_date": { + "name": "payment_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payment_method": { + "name": "payment_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_number": { + "name": "reference_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "payments_customer_id_customers_id_fk": { + "name": "payments_customer_id_customers_id_fk", + "tableFrom": "payments", + "tableTo": "customers", + "columnsFrom": [ + "customer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_vendor_id_vendors_id_fk": { + "name": "payments_vendor_id_vendors_id_fk", + "tableFrom": "payments", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "payments_project_id_projects_id_fk": { + "name": "payments_project_id_projects_id_fk", + "tableFrom": "payments", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "vendor_bills": { + "name": "vendor_bills", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "netsuite_id": { + "name": "netsuite_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vendor_id": { + "name": "vendor_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "bill_number": { + "name": "bill_number", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "bill_date": { + "name": "bill_date", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subtotal": { + "name": "subtotal", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "tax": { + "name": "tax", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total": { + "name": "total", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_paid": { + "name": "amount_paid", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "amount_due": { + "name": "amount_due", + "type": "real", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "memo": { + "name": "memo", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "line_items": { + "name": "line_items", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "vendor_bills_vendor_id_vendors_id_fk": { + "name": "vendor_bills_vendor_id_vendors_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "vendors", + "columnsFrom": [ + "vendor_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "vendor_bills_project_id_projects_id_fk": { + "name": "vendor_bills_project_id_projects_id_fk", + "tableFrom": "vendor_bills", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_config": { + "name": "plugin_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_encrypted": { + "name": "is_encrypted", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_config_plugin_id_plugins_id_fk": { + "name": "plugin_config_plugin_id_plugins_id_fk", + "tableFrom": "plugin_config", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugin_events": { + "name": "plugin_events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "plugin_id": { + "name": "plugin_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "event_type": { + "name": "event_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugin_events_plugin_id_plugins_id_fk": { + "name": "plugin_events_plugin_id_plugins_id_fk", + "tableFrom": "plugin_events", + "tableTo": "plugins", + "columnsFrom": [ + "plugin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "plugin_events_user_id_users_id_fk": { + "name": "plugin_events_user_id_users_id_fk", + "tableFrom": "plugin_events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "plugins": { + "name": "plugins", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "version": { + "name": "version", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source": { + "name": "source", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "source_type": { + "name": "source_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "capabilities": { + "name": "capabilities", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "required_env_vars": { + "name": "required_env_vars", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'disabled'" + }, + "status_reason": { + "name": "status_reason", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_by": { + "name": "enabled_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enabled_at": { + "name": "enabled_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "installed_at": { + "name": "installed_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "plugins_enabled_by_users_id_fk": { + "name": "plugins_enabled_by_users_id_fk", + "tableFrom": "plugins", + "tableTo": "users", + "columnsFrom": [ + "enabled_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_items": { + "name": "agent_items", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "done": { + "name": "done", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "parent_id": { + "name": "parent_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_items_user_id_users_id_fk": { + "name": "agent_items_user_id_users_id_fk", + "tableFrom": "agent_items", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_config": { + "name": "agent_config", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_name": { + "name": "model_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "context_length": { + "name": "context_length", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "max_cost_per_million": { + "name": "max_cost_per_million", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "allow_user_selection": { + "name": "allow_user_selection", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "updated_by": { + "name": "updated_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_config_updated_by_users_id_fk": { + "name": "agent_config_updated_by_users_id_fk", + "tableFrom": "agent_config", + "tableTo": "users", + "columnsFrom": [ + "updated_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "agent_usage": { + "name": "agent_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "conversation_id": { + "name": "conversation_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_tokens": { + "name": "prompt_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "completion_tokens": { + "name": "completion_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "total_tokens": { + "name": "total_tokens", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "estimated_cost": { + "name": "estimated_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "agent_usage_conversation_id_agent_conversations_id_fk": { + "name": "agent_usage_conversation_id_agent_conversations_id_fk", + "tableFrom": "agent_usage", + "tableTo": "agent_conversations", + "columnsFrom": [ + "conversation_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "agent_usage_user_id_users_id_fk": { + "name": "agent_usage_user_id_users_id_fk", + "tableFrom": "agent_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_model_preference": { + "name": "user_model_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "model_id": { + "name": "model_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "prompt_cost": { + "name": "prompt_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "completion_cost": { + "name": "completion_cost", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_model_preference_user_id_users_id_fk": { + "name": "user_model_preference_user_id_users_id_fk", + "tableFrom": "user_model_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_themes": { + "name": "custom_themes", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "theme_data": { + "name": "theme_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_themes_user_id_users_id_fk": { + "name": "custom_themes_user_id_users_id_fk", + "tableFrom": "custom_themes", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_theme_preference": { + "name": "user_theme_preference", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "active_theme_id": { + "name": "active_theme_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_theme_preference_user_id_users_id_fk": { + "name": "user_theme_preference_user_id_users_id_fk", + "tableFrom": "user_theme_preference", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_auth": { + "name": "google_auth", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "service_account_key_encrypted": { + "name": "service_account_key_encrypted", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "workspace_domain": { + "name": "workspace_domain", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "shared_drive_id": { + "name": "shared_drive_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "shared_drive_name": { + "name": "shared_drive_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "connected_by": { + "name": "connected_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_auth_organization_id_organizations_id_fk": { + "name": "google_auth_organization_id_organizations_id_fk", + "tableFrom": "google_auth", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "google_auth_connected_by_users_id_fk": { + "name": "google_auth_connected_by_users_id_fk", + "tableFrom": "google_auth", + "tableTo": "users", + "columnsFrom": [ + "connected_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "google_starred_files": { + "name": "google_starred_files", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "google_file_id": { + "name": "google_file_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "google_starred_files_user_id_users_id_fk": { + "name": "google_starred_files_user_id_users_id_fk", + "tableFrom": "google_starred_files", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "custom_dashboards": { + "name": "custom_dashboards", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "''" + }, + "spec_data": { + "name": "spec_data", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "queries": { + "name": "queries", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "render_prompt": { + "name": "render_prompt", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "custom_dashboards_user_id_users_id_fk": { + "name": "custom_dashboards_user_id_users_id_fk", + "tableFrom": "custom_dashboards", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_api_keys": { + "name": "mcp_api_keys", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_prefix": { + "name": "key_prefix", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_used_at": { + "name": "last_used_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_api_keys_user_id_users_id_fk": { + "name": "mcp_api_keys_user_id_users_id_fk", + "tableFrom": "mcp_api_keys", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mcp_usage": { + "name": "mcp_usage", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "api_key_id": { + "name": "api_key_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tool_name": { + "name": "tool_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "success": { + "name": "success", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "duration_ms": { + "name": "duration_ms", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "mcp_usage_api_key_id_mcp_api_keys_id_fk": { + "name": "mcp_usage_api_key_id_mcp_api_keys_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "mcp_api_keys", + "columnsFrom": [ + "api_key_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "mcp_usage_user_id_users_id_fk": { + "name": "mcp_usage_user_id_users_id_fk", + "tableFrom": "mcp_usage", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_categories": { + "name": "channel_categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "collapsed_by_default": { + "name": "collapsed_by_default", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channel_categories_organization_id_organizations_id_fk": { + "name": "channel_categories_organization_id_organizations_id_fk", + "tableFrom": "channel_categories", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_members": { + "name": "channel_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "notify_level": { + "name": "notify_level", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'all'" + }, + "joined_at": { + "name": "joined_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channel_members_channel_id_channels_id_fk": { + "name": "channel_members_channel_id_channels_id_fk", + "tableFrom": "channel_members", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_members_user_id_users_id_fk": { + "name": "channel_members_user_id_users_id_fk", + "tableFrom": "channel_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channel_read_state": { + "name": "channel_read_state", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_read_message_id": { + "name": "last_read_message_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_read_at": { + "name": "last_read_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "unread_count": { + "name": "unread_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + } + }, + "indexes": {}, + "foreignKeys": { + "channel_read_state_user_id_users_id_fk": { + "name": "channel_read_state_user_id_users_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channel_read_state_channel_id_channels_id_fk": { + "name": "channel_read_state_channel_id_channels_id_fk", + "tableFrom": "channel_read_state", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "channels": { + "name": "channels", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'text'" + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "category_id": { + "name": "category_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "archived_at": { + "name": "archived_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "channels_organization_id_organizations_id_fk": { + "name": "channels_organization_id_organizations_id_fk", + "tableFrom": "channels", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_project_id_projects_id_fk": { + "name": "channels_project_id_projects_id_fk", + "tableFrom": "channels", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "channels_category_id_channel_categories_id_fk": { + "name": "channels_category_id_channel_categories_id_fk", + "tableFrom": "channels", + "tableTo": "channel_categories", + "columnsFrom": [ + "category_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "channels_created_by_users_id_fk": { + "name": "channels_created_by_users_id_fk", + "tableFrom": "channels", + "tableTo": "users", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_attachments": { + "name": "message_attachments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_name": { + "name": "file_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "file_size": { + "name": "file_size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "r2_path": { + "name": "r2_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "width": { + "name": "width", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "height": { + "name": "height", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uploaded_at": { + "name": "uploaded_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_attachments_message_id_messages_id_fk": { + "name": "message_attachments_message_id_messages_id_fk", + "tableFrom": "message_attachments", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "message_reactions": { + "name": "message_reactions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "message_id": { + "name": "message_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emoji": { + "name": "emoji", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "message_reactions_message_id_messages_id_fk": { + "name": "message_reactions_message_id_messages_id_fk", + "tableFrom": "message_reactions", + "tableTo": "messages", + "columnsFrom": [ + "message_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "message_reactions_user_id_users_id_fk": { + "name": "message_reactions_user_id_users_id_fk", + "tableFrom": "message_reactions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "messages": { + "name": "messages", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content": { + "name": "content", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "content_html": { + "name": "content_html", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "edited_at": { + "name": "edited_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_by": { + "name": "deleted_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "is_pinned": { + "name": "is_pinned", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "reply_count": { + "name": "reply_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "last_reply_at": { + "name": "last_reply_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "messages_channel_id_channels_id_fk": { + "name": "messages_channel_id_channels_id_fk", + "tableFrom": "messages", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "messages_user_id_users_id_fk": { + "name": "messages_user_id_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "messages_deleted_by_users_id_fk": { + "name": "messages_deleted_by_users_id_fk", + "tableFrom": "messages", + "tableTo": "users", + "columnsFrom": [ + "deleted_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "typing_sessions": { + "name": "typing_sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "channel_id": { + "name": "channel_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "typing_sessions_channel_id_channels_id_fk": { + "name": "typing_sessions_channel_id_channels_id_fk", + "tableFrom": "typing_sessions", + "tableTo": "channels", + "columnsFrom": [ + "channel_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "typing_sessions_user_id_users_id_fk": { + "name": "typing_sessions_user_id_users_id_fk", + "tableFrom": "typing_sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user_presence": { + "name": "user_presence", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'offline'" + }, + "status_message": { + "name": "status_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_seen_at": { + "name": "last_seen_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "user_presence_user_id_users_id_fk": { + "name": "user_presence_user_id_users_id_fk", + "tableFrom": "user_presence", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "local_sync_metadata": { + "name": "local_sync_metadata", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vector_clock": { + "name": "vector_clock", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_modified_at": { + "name": "last_modified_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sync_status": { + "name": "sync_status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending_sync'" + }, + "conflict_data": { + "name": "conflict_data", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "local_sync_metadata_table_record_idx": { + "name": "local_sync_metadata_table_record_idx", + "columns": [ + "table_name", + "record_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mutation_queue": { + "name": "mutation_queue", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "operation": { + "name": "operation", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "vector_clock": { + "name": "vector_clock", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'pending'" + }, + "retry_count": { + "name": "retry_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "process_after": { + "name": "process_after", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "mutation_queue_status_created_idx": { + "name": "mutation_queue_status_created_idx", + "columns": [ + "status", + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_checkpoint": { + "name": "sync_checkpoint", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_sync_cursor": { + "name": "last_sync_cursor", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "local_vector_clock": { + "name": "local_vector_clock", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "synced_at": { + "name": "synced_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "sync_checkpoint_table_name_unique": { + "name": "sync_checkpoint_table_name_unique", + "columns": [ + "table_name" + ], + "isUnique": true + }, + "sync_checkpoint_table_name_idx": { + "name": "sync_checkpoint_table_name_idx", + "columns": [ + "table_name" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sync_tombstone": { + "name": "sync_tombstone", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "table_name": { + "name": "table_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "record_id": { + "name": "record_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "vector_clock": { + "name": "vector_clock", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "synced": { + "name": "synced", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "sync_tombstone_table_record_idx": { + "name": "sync_tombstone_table_record_idx", + "columns": [ + "table_name", + "record_id" + ], + "isUnique": false + }, + "sync_tombstone_synced_idx": { + "name": "sync_tombstone_synced_idx", + "columns": [ + "synced" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index d7016f7..0bf77c6 100755 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -141,6 +141,41 @@ "when": 1770522037142, "tag": "0019_parched_thunderbird", "breakpoints": true + }, + { + "idx": 20, + "version": "6", + "when": 1770751435638, + "tag": "0020_military_sebastian_shaw", + "breakpoints": true + }, + { + "idx": 21, + "version": "6", + "when": 1770936294175, + "tag": "0021_early_cerise", + "breakpoints": true + }, + { + "idx": 22, + "version": "6", + "when": 1770936295000, + "tag": "0022_add_conversations_indexes", + "breakpoints": true + }, + { + "idx": 23, + "version": "6", + "when": 1771105697034, + "tag": "0023_known_moon_knight", + "breakpoints": true + }, + { + "idx": 24, + "version": "6", + "when": 1771105729640, + "tag": "0024_thankful_slayback", + "breakpoints": true } ] } \ No newline at end of file diff --git a/e2e/desktop/offline.spec.ts b/e2e/desktop/offline.spec.ts new file mode 100644 index 0000000..44e4bf5 --- /dev/null +++ b/e2e/desktop/offline.spec.ts @@ -0,0 +1,221 @@ +import { test, expect } from "@playwright/test" + +// Helper to check if running in Tauri desktop environment +function isTauri(): boolean { + return process.env.TAURI === "true" || process.env.TAURI_TEST === "true" +} + +// Desktop-only E2E tests +test.describe("Offline mode", () => { + test.skip(!isTauri(), "Desktop only") + + test.beforeEach(async ({ page }) => { + await page.goto("/") + // Wait for app to load + await page.waitForSelector('[data-testid="app-loaded"], body', { timeout: 30000 }) + }) + + test("shows offline banner when network is unavailable", async ({ page }) => { + // Go offline + await page.context().setOffline(true) + + // Try to navigate or perform an action that requires network + await page.reload({ waitUntil: "networkidle" }).catch(() => { + // Ignore reload errors when offline + }) + + // Check for offline indicator + const offlineBanner = page.locator('[data-testid="offline-banner"]') + await expect(offlineBanner).toBeVisible({ timeout: 10000 }) + + // Banner should indicate offline status + await expect(offlineBanner).toContainText(/offline|unavailable|disconnected/i) + }) + + test("queues mutations when offline", async ({ page }) => { + // Go offline first + await page.context().setOffline(true) + + // Wait for offline state to be detected + await page.waitForTimeout(1000) + + // Create a record (this should be queued locally) + // Assuming there's a form or button to create a record + const createButton = page.locator('[data-testid="create-record-btn"]') + if (await createButton.isVisible()) { + await createButton.click() + + // Fill out form if present + const nameInput = page.locator('[data-testid="record-name-input"]') + if (await nameInput.isVisible()) { + await nameInput.fill("Offline Test Record") + await page.locator('[data-testid="save-record-btn"]').click() + } + + // Verify queued indicator + const queuedIndicator = page.locator('[data-testid="sync-status-queued"]') + await expect(queuedIndicator).toBeVisible({ timeout: 5000 }) + } + }) + + test("syncs when back online", async ({ page }) => { + // Create a record while online first + const initialCount = await page.locator('[data-testid="record-item"]').count() + + // Go offline + await page.context().setOffline(true) + await page.waitForTimeout(1000) + + // Create a record + const createButton = page.locator('[data-testid="create-record-btn"]') + if (await createButton.isVisible()) { + await createButton.click() + await page.waitForTimeout(500) + } + + // Go back online + await page.context().setOffline(false) + + // Wait for sync to complete + const syncIndicator = page.locator('[data-testid="sync-status-synced"]') + await expect(syncIndicator).toBeVisible({ timeout: 30000 }) + + // Verify the record count increased + const newCount = await page.locator('[data-testid="record-item"]').count() + expect(newCount).toBeGreaterThanOrEqual(initialCount) + }) + + test("persists data locally across reloads", async ({ page }) => { + // Create some data + const testText = `Test ${Date.now()}` + + // Fill in a form or create a record + const input = page.locator('[data-testid="note-input"]') + if (await input.isVisible()) { + await input.fill(testText) + await page.locator('[data-testid="save-note-btn"]').click() + await page.waitForTimeout(1000) + } + + // Reload the page + await page.reload() + + // Verify the data persists + const persistedInput = page.locator('[data-testid="note-input"]') + if (await persistedInput.isVisible()) { + await expect(persistedInput).toHaveValue(testText) + } + }) +}) + +test.describe("Sync status indicators", () => { + test.skip(!isTauri(), "Desktop only") + + test("shows synced status when online and synced", async ({ page }) => { + await page.goto("/") + await page.waitForSelector('[data-testid="app-loaded"], body', { timeout: 30000 }) + + // Check for sync status indicator + const syncStatus = page.locator('[data-testid="sync-status"]') + + // Should show as synced or syncing + const statusText = await syncStatus.textContent() + expect(statusText).toMatch(/synced|syncing|online/i) + }) + + test("shows pending count when there are queued items", async ({ page }) => { + await page.goto("/") + + // Go offline and create records + await page.context().setOffline(true) + await page.waitForTimeout(1000) + + // Check for pending indicator + const pendingCount = page.locator('[data-testid="pending-sync-count"]') + if (await pendingCount.isVisible()) { + const count = await pendingCount.textContent() + expect(parseInt(count ?? "0", 10)).toBeGreaterThanOrEqual(0) + } + }) +}) + +test.describe("Conflict resolution UI", () => { + test.skip(!isTauri(), "Desktop only") + + test("shows conflict dialog when conflicts detected", async ({ page }) => { + // This test would require setting up a specific conflict scenario + // For now, we check if the conflict resolution UI exists + await page.goto("/") + + // Check for conflict dialog or banner + const conflictBanner = page.locator('[data-testid="conflict-banner"]') + const conflictDialog = page.locator('[data-testid="conflict-dialog"]') + + // If visible, verify it has resolution options + if (await conflictBanner.isVisible()) { + await expect(conflictBanner).toContainText(/conflict/i) + } + + if (await conflictDialog.isVisible()) { + // Should have options to resolve + const useLocalBtn = page.locator('[data-testid="use-local-btn"]') + const useRemoteBtn = page.locator('[data-testid="use-remote-btn"]') + + await expect(useLocalBtn.or(useRemoteBtn)).toBeVisible() + } + }) +}) + +test.describe("Database operations", () => { + test.skip(!isTauri(), "Desktop only") + + test("performs CRUD operations locally", async ({ page }) => { + await page.goto("/") + await page.waitForSelector('[data-testid="app-loaded"], body', { timeout: 30000 }) + + // Create + const createBtn = page.locator('[data-testid="create-item-btn"]') + if (await createBtn.isVisible()) { + await createBtn.click() + + // Fill form + const nameInput = page.locator('[data-testid="item-name"]') + if (await nameInput.isVisible()) { + await nameInput.fill("Test Item E2E") + await page.locator('[data-testid="submit-btn"]').click() + + // Verify created + await expect(page.locator("text=Test Item E2E")).toBeVisible({ timeout: 5000 }) + } + + // Update + const editBtn = page.locator('[data-testid="edit-item-btn"]').first() + if (await editBtn.isVisible()) { + await editBtn.click() + await nameInput.fill("Test Item E2E Updated") + await page.locator('[data-testid="submit-btn"]').click() + + await expect(page.locator("text=Test Item E2E Updated")).toBeVisible({ + timeout: 5000, + }) + } + + // Delete + const deleteBtn = page.locator('[data-testid="delete-item-btn"]').first() + if (await deleteBtn.isVisible()) { + await deleteBtn.click() + + // Confirm deletion if dialog appears + const confirmBtn = page.locator('[data-testid="confirm-delete-btn"]') + if (await confirmBtn.isVisible()) { + await confirmBtn.click() + } + + // Verify deleted + await expect(page.locator("text=Test Item E2E Updated")).not.toBeVisible({ + timeout: 5000, + }) + } + } + }) +}) diff --git a/next.config.ts b/next.config.ts index ebd7035..f1cae72 100755 --- a/next.config.ts +++ b/next.config.ts @@ -16,6 +16,8 @@ const nextConfig: NextConfig = { "framer-motion", ], }, + // Node.js native modules that should not be bundled for edge/browser + serverExternalPackages: ["better-sqlite3"], }; export default nextConfig; diff --git a/package.json b/package.json index 6bd7c17..2efd509 100755 --- a/package.json +++ b/package.json @@ -17,7 +17,17 @@ "prepare": "husky", "cap:sync": "cap sync", "cap:ios": "cap open ios", - "cap:android": "cap open android" + "cap:android": "cap open android", + "tauri:dev": "tauri dev", + "tauri:build": "tauri build", + "tauri:preview": "tauri dev --no-watch", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "test:integration": "vitest run __tests__/integration", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:desktop": "TAURI=true playwright test --project=desktop-chromium" }, "dependencies": { "@ai-sdk/react": "^3.0.74", @@ -74,20 +84,36 @@ "@radix-ui/react-tooltip": "^1.2.8", "@tabler/icons-react": "^3.36.1", "@tanstack/react-table": "^8.21.3", + "@tauri-apps/api": "^2.0.0", + "@tauri-apps/plugin-global-shortcut": "^2.0.0", + "@tauri-apps/plugin-http": "^2.0.0", + "@tauri-apps/plugin-sql": "^2.0.0", + "@tauri-apps/plugin-updater": "^2.0.0", + "@tauri-apps/plugin-window-state": "^2.0.0", + "@tiptap/extension-link": "^3.19.0", + "@tiptap/extension-mention": "^3.19.0", + "@tiptap/extension-placeholder": "^3.19.0", + "@tiptap/pm": "^3.19.0", + "@tiptap/react": "^3.19.0", + "@tiptap/starter-kit": "^3.19.0", "@workos-inc/authkit-nextjs": "^2.13.0", "@workos-inc/node": "^8.1.0", "@xyflow/react": "^12.10.0", "ai": "^6.0.73", + "better-sqlite3": "^11.0.0", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.1.1", "date-fns": "^4.1.0", + "dompurify": "^3.3.1", "drizzle-orm": "^0.45.1", "embla-carousel-react": "^8.6.0", "framer-motion": "11", "frappe-gantt": "^1.0.4", "input-otp": "^1.4.2", + "isomorphic-dompurify": "^3.0.0-rc.2", "lucide-react": "^0.563.0", + "marked": "^17.0.2", "motion": "^12.33.0", "nanoid": "^5.1.6", "next": "15.5.9", @@ -114,7 +140,12 @@ }, "devDependencies": { "@eslint/eslintrc": "^3", + "@playwright/test": "^1.41.0", "@tailwindcss/postcss": "^4", + "@tauri-apps/cli": "^2.0.0", + "@types/better-sqlite3": "^7.6.11", + "@types/dompurify": "^3.2.0", + "@types/jsdom": "^27.0.0", "@types/node": "^25.0.10", "@types/react": "^19", "@types/react-dom": "^19", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..3687608 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,64 @@ +import { defineConfig, devices } from "@playwright/test" + +// Detect if running in Tauri desktop environment +const isTauri = () => { + return process.env.TAURI === "true" || process.env.TAURI_TEST === "true" +} + +// Web-specific projects +const webProjects = [ + { + name: "chromium", + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + use: { ...devices["Desktop Safari"] }, + }, +] + +// Desktop (Tauri) project +const desktopProjects = [ + { + name: "desktop-chromium", + testDir: "./e2e/desktop", + use: { + ...devices["Desktop Chrome"], + baseURL: "tauri://localhost", + ignoreHTTPSErrors: true, + }, + }, +] + +export default defineConfig({ + timeout: 60000, + expect: { + timeout: 30000, + }, + fullyParallel: false, + workers: 1, + reporter: [["html"], ["list"]], + testDir: "./e2e", + use: { + actionTimeout: 30000, + navigationTimeout: 30000, + trace: "on-first-retry", + video: "retain-on-failure", + screenshot: "only-on-failure", + }, + outputDir: "test-results", + preserveOutput: "always", + projects: isTauri() ? desktopProjects : webProjects, + webServer: isTauri() + ? undefined + : { + command: "bun dev", + port: 3000, + timeout: 120000, + reuseExistingServer: !process.env.CI, + }, +}) diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore new file mode 100644 index 0000000..2801202 --- /dev/null +++ b/src-tauri/.gitignore @@ -0,0 +1,19 @@ +# Build artifacts +/target/ +/Cargo.lock + +# Generated schemas +/gen/ + +# IDE +/.idea/ +/.vscode/ +*.swp +*.swo + +# OS files +.DS_Store +Thumbs.db + +# Debug +*.log diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml new file mode 100644 index 0000000..7305ea3 --- /dev/null +++ b/src-tauri/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "compass" +version = "0.1.0" +edition = "2021" +description = "Compass Desktop - Construction Project Management" +authors = ["HPS Compass Team"] +license = "MIT" + +[lib] +name = "compass_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = ["devtools"] } +tauri-plugin-shell = "2" +tauri-plugin-sql = { version = "2", features = ["sqlite"] } +tauri-plugin-http = "2" +tauri-plugin-window-state = "2" +tauri-plugin-updater = "2" +tauri-plugin-fs = "2" +tauri-plugin-dialog = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +tokio = { version = "1", features = ["full"] } +thiserror = "2" +chrono = { version = "0.4", features = ["serde"] } +uuid = { version = "1", features = ["v4"] } + +[features] +default = ["custom-protocol"] +custom-protocol = ["tauri/custom-protocol"] + +[profile.release] +panic = "abort" +codegen-units = 1 +lto = true +opt-level = "s" +strip = true diff --git a/src-tauri/build.rs b/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json new file mode 100644 index 0000000..3d1eecb --- /dev/null +++ b/src-tauri/capabilities/default.json @@ -0,0 +1,65 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "windows": ["main"], + "permissions": [ + "core:default", + "core:app:default", + "core:window:default", + "core:webview:default", + "shell:allow-open", + "sql:default", + "sql:allow-load", + "sql:allow-execute", + "sql:allow-select", + "sql:allow-close", + { + "identifier": "http:default", + "allow": [ + { "url": "http://localhost:*" }, + { "url": "http://127.0.0.1:*" }, + { "url": "https://compass.work/**" }, + { "url": "https://*.cloudflare.com/**" }, + { "url": "https://api.openrouter.ai/**" }, + { "url": "https://api.workos.com/**" } + ] + }, + "http:allow-fetch", + "http:allow-fetch-cancel", + "http:allow-fetch-read-body", + "http:allow-fetch-send", + "window-state:default", + "window-state:allow-restore-state", + "window-state:allow-save-window-state", + "updater:default", + "updater:allow-check", + "updater:allow-download", + "updater:allow-install", + { + "identifier": "fs:default", + "allow": [ + { + "path": "$APPDATA/**" + }, + { + "path": "$APPCONFIG/**" + }, + { + "path": "$LOCALDATA/**" + } + ] + }, + "fs:allow-read-text-file", + "fs:allow-write-text-file", + "fs:allow-exists", + "fs:allow-mkdir", + "fs:allow-remove", + "fs:allow-read-dir", + "dialog:default", + "dialog:allow-open", + "dialog:allow-save", + "dialog:allow-message", + "dialog:allow-ask", + "dialog:allow-confirm" + ] +} diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png new file mode 100644 index 0000000..41abfea Binary files /dev/null and b/src-tauri/icons/128x128.png differ diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..7e404e1 Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png new file mode 100644 index 0000000..de3f84b Binary files /dev/null and b/src-tauri/icons/32x32.png differ diff --git a/src-tauri/icons/generate_icons.sh b/src-tauri/icons/generate_icons.sh new file mode 100644 index 0000000..ec6db4f --- /dev/null +++ b/src-tauri/icons/generate_icons.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Generate placeholder PNG icons from SVG using ImageMagick if available +# Falls back to creating minimal placeholder files + +if command -v convert &> /dev/null; then + convert icon.svg icon.png + convert icon.svg -resize 32x32 32x32.png + convert icon.svg -resize 128x128 128x128.png + convert icon.svg -resize 256x256 128x128@2x.png + convert icon.svg -resize 512x512 icon.icns 2>/dev/null || cp icon.png icon.icns + convert icon.svg -resize 256x256 icon.ico 2>/dev/null || cp icon.png icon.ico + echo "Icons generated with ImageMagick" +else + echo "ImageMagick not found - creating placeholder icon files" + # Create minimal placeholder files (1x1 blue pixel) + echo "iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAADklEQVRYR+3BAQ0AAADCoPdPbQ43oAAA" | base64 -d > icon.png 2>/dev/null || touch icon.png + cp icon.png 32x32.png + cp icon.png 128x128.png + cp icon.png "128x128@2x.png" + cp icon.png icon.icns + cp icon.png icon.ico + echo "Placeholder icons created - replace with proper icons before release" +fi diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns new file mode 100644 index 0000000..32c9d20 --- /dev/null +++ b/src-tauri/icons/icon.icns @@ -0,0 +1,18 @@ + + + + +Created by potrace 1.16, written by Peter Selinger 2001-2019 + + + + + diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico new file mode 100644 index 0000000..c8e6bc2 Binary files /dev/null and b/src-tauri/icons/icon.ico differ diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png new file mode 100644 index 0000000..41abfea Binary files /dev/null and b/src-tauri/icons/icon.png differ diff --git a/src-tauri/icons/icon.svg b/src-tauri/icons/icon.svg new file mode 100644 index 0000000..e86e47f --- /dev/null +++ b/src-tauri/icons/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src-tauri/migrations/initial.sql b/src-tauri/migrations/initial.sql new file mode 100644 index 0000000..5be4b2b --- /dev/null +++ b/src-tauri/migrations/initial.sql @@ -0,0 +1,272 @@ +-- Compass Desktop SQLite Schema +-- Mirrors the Drizzle schema for local offline storage + +-- Auth and user management tables +CREATE TABLE IF NOT EXISTS users ( + id TEXT PRIMARY KEY, + email TEXT NOT NULL UNIQUE, + first_name TEXT, + last_name TEXT, + display_name TEXT, + avatar_url TEXT, + role TEXT NOT NULL DEFAULT 'office', + google_email TEXT, + is_active INTEGER NOT NULL DEFAULT 1, + last_login_at TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS organizations ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + slug TEXT NOT NULL UNIQUE, + type TEXT NOT NULL, + logo_url TEXT, + is_active INTEGER NOT NULL DEFAULT 1, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS organization_members ( + id TEXT PRIMARY KEY, + organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + role TEXT NOT NULL, + joined_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS teams ( + id TEXT PRIMARY KEY, + organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, + name TEXT NOT NULL, + description TEXT, + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS team_members ( + id TEXT PRIMARY KEY, + team_id TEXT NOT NULL REFERENCES teams(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + joined_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS groups ( + id TEXT PRIMARY KEY, + organization_id TEXT NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, + name TEXT NOT NULL, + description TEXT, + color TEXT, + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS group_members ( + id TEXT PRIMARY KEY, + group_id TEXT NOT NULL REFERENCES groups(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + joined_at TEXT NOT NULL +); + +-- Project management tables +CREATE TABLE IF NOT EXISTS projects ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'OPEN', + address TEXT, + client_name TEXT, + project_manager TEXT, + organization_id TEXT REFERENCES organizations(id), + netsuite_job_id TEXT, + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS project_members ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + role TEXT NOT NULL, + assigned_at TEXT NOT NULL +); + +-- Schedule tables +CREATE TABLE IF NOT EXISTS schedule_tasks ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + title TEXT NOT NULL, + start_date TEXT NOT NULL, + workdays INTEGER NOT NULL, + end_date_calculated TEXT NOT NULL, + phase TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'PENDING', + is_critical_path INTEGER NOT NULL DEFAULT 0, + is_milestone INTEGER NOT NULL DEFAULT 0, + percent_complete INTEGER NOT NULL DEFAULT 0, + assigned_to TEXT, + sort_order INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS task_dependencies ( + id TEXT PRIMARY KEY, + predecessor_id TEXT NOT NULL REFERENCES schedule_tasks(id) ON DELETE CASCADE, + successor_id TEXT NOT NULL REFERENCES schedule_tasks(id) ON DELETE CASCADE, + type TEXT NOT NULL DEFAULT 'FS', + lag_days INTEGER NOT NULL DEFAULT 0 +); + +CREATE TABLE IF NOT EXISTS workday_exceptions ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + title TEXT NOT NULL, + start_date TEXT NOT NULL, + end_date TEXT NOT NULL, + type TEXT NOT NULL DEFAULT 'non_working', + category TEXT NOT NULL DEFAULT 'company_holiday', + recurrence TEXT NOT NULL DEFAULT 'one_time', + notes TEXT, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS schedule_baselines ( + id TEXT PRIMARY KEY, + project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + name TEXT NOT NULL, + snapshot_data TEXT NOT NULL, + created_at TEXT NOT NULL +); + +-- Customer and vendor tables +CREATE TABLE IF NOT EXISTS customers ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + company TEXT, + email TEXT, + phone TEXT, + address TEXT, + notes TEXT, + netsuite_id TEXT, + created_at TEXT NOT NULL, + updated_at TEXT +); + +CREATE TABLE IF NOT EXISTS vendors ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + category TEXT NOT NULL DEFAULT 'Subcontractor', + email TEXT, + phone TEXT, + address TEXT, + netsuite_id TEXT, + created_at TEXT NOT NULL, + updated_at TEXT +); + +-- Agent memory tables +CREATE TABLE IF NOT EXISTS agent_conversations ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + title TEXT, + last_message_at TEXT NOT NULL, + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS agent_memories ( + id TEXT PRIMARY KEY, + conversation_id TEXT NOT NULL REFERENCES agent_conversations(id) ON DELETE CASCADE, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + role TEXT NOT NULL, + content TEXT NOT NULL, + embedding TEXT, + metadata TEXT, + created_at TEXT NOT NULL +); + +-- Slab persistent memory +CREATE TABLE IF NOT EXISTS slab_memories ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + content TEXT NOT NULL, + memory_type TEXT NOT NULL, + tags TEXT, + importance REAL NOT NULL DEFAULT 0.7, + pinned INTEGER NOT NULL DEFAULT 0, + access_count INTEGER NOT NULL DEFAULT 0, + last_accessed_at TEXT, + created_at TEXT NOT NULL +); + +-- Feedback tables +CREATE TABLE IF NOT EXISTS feedback ( + id TEXT PRIMARY KEY, + type TEXT NOT NULL, + message TEXT NOT NULL, + name TEXT, + email TEXT, + page_url TEXT, + user_agent TEXT, + viewport_width INTEGER, + viewport_height INTEGER, + ip_hash TEXT, + github_issue_url TEXT, + created_at TEXT NOT NULL +); + +CREATE TABLE IF NOT EXISTS feedback_interviews ( + id TEXT PRIMARY KEY, + user_id TEXT REFERENCES users(id), + user_name TEXT NOT NULL, + user_role TEXT NOT NULL, + responses TEXT NOT NULL, + summary TEXT NOT NULL, + pain_points TEXT, + feature_requests TEXT, + overall_sentiment TEXT NOT NULL, + github_issue_url TEXT, + conversation_id TEXT, + created_at TEXT NOT NULL +); + +-- Push notification tokens +CREATE TABLE IF NOT EXISTS push_tokens ( + id TEXT PRIMARY KEY, + user_id TEXT NOT NULL REFERENCES users(id) ON DELETE CASCADE, + token TEXT NOT NULL, + platform TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); + +-- Sync metadata for offline-first support +CREATE TABLE IF NOT EXISTS sync_metadata ( + id TEXT PRIMARY KEY, + table_name TEXT NOT NULL UNIQUE, + last_sync_at TEXT, + sync_version INTEGER NOT NULL DEFAULT 0, + pending_count INTEGER NOT NULL DEFAULT 0, + last_error TEXT +); + +CREATE TABLE IF NOT EXISTS sync_queue ( + id TEXT PRIMARY KEY, + table_name TEXT NOT NULL, + record_id TEXT NOT NULL, + operation TEXT NOT NULL, -- 'create', 'update', 'delete' + data TEXT, -- JSON payload for create/update + created_at TEXT NOT NULL, + attempts INTEGER NOT NULL DEFAULT 0, + last_attempt_at TEXT, + error TEXT +); + +-- Create indexes for common queries +CREATE INDEX IF NOT EXISTS idx_schedule_tasks_project ON schedule_tasks(project_id); +CREATE INDEX IF NOT EXISTS idx_task_dependencies_predecessor ON task_dependencies(predecessor_id); +CREATE INDEX IF NOT EXISTS idx_task_dependencies_successor ON task_dependencies(successor_id); +CREATE INDEX IF NOT EXISTS idx_project_members_project ON project_members(project_id); +CREATE INDEX IF NOT EXISTS idx_project_members_user ON project_members(user_id); +CREATE INDEX IF NOT EXISTS idx_agent_memories_conversation ON agent_memories(conversation_id); +CREATE INDEX IF NOT EXISTS idx_agent_memories_user ON agent_memories(user_id); +CREATE INDEX IF NOT EXISTS idx_sync_queue_table ON sync_queue(table_name); +CREATE INDEX IF NOT EXISTS idx_sync_queue_record ON sync_queue(record_id); diff --git a/src-tauri/src/commands/database.rs b/src-tauri/src/commands/database.rs new file mode 100644 index 0000000..3131561 --- /dev/null +++ b/src-tauri/src/commands/database.rs @@ -0,0 +1,77 @@ +//! Database commands for SQLite operations +//! +//! Provides query and execute commands that interface with the local SQLite +//! database for offline-first data storage. + +use serde_json::Value; +use tauri::{Manager, State}; + +use crate::error::{AppError, Result}; + +/// Initialize the local SQLite database with schema +#[tauri::command] +pub async fn db_init( + app: tauri::AppHandle, + state: State<'_, crate::AppState>, +) -> Result { + // Get the app data directory + let app_dir = app + .path() + .app_data_dir() + .map_err(|e| AppError::Platform(e.to_string()))?; + + // Ensure the directory exists + std::fs::create_dir_all(&app_dir)?; + + let db_path = app_dir.join("compass.db"); + let db_path_str = db_path.to_string_lossy().to_string(); + + // Store the path in state + { + let mut path_guard = state + .db_path + .lock() + .map_err(|e| AppError::Lock(e.to_string()))?; + *path_guard = Some(db_path_str.clone()); + } + + // Migrations are handled automatically by tauri-plugin-sql Builder in lib.rs + // This command returns the database path for frontend reference + Ok(db_path_str) +} + +/// Execute a SELECT query and return results as JSON +/// +/// Note: This uses the frontend SQL plugin via invoke. +/// The actual database operations are handled by tauri-plugin-sql. +#[tauri::command] +pub async fn db_query( + sql: String, + params: Vec, + app: tauri::AppHandle, +) -> Result> { + // Use the SQL plugin's built-in JavaScript API from frontend instead + // This is a placeholder for direct Rust-side queries + let _ = (app, sql, params); + Err(AppError::InvalidInput( + "Use @tauri-apps/plugin-sql from frontend instead".into(), + )) +} + +/// Execute an INSERT, UPDATE, or DELETE statement +/// +/// Note: This uses the frontend SQL plugin via invoke. +/// The actual database operations are handled by tauri-plugin-sql. +#[tauri::command] +pub async fn db_execute( + sql: String, + params: Vec, + app: tauri::AppHandle, +) -> Result { + // Use the SQL plugin's built-in JavaScript API from frontend instead + // This is a placeholder for direct Rust-side queries + let _ = (app, sql, params); + Err(AppError::InvalidInput( + "Use @tauri-apps/plugin-sql from frontend instead".into(), + )) +} diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs new file mode 100644 index 0000000..1045948 --- /dev/null +++ b/src-tauri/src/commands/mod.rs @@ -0,0 +1,7 @@ +//! Tauri command modules +//! +//! Exports all command handlers for IPC between frontend and Rust backend. + +pub mod database; +pub mod platform; +pub mod sync; diff --git a/src-tauri/src/commands/platform.rs b/src-tauri/src/commands/platform.rs new file mode 100644 index 0000000..fa19414 --- /dev/null +++ b/src-tauri/src/commands/platform.rs @@ -0,0 +1,101 @@ +//! Platform detection commands +//! +//! Provides information about the current platform, including display server +//! detection for Linux (X11 vs Wayland). + +use std::env; + +use crate::error::Result; + +/// Platform information returned to the frontend +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PlatformInfo { + pub os: String, + pub arch: String, + pub family: String, + pub display_server: Option, + pub is_desktop: bool, + pub is_mobile: bool, +} + +/// Display server type for Linux systems +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum DisplayServer { + X11, + Wayland, + Unknown, +} + +/// Get comprehensive platform information +#[tauri::command] +pub fn get_platform_info() -> Result { + let os = env::consts::OS.to_string(); + let arch = env::consts::ARCH.to_string(); + let family = env::consts::FAMILY.to_string(); + + let display_server = if os == "linux" { + Some(get_display_server()) + } else { + None + }; + + Ok(PlatformInfo { + os, + arch, + family, + display_server, + is_desktop: true, + is_mobile: false, + }) +} + +/// Get the display server type (Linux only) +#[tauri::command] +pub fn get_display_server() -> DisplayServer { + // Check WAYLAND_DISPLAY first (most reliable) + if env::var("WAYLAND_DISPLAY").is_ok() { + return DisplayServer::Wayland; + } + + // Check XDG_SESSION_TYPE + if let Ok(session_type) = env::var("XDG_SESSION_TYPE") { + match session_type.to_lowercase().as_str() { + "wayland" => return DisplayServer::Wayland, + "x11" | "x" => return DisplayServer::X11, + _ => {} + } + } + + // Check DISPLAY (indicates X11) + if env::var("DISPLAY").is_ok() { + return DisplayServer::X11; + } + + // Check for Wayland-specific environment variables + if env::var("WAYLAND_SOCKET").is_ok() { + return DisplayServer::Wayland; + } + + DisplayServer::Unknown +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_platform_info_serialization() { + let info = PlatformInfo { + os: "linux".to_string(), + arch: "x86_64".to_string(), + family: "unix".to_string(), + display_server: Some(DisplayServer::Wayland), + is_desktop: true, + is_mobile: false, + }; + + let json = serde_json::to_string(&info).unwrap(); + assert!(json.contains("wayland")); + } +} diff --git a/src-tauri/src/commands/sync.rs b/src-tauri/src/commands/sync.rs new file mode 100644 index 0000000..8a13d31 --- /dev/null +++ b/src-tauri/src/commands/sync.rs @@ -0,0 +1,115 @@ +//! Sync commands for offline-first data synchronization +//! +//! Manages synchronization between local SQLite and remote server. + +use std::sync::Arc; +use tauri::{Emitter, State}; + +use crate::error::{AppError, Result}; +use crate::SyncStatus; + +/// Get the current sync status +#[tauri::command] +pub async fn get_sync_status(state: State<'_, crate::AppState>) -> Result { + let status = state + .sync_status + .lock() + .map_err(|e| AppError::Lock(e.to_string()))?; + + Ok(status.clone()) +} + +/// Trigger a sync with the remote server +#[tauri::command] +pub async fn trigger_sync( + api_base: String, + state: State<'_, crate::AppState>, + app: tauri::AppHandle, +) -> Result { + // Update status to indicate syncing + { + let mut status = state + .sync_status + .lock() + .map_err(|e| AppError::Lock(e.to_string()))?; + status.is_syncing = true; + status.error = None; + } + + // Emit sync started event + let _ = app.emit("sync-started", ()); + + // Get a clone of the current status to pass to background task + let current_status = { + let status = state + .sync_status + .lock() + .map_err(|e| AppError::Lock(e.to_string()))?; + status.clone() + }; + + // Create an Arc for the background task to update status + let status_mutex = Arc::new(std::sync::Mutex::new(current_status)); + let app_handle = app.clone(); + let api_url = api_base.clone(); + + tokio::spawn(async move { + match perform_sync(&api_url, &status_mutex).await { + Ok(_) => { + let _ = app_handle.emit("sync-complete", ()); + } + Err(e) => { + let _ = app_handle.emit("sync-error", e.to_string()); + } + } + }); + + get_sync_status(state).await +} + +/// Cancel an ongoing sync operation +#[tauri::command] +pub async fn cancel_sync(state: State<'_, crate::AppState>) -> Result { + let mut status = state + .sync_status + .lock() + .map_err(|e| AppError::Lock(e.to_string()))?; + + status.is_syncing = false; + status.error = Some("Sync cancelled by user".into()); + + Ok(status.clone()) +} + +/// Internal sync implementation +async fn perform_sync( + _api_base: &str, + status_mutex: &Arc>, +) -> Result<()> { + // TODO: Implement actual sync logic + // 1. Get pending changes from local DB + // 2. Push changes to server + // 3. Pull remote changes + // 4. Resolve conflicts + // 5. Update sync metadata + + // Simulate sync delay for now + tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; + + // Update status on completion + if let Ok(mut status) = status_mutex.lock() { + status.is_syncing = false; + status.last_sync = Some(chrono::Utc::now().to_rfc3339()); + } + + Ok(()) +} + +/// Sync metadata table structure +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct SyncMetadata { + pub table_name: String, + pub last_sync_at: Option, + pub sync_version: i64, + pub pending_count: i64, +} diff --git a/src-tauri/src/error.rs b/src-tauri/src/error.rs new file mode 100644 index 0000000..dd572c8 --- /dev/null +++ b/src-tauri/src/error.rs @@ -0,0 +1,49 @@ +//! Error types for the Compass desktop application + +use serde::Serialize; +use thiserror::Error; + +/// Main application error type +#[derive(Debug, Error)] +pub enum AppError { + #[error("Database error: {0}")] + Database(String), + + #[error("IO error: {0}")] + Io(#[from] std::io::Error), + + #[error("Serialization error: {0}")] + Serialization(#[from] serde_json::Error), + + #[error("Not found: {0}")] + NotFound(String), + + #[error("Invalid input: {0}")] + InvalidInput(String), + + #[error("Sync error: {0}")] + Sync(String), + + #[error("Network error: {0}")] + Network(String), + + #[error("Unauthorized")] + Unauthorized, + + #[error("Platform error: {0}")] + Platform(String), + + #[error("Lock error: {0}")] + Lock(String), +} + +impl Serialize for AppError { + fn serialize(&self, serializer: S) -> std::result::Result + where + S: serde::ser::Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +pub type Result = std::result::Result; diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs new file mode 100644 index 0000000..9aa6893 --- /dev/null +++ b/src-tauri/src/lib.rs @@ -0,0 +1,127 @@ +//! Compass Desktop - Tauri v2 Backend +//! +//! Provides native desktop support for Compass construction project management +//! with SQLite database, sync capabilities, and cross-platform compatibility. + +mod commands; +mod error; + +use tauri::Manager; +use tauri_plugin_sql::{Migration, MigrationKind}; + +pub use error::{AppError, Result}; + +/// Application state shared across all commands +pub struct AppState { + pub db_path: std::sync::Mutex>, + pub sync_status: std::sync::Mutex, +} + +/// Current sync status with remote server +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Default)] +pub struct SyncStatus { + pub last_sync: Option, + pub pending_changes: u64, + pub is_syncing: bool, + pub error: Option, +} + +impl Default for AppState { + fn default() -> Self { + Self { + db_path: std::sync::Mutex::new(None), + sync_status: std::sync::Mutex::new(SyncStatus::default()), + } + } +} + +/// Initialize the Tauri application +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + // Apply Linux/Wayland/NVIDIA compatibility fixes before anything else + #[cfg(target_os = "linux")] + { + let session_type = std::env::var("XDG_SESSION_TYPE").unwrap_or_default(); + let is_wayland = session_type == "wayland"; + + // Check for NVIDIA driver via /proc/driver/nvidia + let has_nvidia = std::path::Path::new("/proc/driver/nvidia").exists(); + + if is_wayland && has_nvidia { + // NVIDIA explicit sync disable - better performance on newer drivers (545+) + // Only set if user hasn't already configured it + if std::env::var("__NV_DISABLE_EXPLICIT_SYNC").is_err() { + std::env::set_var("__NV_DISABLE_EXPLICIT_SYNC", "1"); + } + // Note: We don't set WEBKIT_DISABLE_DMABUF_RENDERER here because it forces + // software rendering which is very slow. If explicit sync doesn't work, + // users can manually set: WEBKIT_DISABLE_DMABUF_RENDERER=1 bun tauri:dev + } + } + + let migrations = vec![ + Migration { + version: 1, + description: "Initial schema", + sql: include_str!("../migrations/initial.sql"), + kind: MigrationKind::Up, + }, + ]; + + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .plugin( + tauri_plugin_sql::Builder::default() + .add_migrations("sqlite:compass.db", migrations) + .build(), + ) + .plugin(tauri_plugin_http::init()) + .plugin(tauri_plugin_window_state::Builder::default().build()) + .plugin(tauri_plugin_updater::Builder::new().build()) + .plugin(tauri_plugin_fs::init()) + .plugin(tauri_plugin_dialog::init()) + .manage(AppState::default()) + .invoke_handler(tauri::generate_handler![ + // Database commands + commands::database::db_query, + commands::database::db_execute, + commands::database::db_init, + // Sync commands + commands::sync::get_sync_status, + commands::sync::trigger_sync, + commands::sync::cancel_sync, + // Platform commands + commands::platform::get_platform_info, + commands::platform::get_display_server, + ]) + .setup(|app| { + // Get the main window + let window = app.get_webview_window("main").expect("no main window"); + + // Log startup info + #[cfg(debug_assertions)] + { + println!("Compass Desktop starting up..."); + println!("Platform: {}", std::env::consts::OS); + } + + // On Wayland, disable decorations since the compositor handles window chrome + #[cfg(target_os = "linux")] + { + let session_type = std::env::var("XDG_SESSION_TYPE").unwrap_or_default(); + let is_wayland = session_type == "wayland"; + + if is_wayland { + #[cfg(debug_assertions)] + println!("Wayland detected - disabling CSD decorations"); + + // Set decorations to false for Wayland + let _ = window.set_decorations(false); + } + } + + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs new file mode 100644 index 0000000..1868952 --- /dev/null +++ b/src-tauri/src/main.rs @@ -0,0 +1,14 @@ +//! Compass Desktop Entry Point +//! +//! This is the main entry point for the Tauri desktop application. +//! The actual application logic lives in the lib.rs for mobile compatibility. + +// Prevents additional console window on Windows in release +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +fn main() { + compass_lib::run() +} diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json new file mode 100644 index 0000000..32c0f4b --- /dev/null +++ b/src-tauri/tauri.conf.json @@ -0,0 +1,80 @@ +{ + "$schema": "./gen/schemas/desktop-schema.json", + "productName": "Compass", + "version": "0.1.0", + "identifier": "work.nicholai.compass", + "build": { + "devUrl": "http://localhost:3000", + "frontendDist": "../out", + "beforeDevCommand": "bun dev", + "beforeBuildCommand": "bun run build" + }, + "app": { + "windows": [ + { + "label": "main", + "title": "Compass", + "width": 1280, + "height": 800, + "minWidth": 800, + "minHeight": 600, + "center": true, + "resizable": true, + "fullscreen": false, + "decorations": true, + "transparent": false + } + ], + "security": { + "csp": "default-src 'self'; img-src 'self' data: https: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; connect-src 'self' http://localhost:* http://127.0.0.1:* https: wss:; font-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'", + "capabilities": ["default"] + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "publisher": "HPS Compass", + "category": "Business", + "shortDescription": "Construction Project Management", + "longDescription": "Compass - A comprehensive construction project management application with AI-powered assistance", + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + }, + "macOS": { + "minimumSystemVersion": "10.13", + "entitlements": null, + "exceptionDomain": "", + "frameworks": [], + "providerShortName": null, + "signingIdentity": null + }, + "linux": { + "deb": { + "depends": [] + }, + "appimage": { + "bundleMediaFramework": false + } + }, + "externalBin": [], + "copyright": "", + "license": "MIT", + "category": "Business" + }, + "plugins": { + "updater": { + "active": true, + "endpoints": [], + "pubkey": "" + } + } +} diff --git a/src/app/actions/channel-categories.ts b/src/app/actions/channel-categories.ts new file mode 100644 index 0000000..31d839b --- /dev/null +++ b/src/app/actions/channel-categories.ts @@ -0,0 +1,310 @@ +"use server" + +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { eq, and, sql } from "drizzle-orm" +import { getDb } from "@/db" +import { channelCategories, channels, type NewChannelCategory } from "@/db/schema-conversations" +import { getCurrentUser } from "@/lib/auth" +import { requirePermission } from "@/lib/permissions" +import { revalidatePath } from "next/cache" + +export async function listCategories() { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // get user's organization + const orgMember = await db + .select({ organizationId: sql`organization_id` }) + .from(sql`organization_members`) + .where(sql`user_id = ${user.id}`) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!orgMember) { + return { success: false, error: "No organization found" } + } + + // fetch categories with channel counts + const categories = await db + .select({ + id: channelCategories.id, + name: channelCategories.name, + position: channelCategories.position, + channelCount: sql`( + SELECT COUNT(*) FROM ${channels} + WHERE ${channels.categoryId} = ${channelCategories.id} + AND ${channels.archivedAt} IS NULL + )`, + }) + .from(channelCategories) + .where(eq(channelCategories.organizationId, orgMember.organizationId)) + .orderBy(channelCategories.position) + + return { success: true, data: categories } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to list categories", + } + } +} + +export async function createCategory(name: string, position?: number) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + // admin only + requirePermission(user, "channels", "create") + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // get user's organization + const orgMember = await db + .select({ organizationId: sql`organization_id` }) + .from(sql`organization_members`) + .where(sql`user_id = ${user.id}`) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!orgMember) { + return { success: false, error: "No organization found" } + } + + const categoryId = crypto.randomUUID() + const now = new Date().toISOString() + + const newCategory: NewChannelCategory = { + id: categoryId, + name, + organizationId: orgMember.organizationId, + position: position ?? 0, + collapsedByDefault: false, + createdAt: now, + } + + await db.insert(channelCategories).values(newCategory) + + revalidatePath("/dashboard") + return { success: true, data: { categoryId } } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to create category", + } + } +} + +export async function updateCategory( + id: string, + data: { name?: string; position?: number } +) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + // admin only + requirePermission(user, "channels", "create") + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // get user's organization + const orgMember = await db + .select({ organizationId: sql`organization_id` }) + .from(sql`organization_members`) + .where(sql`user_id = ${user.id}`) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!orgMember) { + return { success: false, error: "No organization found" } + } + + // verify category exists in user's org + const category = await db + .select() + .from(channelCategories) + .where(eq(channelCategories.id, id)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!category || category.organizationId !== orgMember.organizationId) { + return { success: false, error: "Category not found" } + } + + // build update object with only provided fields + const updates: Partial = {} + if (data.name !== undefined) { + updates.name = data.name + } + if (data.position !== undefined) { + updates.position = data.position + } + + if (Object.keys(updates).length === 0) { + return { success: true } + } + + await db + .update(channelCategories) + .set(updates) + .where(eq(channelCategories.id, id)) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to update category", + } + } +} + +export async function deleteCategory(id: string) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + // admin only + requirePermission(user, "channels", "create") + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // get user's organization + const orgMember = await db + .select({ organizationId: sql`organization_id` }) + .from(sql`organization_members`) + .where(sql`user_id = ${user.id}`) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!orgMember) { + return { success: false, error: "No organization found" } + } + + // verify category exists in user's org + const category = await db + .select() + .from(channelCategories) + .where(eq(channelCategories.id, id)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!category || category.organizationId !== orgMember.organizationId) { + return { success: false, error: "Category not found" } + } + + // check for channels in this category + const channelCount = await db + .select({ count: sql`count(*)` }) + .from(channels) + .where( + and( + eq(channels.categoryId, id), + sql`${channels.archivedAt} IS NULL` + ) + ) + .then((rows) => rows[0]?.count ?? 0) + + if (channelCount > 0) { + return { success: false, error: "Category has channels" } + } + + await db.delete(channelCategories).where(eq(channelCategories.id, id)) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to delete category", + } + } +} + +export async function reorderChannels( + categoryId: string, + channelOrder: string[] +) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + requirePermission(user, "channels", "update") + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // get user's organization + const orgMember = await db + .select({ organizationId: sql`organization_id` }) + .from(sql`organization_members`) + .where(sql`user_id = ${user.id}`) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!orgMember) { + return { success: false, error: "No organization found" } + } + + // verify category exists and belongs to user's org + const category = await db + .select() + .from(channelCategories) + .where(eq(channelCategories.id, categoryId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!category || category.organizationId !== orgMember.organizationId) { + return { success: false, error: "Category not found" } + } + + // verify all channels belong to this category + if (channelOrder.length > 0) { + const categoryChannels = await db + .select({ id: channels.id }) + .from(channels) + .where(eq(channels.categoryId, categoryId)) + + const validChannelIds = new Set(categoryChannels.map((c) => c.id)) + for (const channelId of channelOrder) { + if (!validChannelIds.has(channelId)) { + return { success: false, error: "Invalid channel in order" } + } + } + } + + // update sortOrder for each channel in the array + for (let i = 0; i < channelOrder.length; i++) { + await db + .update(channels) + .set({ sortOrder: i }) + .where(eq(channels.id, channelOrder[i])) + } + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to reorder channels", + } + } +} diff --git a/src/app/actions/chat-messages.ts b/src/app/actions/chat-messages.ts new file mode 100644 index 0000000..8e5068b --- /dev/null +++ b/src/app/actions/chat-messages.ts @@ -0,0 +1,679 @@ +"use server" + +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { eq, and, desc, lt, sql } from "drizzle-orm" +import { marked } from "marked" +import DOMPurify from "isomorphic-dompurify" +import { getDb } from "@/db" +import { + messages, + messageReactions, + channelMembers, + channelReadState, + type NewMessage, + type NewMessageReaction, +} from "@/db/schema-conversations" +import { users } from "@/db/schema" +import { getCurrentUser } from "@/lib/auth" +import { requirePermission } from "@/lib/permissions" +import { revalidatePath } from "next/cache" + +const MAX_MESSAGE_LENGTH = 4000 +const EMOJI_REGEX = /^[\p{Emoji}\u200d]+$/u + +// Configure marked for safe rendering +marked.setOptions({ + breaks: true, + gfm: true, +}) + +async function renderMarkdown(content: string): Promise { + const html = await marked(content) + return DOMPurify.sanitize(html, { + ALLOWED_TAGS: [ + "p", + "br", + "strong", + "em", + "u", + "s", + "del", + "code", + "pre", + "blockquote", + "ul", + "ol", + "li", + "a", + "img", + "h1", + "h2", + "h3", + "h4", + "h5", + "h6", + "hr", + "table", + "thead", + "tbody", + "tr", + "th", + "td", + "span", + "div", + ], + ALLOWED_ATTR: ["href", "src", "alt", "title", "class", "id", "target", "rel"], + ALLOWED_URI_REGEXP: + /^(?:(?:(?:f|ht)tps?|mailto):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i, + }) +} + +export async function sendMessage(data: { + channelId: string + content: string + threadId?: string +}) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + if (data.content.length > MAX_MESSAGE_LENGTH) { + return { success: false, error: `Message too long (max ${MAX_MESSAGE_LENGTH} characters)` } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // verify user is a member of the channel + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, data.channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + const now = new Date().toISOString() + const messageId = crypto.randomUUID() + + // Pre-render markdown to sanitized HTML + const contentHtml = await renderMarkdown(data.content) + + const newMessage: NewMessage = { + id: messageId, + channelId: data.channelId, + threadId: data.threadId ?? null, + userId: user.id, + content: data.content, + contentHtml, + editedAt: null, + deletedAt: null, + deletedBy: null, + isPinned: false, + replyCount: 0, + lastReplyAt: null, + createdAt: now, + } + + await db.insert(messages).values(newMessage) + + // if this is a thread reply, update parent message + if (data.threadId) { + await db + .update(messages) + .set({ + replyCount: sql`${messages.replyCount} + 1`, + lastReplyAt: now, + }) + .where(eq(messages.id, data.threadId)) + } + + // update read state for sender (mark as read) + await db + .update(channelReadState) + .set({ + lastReadMessageId: messageId, + lastReadAt: now, + unreadCount: 0, + }) + .where( + and( + eq(channelReadState.channelId, data.channelId), + eq(channelReadState.userId, user.id) + ) + ) + + // fetch the created message with user info + const messageWithUser = await db + .select({ + id: messages.id, + channelId: messages.channelId, + threadId: messages.threadId, + content: messages.content, + contentHtml: messages.contentHtml, + editedAt: messages.editedAt, + isPinned: messages.isPinned, + replyCount: messages.replyCount, + lastReplyAt: messages.lastReplyAt, + createdAt: messages.createdAt, + user: { + id: users.id, + displayName: users.displayName, + email: users.email, + avatarUrl: users.avatarUrl, + }, + }) + .from(messages) + .leftJoin(users, eq(users.id, messages.userId)) + .where(eq(messages.id, messageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + revalidatePath("/dashboard") + return { success: true, data: messageWithUser } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to send message", + } + } +} + +export async function editMessage(messageId: string, newContent: string) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // fetch the message + const message = await db + .select() + .from(messages) + .where(eq(messages.id, messageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!message) { + return { success: false, error: "Message not found" } + } + + // check permission: own message or admin + if (message.userId !== user.id) { + try { + requirePermission(user, "channels", "moderate") + } catch { + return { success: false, error: "Cannot edit other users' messages" } + } + } + + const now = new Date().toISOString() + + // Re-render markdown to sanitized HTML + const contentHtml = await renderMarkdown(newContent) + + await db + .update(messages) + .set({ + content: newContent, + contentHtml, + editedAt: now, + }) + .where(eq(messages.id, messageId)) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to edit message", + } + } +} + +export async function deleteMessage(messageId: string) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // fetch the message + const message = await db + .select() + .from(messages) + .where(eq(messages.id, messageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!message) { + return { success: false, error: "Message not found" } + } + + // check permission: own message or admin + if (message.userId !== user.id) { + try { + requirePermission(user, "channels", "moderate") + } catch { + return { success: false, error: "Cannot delete other users' messages" } + } + } + + const now = new Date().toISOString() + await db + .update(messages) + .set({ + deletedAt: now, + deletedBy: user.id, + }) + .where(eq(messages.id, messageId)) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to delete message", + } + } +} + +export async function getMessages( + channelId: string, + options?: { limit?: number; cursor?: string } +) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // verify user is a member + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + const limit = options?.limit ?? 50 + const cursor = options?.cursor + + const query = db + .select({ + id: messages.id, + channelId: messages.channelId, + threadId: messages.threadId, + content: messages.content, + contentHtml: messages.contentHtml, + editedAt: messages.editedAt, + deletedAt: messages.deletedAt, + isPinned: messages.isPinned, + replyCount: messages.replyCount, + lastReplyAt: messages.lastReplyAt, + createdAt: messages.createdAt, + user: { + id: users.id, + displayName: users.displayName, + email: users.email, + avatarUrl: users.avatarUrl, + }, + }) + .from(messages) + .leftJoin(users, eq(users.id, messages.userId)) + .where( + and( + eq(messages.channelId, channelId), + sql`${messages.threadId} IS NULL`, // only top-level messages + cursor ? lt(messages.createdAt, cursor) : undefined + ) + ) + .orderBy(desc(messages.createdAt)) + .limit(limit) + + const results = await query + + // replace deleted content with placeholder + const sanitized = results.map((msg) => ({ + ...msg, + content: msg.deletedAt ? "[Message deleted]" : msg.content, + contentHtml: msg.deletedAt ? null : msg.contentHtml, + })) + + return { success: true, data: sanitized } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to get messages", + } + } +} + +export async function getThreadMessages( + parentMessageId: string, + options?: { limit?: number; cursor?: string } +) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // fetch parent message to get channelId + const parentMessage = await db + .select({ channelId: messages.channelId }) + .from(messages) + .where(eq(messages.id, parentMessageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!parentMessage) { + return { success: false, error: "Parent message not found" } + } + + // verify user is a member + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, parentMessage.channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + const limit = options?.limit ?? 50 + const cursor = options?.cursor + + const query = db + .select({ + id: messages.id, + channelId: messages.channelId, + threadId: messages.threadId, + content: messages.content, + contentHtml: messages.contentHtml, + editedAt: messages.editedAt, + deletedAt: messages.deletedAt, + isPinned: messages.isPinned, + replyCount: messages.replyCount, + lastReplyAt: messages.lastReplyAt, + createdAt: messages.createdAt, + user: { + id: users.id, + displayName: users.displayName, + email: users.email, + avatarUrl: users.avatarUrl, + }, + }) + .from(messages) + .leftJoin(users, eq(users.id, messages.userId)) + .where( + and( + eq(messages.threadId, parentMessageId), + cursor ? lt(messages.createdAt, cursor) : undefined + ) + ) + .orderBy(desc(messages.createdAt)) + .limit(limit) + + const results = await query + + // replace deleted content with placeholder + const sanitized = results.map((msg) => ({ + ...msg, + content: msg.deletedAt ? "[Message deleted]" : msg.content, + contentHtml: msg.deletedAt ? null : msg.contentHtml, + })) + + return { success: true, data: sanitized } + } catch (err) { + return { + success: false, + error: + err instanceof Error ? err.message : "Failed to get thread messages", + } + } +} + +export async function addReaction(messageId: string, emoji: string) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + if (emoji.length > 10 || !EMOJI_REGEX.test(emoji)) { + return { success: false, error: "Invalid emoji" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // fetch message to get channelId + const message = await db + .select({ channelId: messages.channelId }) + .from(messages) + .where(eq(messages.id, messageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!message) { + return { success: false, error: "Message not found" } + } + + // verify user is a member of the channel + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, message.channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + // check if reaction already exists + const existing = await db + .select() + .from(messageReactions) + .where( + and( + eq(messageReactions.messageId, messageId), + eq(messageReactions.userId, user.id), + eq(messageReactions.emoji, emoji) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (existing) { + return { success: false, error: "Already reacted with this emoji" } + } + + const now = new Date().toISOString() + const reactionId = crypto.randomUUID() + + const newReaction: NewMessageReaction = { + id: reactionId, + messageId, + userId: user.id, + emoji, + createdAt: now, + } + + await db.insert(messageReactions).values(newReaction) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to add reaction", + } + } +} + +export async function removeReaction(messageId: string, emoji: string) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + if (emoji.length > 10 || !EMOJI_REGEX.test(emoji)) { + return { success: false, error: "Invalid emoji" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // fetch message to get channelId + const message = await db + .select({ channelId: messages.channelId }) + .from(messages) + .where(eq(messages.id, messageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!message) { + return { success: false, error: "Message not found" } + } + + // verify user is a member of the channel + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, message.channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + await db + .delete(messageReactions) + .where( + and( + eq(messageReactions.messageId, messageId), + eq(messageReactions.userId, user.id), + eq(messageReactions.emoji, emoji) + ) + ) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to remove reaction", + } + } +} + +export async function markChannelRead( + channelId: string, + lastMessageId: string +) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + const now = new Date().toISOString() + + // upsert read state + const existing = await db + .select() + .from(channelReadState) + .where( + and( + eq(channelReadState.channelId, channelId), + eq(channelReadState.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (existing) { + await db + .update(channelReadState) + .set({ + lastReadMessageId: lastMessageId, + lastReadAt: now, + unreadCount: 0, + }) + .where(eq(channelReadState.id, existing.id)) + } else { + const readStateId = crypto.randomUUID() + await db.insert(channelReadState).values({ + id: readStateId, + userId: user.id, + channelId, + lastReadMessageId: lastMessageId, + lastReadAt: now, + unreadCount: 0, + }) + } + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to mark channel read", + } + } +} diff --git a/src/app/actions/conversations-realtime.ts b/src/app/actions/conversations-realtime.ts new file mode 100644 index 0000000..9a24d01 --- /dev/null +++ b/src/app/actions/conversations-realtime.ts @@ -0,0 +1,242 @@ +"use server" + +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { eq, and, gt, desc, sql } from "drizzle-orm" +import { getDb } from "@/db" +import { messages, typingSessions, channelMembers } from "@/db/schema-conversations" +import { users } from "@/db/schema" +import { getCurrentUser } from "@/lib/auth" + +type TypingUser = { + id: string + displayName: string | null +} + +type MessageWithUser = { + id: string + channelId: string + threadId: string | null + content: string + contentHtml: string | null + editedAt: string | null + deletedAt: string | null + isPinned: boolean + replyCount: number + lastReplyAt: string | null + createdAt: string + user: { + id: string + displayName: string | null + email: string + avatarUrl: string | null + } | null +} + +type ChannelUpdatesResult = { + messages: MessageWithUser[] + typingUsers: TypingUser[] +} + +const messageSelectFields = { + id: messages.id, + channelId: messages.channelId, + threadId: messages.threadId, + content: messages.content, + contentHtml: messages.contentHtml, + editedAt: messages.editedAt, + deletedAt: messages.deletedAt, + isPinned: messages.isPinned, + replyCount: messages.replyCount, + lastReplyAt: messages.lastReplyAt, + createdAt: messages.createdAt, + user: { + id: users.id, + displayName: users.displayName, + email: users.email, + avatarUrl: users.avatarUrl, + }, +} as const + +export async function getChannelUpdates( + channelId: string, + lastMessageId?: string +): Promise<{ success: true; data: ChannelUpdatesResult } | { success: false; error: string }> { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // verify user is a member of the channel + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + const now = new Date().toISOString() + + // clean up expired typing sessions + await db + .delete(typingSessions) + .where(sql`${typingSessions.expiresAt} < ${now}`) + + // fetch new messages since lastMessageId (or last 20 if no cursor) + let messagesQuery = db + .select(messageSelectFields) + .from(messages) + .leftJoin(users, eq(users.id, messages.userId)) + .where(eq(messages.channelId, channelId)) + .orderBy(desc(messages.createdAt)) + + if (lastMessageId) { + // find the createdAt of the lastMessageId to use as cursor + const lastMessage = await db + .select({ createdAt: messages.createdAt }) + .from(messages) + .where(eq(messages.id, lastMessageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (lastMessage) { + messagesQuery = db + .select(messageSelectFields) + .from(messages) + .leftJoin(users, eq(users.id, messages.userId)) + .where( + and( + eq(messages.channelId, channelId), + gt(messages.createdAt, lastMessage.createdAt) + ) + ) + .orderBy(desc(messages.createdAt)) + } + } + + const fetchedMessages = await messagesQuery.limit(lastMessageId ? 100 : 20) + + // replace deleted content with placeholder + const sanitizedMessages: MessageWithUser[] = fetchedMessages.map((msg) => ({ + ...msg, + content: msg.deletedAt ? "[Message deleted]" : msg.content, + contentHtml: msg.deletedAt ? null : msg.contentHtml, + })) + + // fetch currently typing users (excluding current user) + const typingUsers = await db + .select({ + id: users.id, + displayName: users.displayName, + }) + .from(typingSessions) + .leftJoin(users, eq(users.id, typingSessions.userId)) + .where( + and( + eq(typingSessions.channelId, channelId), + gt(typingSessions.expiresAt, now), + sql`${typingSessions.userId} != ${user.id}` + ) + ) + .then((rows) => rows as TypingUser[]) + + return { + success: true, + data: { + messages: sanitizedMessages, + typingUsers, + }, + } + } catch (err) { + return { + success: false, + error: + err instanceof Error ? err.message : "Failed to get channel updates", + } + } +} + +export async function setTyping( + channelId: string +): Promise<{ success: true } | { success: false; error: string }> { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // verify user is a member of the channel + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + const now = new Date() + const expiresAt = new Date(now.getTime() + 5000).toISOString() // 5-second expiry + const startedAt = now.toISOString() + + // check if typing session already exists for this user/channel + const existingSession = await db + .select() + .from(typingSessions) + .where( + and( + eq(typingSessions.channelId, channelId), + eq(typingSessions.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (existingSession) { + // update existing session expiry + await db + .update(typingSessions) + .set({ expiresAt }) + .where(eq(typingSessions.id, existingSession.id)) + } else { + // create new typing session + const sessionId = crypto.randomUUID() + await db.insert(typingSessions).values({ + id: sessionId, + channelId, + userId: user.id, + startedAt, + expiresAt, + }) + } + + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to set typing status", + } + } +} diff --git a/src/app/actions/conversations.ts b/src/app/actions/conversations.ts new file mode 100644 index 0000000..da90405 --- /dev/null +++ b/src/app/actions/conversations.ts @@ -0,0 +1,393 @@ +"use server" + +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { eq, and, sql } from "drizzle-orm" +import { getDb } from "@/db" +import { + channels, + channelMembers, + channelReadState, + type NewChannel, + type NewChannelMember, + type NewChannelReadState, +} from "@/db/schema-conversations" +import { users } from "@/db/schema" +import { getCurrentUser } from "@/lib/auth" +import { requirePermission } from "@/lib/permissions" +import { revalidatePath } from "next/cache" + +export async function listChannels() { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // get all channels the user can access: + // - public channels in their org + // - private channels they're a member of + const allChannels = await db + .select({ + id: channels.id, + name: channels.name, + type: channels.type, + description: channels.description, + organizationId: channels.organizationId, + projectId: channels.projectId, + categoryId: channels.categoryId, + isPrivate: channels.isPrivate, + sortOrder: channels.sortOrder, + archivedAt: channels.archivedAt, + createdAt: channels.createdAt, + updatedAt: channels.updatedAt, + memberRole: channelMembers.role, + unreadCount: channelReadState.unreadCount, + }) + .from(channels) + .leftJoin( + channelMembers, + and( + eq(channelMembers.channelId, channels.id), + eq(channelMembers.userId, user.id) + ) + ) + .leftJoin( + channelReadState, + and( + eq(channelReadState.channelId, channels.id), + eq(channelReadState.userId, user.id) + ) + ) + .where( + and( + // must be in user's org + sql`${channels.organizationId} = ( + SELECT organization_id FROM organization_members + WHERE user_id = ${user.id} LIMIT 1 + )`, + // if private, must be a member + sql`(${channels.isPrivate} = 0 OR ${channelMembers.userId} IS NOT NULL)`, + // not archived + sql`${channels.archivedAt} IS NULL` + ) + ) + .orderBy(channels.sortOrder, channels.createdAt) + + return { success: true, data: allChannels } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to list channels", + } + } +} + +export async function getChannel(channelId: string) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // verify user has access + const channel = await db + .select() + .from(channels) + .where(eq(channels.id, channelId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!channel) { + return { success: false, error: "Channel not found" } + } + + // if private, check membership + if (channel.isPrivate) { + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Access denied" } + } + } + + // count members + const memberCount = await db + .select({ count: sql`count(*)` }) + .from(channelMembers) + .where(eq(channelMembers.channelId, channelId)) + .then((rows) => rows[0]?.count ?? 0) + + return { + success: true, + data: { + ...channel, + memberCount, + }, + } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to get channel", + } + } +} + +export async function createChannel(data: { + name: string + type: "text" | "voice" | "announcement" + description?: string + projectId?: string + categoryId?: string | null + isPrivate?: boolean +}) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + // only office+ can create channels + requirePermission(user, "channels", "create") + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // get user's organization + const orgMember = await db + .select({ organizationId: sql`organization_id` }) + .from(sql`organization_members`) + .where(sql`user_id = ${user.id}`) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!orgMember) { + return { success: false, error: "No organization found" } + } + + const now = new Date().toISOString() + const channelId = crypto.randomUUID() + + const newChannel: NewChannel = { + id: channelId, + name: data.name, + type: data.type, + description: data.description ?? null, + organizationId: orgMember.organizationId, + projectId: data.projectId ?? null, + categoryId: data.categoryId ?? null, + isPrivate: data.isPrivate ?? false, + createdBy: user.id, + sortOrder: 0, + archivedAt: null, + createdAt: now, + updatedAt: now, + } + + await db.insert(channels).values(newChannel) + + // add creator as owner + const memberId = crypto.randomUUID() + const newMember: NewChannelMember = { + id: memberId, + channelId, + userId: user.id, + role: "owner", + notifyLevel: "all", + joinedAt: now, + } + await db.insert(channelMembers).values(newMember) + + // initialize read state for creator + const readStateId = crypto.randomUUID() + const newReadState: NewChannelReadState = { + id: readStateId, + userId: user.id, + channelId, + lastReadMessageId: null, + lastReadAt: now, + unreadCount: 0, + } + await db.insert(channelReadState).values(newReadState) + + revalidatePath("/dashboard") + return { success: true, data: { channelId } } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to create channel", + } + } +} + +export async function joinChannel(channelId: string) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // verify channel exists and is not private + const channel = await db + .select() + .from(channels) + .where(eq(channels.id, channelId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!channel) { + return { success: false, error: "Channel not found" } + } + + if (channel.isPrivate) { + return { + success: false, + error: "Cannot join private channel without invitation", + } + } + + // check if already a member + const existing = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (existing) { + return { success: false, error: "Already a member" } + } + + const now = new Date().toISOString() + const memberId = crypto.randomUUID() + const newMember: NewChannelMember = { + id: memberId, + channelId, + userId: user.id, + role: "member", + notifyLevel: "all", + joinedAt: now, + } + await db.insert(channelMembers).values(newMember) + + // initialize read state + const readStateId = crypto.randomUUID() + const newReadState: NewChannelReadState = { + id: readStateId, + userId: user.id, + channelId, + lastReadMessageId: null, + lastReadAt: now, + unreadCount: 0, + } + await db.insert(channelReadState).values(newReadState) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to join channel", + } + } +} + +export async function leaveChannel(channelId: string) { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // check current membership + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + // if owner, check if there are other owners + if (membership.role === "owner") { + const ownerCount = await db + .select({ count: sql`count(*)` }) + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.role, "owner") + ) + ) + .then((rows) => rows[0]?.count ?? 0) + + if (ownerCount <= 1) { + return { + success: false, + error: "Cannot leave - you are the last owner", + } + } + } + + // remove membership and read state + await db + .delete(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + + await db + .delete(channelReadState) + .where( + and( + eq(channelReadState.channelId, channelId), + eq(channelReadState.userId, user.id) + ) + ) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to leave channel", + } + } +} diff --git a/src/app/actions/message-search.ts b/src/app/actions/message-search.ts new file mode 100644 index 0000000..8dd16a8 --- /dev/null +++ b/src/app/actions/message-search.ts @@ -0,0 +1,369 @@ +"use server" + +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { eq, and, or, like, sql, gte, lte, desc, inArray } from "drizzle-orm" +import type { SQL } from "drizzle-orm" +import { getDb } from "@/db" +import { messages, channels, channelMembers } from "@/db/schema-conversations" +import { users } from "@/db/schema" +import { getCurrentUser } from "@/lib/auth" +import { requirePermission } from "@/lib/permissions" +import { revalidatePath } from "next/cache" + +const MAX_QUERY_LENGTH = 100 + +function escapeLikeWildcards(str: string): string { + return str.replace(/[%_]/g, (char) => `\\${char}`) +} + +type SearchFilters = { + channelId?: string + userId?: string + startDate?: string + endDate?: string +} + +type SearchResultMessage = { + id: string + content: string + channelId: string + channelName: string + createdAt: string + user: { + id: string + displayName: string | null + avatarUrl: string | null + } +} + +export async function searchMessages( + query: string, + filters?: SearchFilters +): Promise< + | { success: true; data: SearchResultMessage[] } + | { success: false; error: string } +> { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + if (!query || query.trim().length === 0) { + return { success: false, error: "Search query is required" } + } + + if (query.length > MAX_QUERY_LENGTH) { + return { success: false, error: `Search query too long (max ${MAX_QUERY_LENGTH} characters)` } + } + + const escapedQuery = escapeLikeWildcards(query) + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // get all channels user has access to + const accessibleChannels = await db + .select({ channelId: channelMembers.channelId }) + .from(channelMembers) + .where(eq(channelMembers.userId, user.id)) + + if (accessibleChannels.length === 0) { + return { success: true, data: [] } + } + + const accessibleChannelIds = accessibleChannels.map((c) => c.channelId) + + // build filter conditions + const conditions: (SQL | undefined)[] = [ + inArray(messages.channelId, accessibleChannelIds), + like(messages.content, `%${escapedQuery}%`), + sql`${messages.deletedAt} IS NULL`, // exclude deleted messages + ] + + if (filters?.channelId) { + // verify user has access to this specific channel + if (!accessibleChannelIds.includes(filters.channelId)) { + return { success: false, error: "No access to this channel" } + } + conditions.push(eq(messages.channelId, filters.channelId)) + } + + if (filters?.userId) { + conditions.push(eq(messages.userId, filters.userId)) + } + + if (filters?.startDate) { + conditions.push(gte(messages.createdAt, filters.startDate)) + } + + if (filters?.endDate) { + conditions.push(lte(messages.createdAt, filters.endDate)) + } + + const results = await db + .select({ + id: messages.id, + content: messages.content, + channelId: messages.channelId, + createdAt: messages.createdAt, + channelName: channels.name, + userId: messages.userId, + userDisplayName: users.displayName, + userAvatarUrl: users.avatarUrl, + }) + .from(messages) + .leftJoin(channels, eq(channels.id, messages.channelId)) + .leftJoin(users, eq(users.id, messages.userId)) + .where(and(...conditions)) + .orderBy(desc(messages.createdAt)) + .limit(100) + + const searchResults: SearchResultMessage[] = results.map((row) => ({ + id: row.id, + content: row.content, + channelId: row.channelId, + channelName: row.channelName ?? "Unknown Channel", + createdAt: row.createdAt, + user: { + id: row.userId, + displayName: row.userDisplayName, + avatarUrl: row.userAvatarUrl, + }, + })) + + return { success: true, data: searchResults } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to search messages", + } + } +} + +export async function getPinnedMessages( + channelId: string +): Promise<{ success: true; data: unknown[] } | { success: false; error: string }> { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // verify user is a channel member + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + const pinnedMessages = await db + .select({ + id: messages.id, + channelId: messages.channelId, + threadId: messages.threadId, + content: messages.content, + contentHtml: messages.contentHtml, + editedAt: messages.editedAt, + isPinned: messages.isPinned, + replyCount: messages.replyCount, + lastReplyAt: messages.lastReplyAt, + createdAt: messages.createdAt, + user: { + id: users.id, + displayName: users.displayName, + email: users.email, + avatarUrl: users.avatarUrl, + }, + }) + .from(messages) + .leftJoin(users, eq(users.id, messages.userId)) + .where( + and( + eq(messages.channelId, channelId), + eq(messages.isPinned, true), + sql`${messages.deletedAt} IS NULL` + ) + ) + .orderBy(desc(messages.createdAt)) + + return { success: true, data: pinnedMessages } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to get pinned messages", + } + } +} + +export async function pinMessage( + messageId: string +): Promise<{ success: true } | { success: false; error: string }> { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // fetch the message + const message = await db + .select() + .from(messages) + .where(eq(messages.id, messageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!message) { + return { success: false, error: "Message not found" } + } + + // verify user is a channel member + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, message.channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + // check permission: message author or moderator+ + const isAuthor = message.userId === user.id + const canModerate = (() => { + try { + requirePermission(user, "channels", "moderate") + return true + } catch { + return false + } + })() + + // also allow if user has moderator role in the channel + const isChannelModerator = + membership.role === "moderator" || membership.role === "owner" + + if (!isAuthor && !canModerate && !isChannelModerator) { + return { + success: false, + error: "Must be message author or have moderator permission to pin", + } + } + + if (message.isPinned) { + return { success: false, error: "Message is already pinned" } + } + + await db.update(messages).set({ isPinned: true }).where(eq(messages.id, messageId)) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to pin message", + } + } +} + +export async function unpinMessage( + messageId: string +): Promise<{ success: true } | { success: false; error: string }> { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // fetch the message + const message = await db + .select() + .from(messages) + .where(eq(messages.id, messageId)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!message) { + return { success: false, error: "Message not found" } + } + + // verify user is a channel member + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, message.channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { success: false, error: "Not a member of this channel" } + } + + // check permission: moderator+ required for unpin + const canModerate = (() => { + try { + requirePermission(user, "channels", "moderate") + return true + } catch { + return false + } + })() + + const isChannelModerator = + membership.role === "moderator" || membership.role === "owner" + + if (!canModerate && !isChannelModerator) { + return { + success: false, + error: "Must have moderator permission to unpin messages", + } + } + + if (!message.isPinned) { + return { success: false, error: "Message is not pinned" } + } + + await db + .update(messages) + .set({ isPinned: false }) + .where(eq(messages.id, messageId)) + + revalidatePath("/dashboard") + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to unpin message", + } + } +} diff --git a/src/app/actions/presence.ts b/src/app/actions/presence.ts new file mode 100644 index 0000000..b33f149 --- /dev/null +++ b/src/app/actions/presence.ts @@ -0,0 +1,196 @@ +"use server" + +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { eq, and } from "drizzle-orm" +import { getDb } from "@/db" +import { userPresence, channelMembers, channels } from "@/db/schema-conversations" +import { users } from "@/db/schema" +import { getCurrentUser } from "@/lib/auth" + +type PresenceStatus = "online" | "idle" | "dnd" | "offline" + +const VALID_STATUSES = ["online", "idle", "dnd", "offline"] as const +const MAX_STATUS_LENGTH = 100 + +type ChannelMemberWithPresence = { + id: string + displayName: string | null + avatarUrl: string | null + role: string + status: string + statusMessage: string | null + lastSeenAt: string +} + +type GroupedMembers = { + online: ChannelMemberWithPresence[] + idle: ChannelMemberWithPresence[] + dnd: ChannelMemberWithPresence[] + offline: ChannelMemberWithPresence[] +} + +/** + * Update the current user's presence status. + * Creates a new presence record or updates the existing one. + */ +export async function updatePresence( + status?: PresenceStatus, + statusMessage?: string +): Promise<{ success: true } | { success: false; error: string }> { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + if (statusMessage && statusMessage.length > MAX_STATUS_LENGTH) { + return { success: false, error: `Status message too long (max ${MAX_STATUS_LENGTH} characters)` } + } + + const effectiveStatus = status ?? "online" + if (!VALID_STATUSES.includes(effectiveStatus as typeof VALID_STATUSES[number])) { + return { success: false, error: "Invalid status" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + const now = new Date().toISOString() + + // check if presence record exists + const existing = await db + .select() + .from(userPresence) + .where(eq(userPresence.userId, user.id)) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (existing) { + // update existing record + await db + .update(userPresence) + .set({ + status: effectiveStatus, + statusMessage: statusMessage ?? existing.statusMessage, + lastSeenAt: now, + updatedAt: now, + }) + .where(eq(userPresence.userId, user.id)) + } else { + // create new presence record + await db.insert(userPresence).values({ + id: crypto.randomUUID(), + userId: user.id, + status: effectiveStatus, + statusMessage: statusMessage ?? null, + lastSeenAt: now, + updatedAt: now, + }) + } + + return { success: true } + } catch (err) { + return { + success: false, + error: err instanceof Error ? err.message : "Failed to update presence", + } + } +} + +/** + * Get all members of a channel with their presence information. + * Results are grouped by status for easy display. + */ +export async function getChannelMembersWithPresence( + channelId: string +): Promise< + { success: true; data: GroupedMembers } | { success: false; error: string } +> { + try { + const user = await getCurrentUser() + if (!user) { + return { success: false, error: "Unauthorized" } + } + + const { env } = await getCloudflareContext() + const db = getDb(env.DB) + + // verify user is a member of this channel + const membership = await db + .select() + .from(channelMembers) + .where( + and( + eq(channelMembers.channelId, channelId), + eq(channelMembers.userId, user.id) + ) + ) + .limit(1) + .then((rows) => rows[0] ?? null) + + if (!membership) { + return { + success: false, + error: "Access denied - not a channel member", + } + } + + // fetch all channel members with their user info and presence + const members = await db + .select({ + id: users.id, + displayName: users.displayName, + avatarUrl: users.avatarUrl, + role: channelMembers.role, + status: userPresence.status, + statusMessage: userPresence.statusMessage, + lastSeenAt: userPresence.lastSeenAt, + }) + .from(channelMembers) + .innerJoin(users, eq(channelMembers.userId, users.id)) + .leftJoin(userPresence, eq(users.id, userPresence.userId)) + .where(eq(channelMembers.channelId, channelId)) + + // group members by status + const grouped: GroupedMembers = { + online: [], + idle: [], + dnd: [], + offline: [], + } + + for (const member of members) { + const memberData: ChannelMemberWithPresence = { + id: member.id, + displayName: member.displayName, + avatarUrl: member.avatarUrl, + role: member.role, + status: member.status ?? "offline", + statusMessage: member.statusMessage, + lastSeenAt: member.lastSeenAt ?? new Date(0).toISOString(), + } + + // determine which group based on status + const status = member.status ?? "offline" + if (status === "online") { + grouped.online.push(memberData) + } else if (status === "idle") { + grouped.idle.push(memberData) + } else if (status === "dnd") { + grouped.dnd.push(memberData) + } else { + grouped.offline.push(memberData) + } + } + + return { success: true, data: grouped } + } catch (err) { + return { + success: false, + error: + err instanceof Error + ? err.message + : "Failed to get channel members with presence", + } + } +} diff --git a/src/app/api/sync/checkpoint/route.ts b/src/app/api/sync/checkpoint/route.ts new file mode 100644 index 0000000..594ab55 --- /dev/null +++ b/src/app/api/sync/checkpoint/route.ts @@ -0,0 +1,121 @@ +import { NextRequest, NextResponse } from "next/server" +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { drizzle } from "drizzle-orm/d1" +import { eq } from "drizzle-orm" +import { z } from "zod/v4" +import { getCurrentUser } from "@/lib/auth" +import { syncCheckpoint } from "@/lib/sync/schema" +import { serializeClock, type VectorClockValue } from "@/lib/sync/clock" + +const VectorClockSchema = z.record(z.string(), z.number()) + +const UpdateCheckpointSchema = z.object({ + tableName: z.string(), + cursor: z.string(), + localVectorClock: VectorClockSchema.optional(), +}) + +type CheckpointResponse = { + checkpoints: Array<{ + tableName: string + lastSyncCursor: string | null + localVectorClock: VectorClockValue | null + syncedAt: string + }> +} + +type UpdateResponse = + | { success: true } + | { error: "invalid request"; details?: unknown } + | { error: "unauthorized" } + +export async function GET(): Promise> { + const user = await getCurrentUser() + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const { env } = await getCloudflareContext() + const db = drizzle(env.DB) + + const checkpoints = await db + .select() + .from(syncCheckpoint) + + const response: CheckpointResponse = { + checkpoints: checkpoints.map((cp) => ({ + tableName: cp.tableName, + lastSyncCursor: cp.lastSyncCursor, + localVectorClock: cp.localVectorClock + ? (JSON.parse(cp.localVectorClock) as VectorClockValue) + : null, + syncedAt: cp.syncedAt, + })), + } + + return NextResponse.json(response) +} + +export async function POST( + request: NextRequest, +): Promise> { + const user = await getCurrentUser() + if (!user) { + return NextResponse.json({ error: "unauthorized" }, { status: 401 }) + } + + let body: unknown + try { + body = await request.json() + } catch { + return NextResponse.json( + { error: "invalid request", details: "Invalid JSON body" }, + { status: 400 }, + ) + } + + const parseResult = UpdateCheckpointSchema.safeParse(body) + if (!parseResult.success) { + return NextResponse.json( + { error: "invalid request", details: parseResult.error.issues }, + { status: 400 }, + ) + } + + const { tableName, cursor, localVectorClock } = parseResult.data + + const { env } = await getCloudflareContext() + const db = drizzle(env.DB) + + const now = new Date().toISOString() + + const existing = await db + .select() + .from(syncCheckpoint) + .where(eq(syncCheckpoint.tableName, tableName)) + .limit(1) + + const clockJson = localVectorClock + ? serializeClock(localVectorClock) + : null + + if (existing[0]) { + await db + .update(syncCheckpoint) + .set({ + lastSyncCursor: cursor, + localVectorClock: clockJson, + syncedAt: now, + }) + .where(eq(syncCheckpoint.tableName, tableName)) + } else { + await db.insert(syncCheckpoint).values({ + tableName, + lastSyncCursor: cursor, + localVectorClock: clockJson, + syncedAt: now, + }) + } + + return NextResponse.json({ success: true }) +} diff --git a/src/app/api/sync/delta/route.ts b/src/app/api/sync/delta/route.ts new file mode 100644 index 0000000..9d85b01 --- /dev/null +++ b/src/app/api/sync/delta/route.ts @@ -0,0 +1,233 @@ +import { NextRequest, NextResponse } from "next/server" +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { drizzle } from "drizzle-orm/d1" +import { eq, and, gt, inArray } from "drizzle-orm" +import { z } from "zod/v4" +import { getCurrentUser } from "@/lib/auth" +import { + localSyncMetadata, +} from "@/lib/sync/schema" +import { + projects, + scheduleTasks, + taskDependencies, + users, + organizations, + teams, + groups, +} from "@/db/schema" + +const QuerySchema = z.object({ + since: z.string().datetime().optional(), + tables: z.string().optional(), +}) + +type TableWithUpdatedAt = { + id: string + updatedAt: string +} + +type ChangeRecord = { + table: string + id: string + data: Record + vectorClock: Record + deleted: boolean +} + +type DeltaResponse = { + changes: ChangeRecord[] + checkpoint: string +} + +const TABLE_FETCHERS = { + projects: async ( + db: ReturnType, + since: string | null, + ) => { + const conditions = since ? [gt(projects.createdAt, since)] : [] + return db + .select() + .from(projects) + .where(conditions.length > 0 ? and(...conditions) : undefined) + }, + scheduleTasks: async ( + db: ReturnType, + since: string | null, + ) => { + const conditions = since ? [gt(scheduleTasks.updatedAt, since)] : [] + return db + .select() + .from(scheduleTasks) + .where(conditions.length > 0 ? and(...conditions) : undefined) + }, + taskDependencies: async ( + db: ReturnType, + _since: string | null, + ) => { + return db.select().from(taskDependencies) + }, + users: async ( + db: ReturnType, + since: string | null, + ) => { + const conditions = since ? [gt(users.updatedAt, since)] : [] + return db + .select() + .from(users) + .where(conditions.length > 0 ? and(...conditions) : undefined) + }, + organizations: async ( + db: ReturnType, + since: string | null, + ) => { + const conditions = since ? [gt(organizations.updatedAt, since)] : [] + return db + .select() + .from(organizations) + .where(conditions.length > 0 ? and(...conditions) : undefined) + }, + teams: async ( + db: ReturnType, + since: string | null, + ) => { + const conditions = since ? [gt(teams.createdAt, since)] : [] + return db + .select() + .from(teams) + .where(conditions.length > 0 ? and(...conditions) : undefined) + }, + groups: async ( + db: ReturnType, + since: string | null, + ) => { + const conditions = since ? [gt(groups.createdAt, since)] : [] + return db + .select() + .from(groups) + .where(conditions.length > 0 ? and(...conditions) : undefined) + }, +} as const + +type SyncableTable = keyof typeof TABLE_FETCHERS + +const SYNCABLE_TABLES = Object.keys(TABLE_FETCHERS) as SyncableTable[] + +export async function GET(request: NextRequest): Promise { + const user = await getCurrentUser() + if (!user) { + return NextResponse.json({ error: "Unauthorized" }, { status: 401 }) + } + + const { searchParams } = new URL(request.url) + const parseResult = QuerySchema.safeParse({ + since: searchParams.get("since") ?? undefined, + tables: searchParams.get("tables") ?? undefined, + }) + + if (!parseResult.success) { + return NextResponse.json( + { error: "Invalid query parameters", details: parseResult.error.issues }, + { status: 400 }, + ) + } + + const { since, tables: tablesParam } = parseResult.data + + let requestedTables: SyncableTable[] = [...SYNCABLE_TABLES] + if (tablesParam) { + const tableNames = tablesParam.split(",").map((t) => t.trim()) + requestedTables = tableNames.filter((t): t is SyncableTable => + SYNCABLE_TABLES.includes(t as SyncableTable), + ) + if (requestedTables.length === 0) { + return NextResponse.json( + { error: "No valid tables specified" }, + { status: 400 }, + ) + } + } + + const { env } = await getCloudflareContext() + const db = drizzle(env.DB) + + const changes: ChangeRecord[] = [] + const checkpoint = new Date().toISOString() + + for (const tableName of requestedTables) { + try { + const tableChanges = await fetchTableChanges( + db, + tableName, + since ?? null, + ) + changes.push(...tableChanges) + } catch (err) { + console.error(`Error fetching changes for ${tableName}:`, err) + } + } + + const response: DeltaResponse = { + changes, + checkpoint, + } + + return NextResponse.json(response) +} + +async function fetchTableChanges( + db: ReturnType, + tableName: SyncableTable, + since: string | null, +): Promise { + const fetcher = TABLE_FETCHERS[tableName] + if (!fetcher) return [] + + const records = await fetcher(db, since) + const changes: ChangeRecord[] = [] + + const recordIds = records.map((r) => (r as TableWithUpdatedAt).id) + + if (recordIds.length === 0) return [] + + const metadataRecords = await db + .select() + .from(localSyncMetadata) + .where( + and( + eq(localSyncMetadata.tableName, tableName), + inArray(localSyncMetadata.recordId, recordIds), + ), + ) + + const metadataMap = new Map( + metadataRecords.map((m) => [m.recordId, m]), + ) + + for (const record of records) { + const r = record as TableWithUpdatedAt + const metadata = metadataMap.get(r.id) + + let vectorClock: Record = {} + + if (metadata) { + try { + vectorClock = JSON.parse(metadata.vectorClock) as Record + } catch { + vectorClock = {} + } + } + + const { id, ...data } = r as TableWithUpdatedAt & Record + + changes.push({ + table: tableName, + id, + data, + vectorClock, + deleted: false, + }) + } + + return changes +} diff --git a/src/app/api/sync/mutate/route.ts b/src/app/api/sync/mutate/route.ts new file mode 100644 index 0000000..c822da3 --- /dev/null +++ b/src/app/api/sync/mutate/route.ts @@ -0,0 +1,880 @@ +import { NextRequest, NextResponse } from "next/server" +import { getCloudflareContext } from "@opennextjs/cloudflare" +import { drizzle } from "drizzle-orm/d1" +import { eq, and } from "drizzle-orm" +import { z } from "zod/v4" +import { getCurrentUser } from "@/lib/auth" +import { can } from "@/lib/permissions" +import { + localSyncMetadata, + SyncStatus, +} from "@/lib/sync/schema" +import { + detectConflict, + resolveConflict, + ConflictStrategy, +} from "@/lib/sync/conflict" +import { + serializeClock, + parseClock, + type VectorClockValue, +} from "@/lib/sync/clock" +import { + projects, + scheduleTasks, + taskDependencies, + users, + organizations, + teams, + groups, + projectMembers, + organizationMembers, +} from "@/db/schema" +import type { SQLiteTable } from "drizzle-orm/sqlite-core" + +const VectorClockSchema = z.record(z.string(), z.number()) + +// Field allowlists for each syncable table - prevents mass assignment +const FIELD_ALLOWLISTS = { + projects: [ + "name", + "status", + "address", + "clientName", + "projectManager", + "organizationId", + "netsuiteJobId", + ] as const, + scheduleTasks: [ + "projectId", + "title", + "startDate", + "workdays", + "endDateCalculated", + "phase", + "status", + "isCriticalPath", + "isMilestone", + "percentComplete", + "assignedTo", + "sortOrder", + ] as const, + taskDependencies: ["predecessorId", "successorId", "type", "lagDays"] as const, + users: [ + "email", + "firstName", + "lastName", + "displayName", + "avatarUrl", + "role", + "googleEmail", + "isActive", + "lastLoginAt", + ] as const, + organizations: ["name", "slug", "type", "logoUrl", "isActive"] as const, + teams: ["organizationId", "name", "description"] as const, + groups: ["organizationId", "name", "description", "color"] as const, +} as const + +// TableAllowlist type available for future use if needed +// type TableAllowlist = (typeof FIELD_ALLOWLISTS)[keyof typeof FIELD_ALLOWLISTS] + +// Zod schemas for payload validation per table +const ProjectPayloadSchema = z.object({ + name: z.string().optional(), + status: z.string().optional(), + address: z.string().nullable().optional(), + clientName: z.string().nullable().optional(), + projectManager: z.string().nullable().optional(), + organizationId: z.string().nullable().optional(), + netsuiteJobId: z.string().nullable().optional(), +}) + +const ScheduleTaskPayloadSchema = z.object({ + projectId: z.string().optional(), + title: z.string().optional(), + startDate: z.string().optional(), + workdays: z.number().optional(), + endDateCalculated: z.string().optional(), + phase: z.string().optional(), + status: z.string().optional(), + isCriticalPath: z.boolean().optional(), + isMilestone: z.boolean().optional(), + percentComplete: z.number().optional(), + assignedTo: z.string().nullable().optional(), + sortOrder: z.number().optional(), +}) + +const TaskDependencyPayloadSchema = z.object({ + predecessorId: z.string().optional(), + successorId: z.string().optional(), + type: z.string().optional(), + lagDays: z.number().optional(), +}) + +const UserPayloadSchema = z.object({ + email: z.string().optional(), + firstName: z.string().nullable().optional(), + lastName: z.string().nullable().optional(), + displayName: z.string().nullable().optional(), + avatarUrl: z.string().nullable().optional(), + role: z.string().optional(), + googleEmail: z.string().nullable().optional(), + isActive: z.boolean().optional(), + lastLoginAt: z.string().nullable().optional(), +}) + +const OrganizationPayloadSchema = z.object({ + name: z.string().optional(), + slug: z.string().optional(), + type: z.string().optional(), + logoUrl: z.string().nullable().optional(), + isActive: z.boolean().optional(), +}) + +const TeamPayloadSchema = z.object({ + organizationId: z.string().optional(), + name: z.string().optional(), + description: z.string().nullable().optional(), +}) + +const GroupPayloadSchema = z.object({ + organizationId: z.string().optional(), + name: z.string().optional(), + description: z.string().nullable().optional(), + color: z.string().nullable().optional(), +}) + +const PayloadSchemas = { + projects: ProjectPayloadSchema, + scheduleTasks: ScheduleTaskPayloadSchema, + taskDependencies: TaskDependencyPayloadSchema, + users: UserPayloadSchema, + organizations: OrganizationPayloadSchema, + teams: TeamPayloadSchema, + groups: GroupPayloadSchema, +} as const + +// Maps table names to permission resources +const TABLE_TO_RESOURCE = { + projects: "project" as const, + scheduleTasks: "schedule" as const, + taskDependencies: "schedule" as const, + users: "user" as const, + organizations: "organization" as const, + teams: "team" as const, + groups: "group" as const, +} as const + +const MutateRequestSchema = z.object({ + operation: z.enum(["insert", "update", "delete"]), + table: z.string(), + recordId: z.string(), + payload: z.record(z.string(), z.unknown()).nullable(), + vectorClock: VectorClockSchema, +}) + +type MutateResponse = + | { success: true } + | { error: "conflict"; serverData: Record } + | { error: "invalid request"; details?: unknown } + | { error: "unauthorized" } + | { error: "forbidden"; reason?: string } + | { error: "table not supported" } + | { error: "internal error"; message?: string } + +const SYNCABLE_TABLES = new Set([ + "projects", + "scheduleTasks", + "taskDependencies", + "users", + "organizations", + "teams", + "groups", +]) + +// Filter payload to only include allowed fields for a table +function filterPayloadFields( + table: string, + payload: Record, +): Record { + const allowlist = FIELD_ALLOWLISTS[table as keyof typeof FIELD_ALLOWLISTS] + if (!allowlist) { + return {} + } + const filtered: Record = {} + for (const key of allowlist) { + if (key in payload) { + filtered[key] = payload[key] + } + } + return filtered +} + +// Validate payload against Zod schema for the table +function validatePayload( + table: string, + payload: Record, +): { success: true; data: Record } | { success: false; error: unknown } { + const schema = PayloadSchemas[table as keyof typeof PayloadSchemas] + if (!schema) { + return { success: false, error: "No schema for table" } + } + const result = schema.safeParse(payload) + if (!result.success) { + return { success: false, error: result.error.issues } + } + return { success: true, data: result.data } +} + +// Map operation to permission action +function operationToAction( + operation: "insert" | "update" | "delete", +): "create" | "update" | "delete" { + switch (operation) { + case "insert": + return "create" + case "update": + return "update" + case "delete": + return "delete" + } +} + +// Check if user has access to a specific project +async function checkProjectAccess( + db: ReturnType, + userId: string, + projectId: string, +): Promise { + const membership = await db + .select() + .from(projectMembers) + .where(and(eq(projectMembers.userId, userId), eq(projectMembers.projectId, projectId))) + .limit(1) + return membership.length > 0 +} + +// Check if user has access to an organization +async function checkOrganizationAccess( + db: ReturnType, + userId: string, + organizationId: string, +): Promise { + const membership = await db + .select() + .from(organizationMembers) + .where( + and( + eq(organizationMembers.userId, userId), + eq(organizationMembers.organizationId, organizationId), + ), + ) + .limit(1) + return membership.length > 0 +} + +// Resource-specific authorization check +async function checkResourceAuthorization( + db: ReturnType, + userId: string, + table: string, + operation: "insert" | "update" | "delete", + payload: Record | null, + recordId: string, +): Promise<{ authorized: boolean; reason?: string }> { + const resource = TABLE_TO_RESOURCE[table as keyof typeof TABLE_TO_RESOURCE] + if (!resource) { + return { authorized: false, reason: "Unknown resource type" } + } + + // Get user for role-based permission check + const userRecords = await db.select().from(users).where(eq(users.id, userId)).limit(1) + const user = userRecords[0] + if (!user) { + return { authorized: false, reason: "User not found" } + } + + const action = operationToAction(operation) + + // Check role-based permission + if (!can(user, resource, action)) { + return { authorized: false, reason: `Role ${user.role} cannot ${action} ${resource}` } + } + + // For project-related resources, check project membership + if (table === "scheduleTasks" || table === "taskDependencies") { + let projectId: string | undefined + if (table === "scheduleTasks") { + projectId = payload?.projectId as string | undefined + if (!projectId && operation !== "insert") { + // For updates/deletes, fetch the task to get projectId + const task = await db + .select() + .from(scheduleTasks) + .where(eq(scheduleTasks.id, recordId)) + .limit(1) + projectId = task[0]?.projectId + } + } else if (table === "taskDependencies") { + // For dependencies, get the task's project + // First try to find the dependency itself + const dep = await db + .select() + .from(taskDependencies) + .where(eq(taskDependencies.id, recordId)) + .limit(1) + if (dep[0]) { + const task = await db + .select() + .from(scheduleTasks) + .where(eq(scheduleTasks.id, dep[0].predecessorId)) + .limit(1) + projectId = task[0]?.projectId + } else if (payload?.predecessorId) { + const task = await db + .select() + .from(scheduleTasks) + .where(eq(scheduleTasks.id, payload.predecessorId as string)) + .limit(1) + projectId = task[0]?.projectId + } + } + if (projectId && !(await checkProjectAccess(db, userId, projectId))) { + return { authorized: false, reason: "No access to project" } + } + } + + // For projects, check organization membership + if (table === "projects") { + let organizationId = payload?.organizationId as string | undefined + if (!organizationId && operation !== "insert") { + const project = await db + .select() + .from(projects) + .where(eq(projects.id, recordId)) + .limit(1) + organizationId = project[0]?.organizationId ?? undefined + } + if (organizationId && !(await checkOrganizationAccess(db, userId, organizationId))) { + return { authorized: false, reason: "No access to organization" } + } + } + + // For teams and groups, check organization membership + if (table === "teams" || table === "groups") { + const organizationId = payload?.organizationId as string | undefined + if (organizationId && !(await checkOrganizationAccess(db, userId, organizationId))) { + return { authorized: false, reason: "No access to organization" } + } + } + + return { authorized: true } +} + +type TableHandler = { + table: SQLiteTable + idColumn: unknown + fetchRecord: ( + db: ReturnType, + recordId: string, + ) => Promise | null> + applyInsert: ( + db: ReturnType, + recordId: string, + payload: Record, + now: string, + ) => Promise + applyUpdate: ( + db: ReturnType, + recordId: string, + payload: Record, + now: string, + ) => Promise + applyDelete: ( + db: ReturnType, + recordId: string, + ) => Promise +} + +const TABLE_HANDLERS: Record = { + projects: { + table: projects, + idColumn: projects.id, + fetchRecord: async (db, recordId) => { + const records = await db + .select() + .from(projects) + .where(eq(projects.id, recordId)) + .limit(1) + return (records[0] as Record) ?? null + }, + applyInsert: async (db, recordId, payload, now) => { + await db.insert(projects).values({ + id: recordId, + name: payload.name as string, + status: (payload.status as string) ?? "OPEN", + address: payload.address as string | null ?? null, + clientName: payload.clientName as string | null ?? null, + projectManager: payload.projectManager as string | null ?? null, + organizationId: payload.organizationId as string | null ?? null, + netsuiteJobId: payload.netsuiteJobId as string | null ?? null, + createdAt: now, + }) + }, + applyUpdate: async (db, recordId, payload, now) => { + const updateData: Record = { ...payload, updatedAt: now } + delete updateData.id + delete updateData.createdAt + await db.update(projects).set(updateData).where(eq(projects.id, recordId)) + }, + applyDelete: async (db, recordId) => { + await db.delete(projects).where(eq(projects.id, recordId)) + }, + }, + scheduleTasks: { + table: scheduleTasks, + idColumn: scheduleTasks.id, + fetchRecord: async (db, recordId) => { + const records = await db + .select() + .from(scheduleTasks) + .where(eq(scheduleTasks.id, recordId)) + .limit(1) + return (records[0] as Record) ?? null + }, + applyInsert: async (db, recordId, payload, now) => { + await db.insert(scheduleTasks).values({ + id: recordId, + projectId: payload.projectId as string, + title: payload.title as string, + startDate: payload.startDate as string, + workdays: payload.workdays as number, + endDateCalculated: payload.endDateCalculated as string, + phase: payload.phase as string, + status: (payload.status as string) ?? "PENDING", + isCriticalPath: payload.isCriticalPath as boolean ?? false, + isMilestone: payload.isMilestone as boolean ?? false, + percentComplete: payload.percentComplete as number ?? 0, + assignedTo: payload.assignedTo as string | null ?? null, + sortOrder: payload.sortOrder as number ?? 0, + createdAt: now, + updatedAt: now, + }) + }, + applyUpdate: async (db, recordId, payload, now) => { + const updateData: Record = { ...payload, updatedAt: now } + delete updateData.id + delete updateData.createdAt + await db.update(scheduleTasks).set(updateData).where(eq(scheduleTasks.id, recordId)) + }, + applyDelete: async (db, recordId) => { + await db.delete(scheduleTasks).where(eq(scheduleTasks.id, recordId)) + }, + }, + taskDependencies: { + table: taskDependencies, + idColumn: taskDependencies.id, + fetchRecord: async (db, recordId) => { + const records = await db + .select() + .from(taskDependencies) + .where(eq(taskDependencies.id, recordId)) + .limit(1) + return (records[0] as Record) ?? null + }, + applyInsert: async (db, recordId, payload, _now) => { + await db.insert(taskDependencies).values({ + id: recordId, + predecessorId: payload.predecessorId as string, + successorId: payload.successorId as string, + type: (payload.type as string) ?? "FS", + lagDays: payload.lagDays as number ?? 0, + }) + }, + applyUpdate: async (db, recordId, payload, _now) => { + const updateData: Record = { ...payload } + delete updateData.id + await db.update(taskDependencies).set(updateData).where(eq(taskDependencies.id, recordId)) + }, + applyDelete: async (db, recordId) => { + await db.delete(taskDependencies).where(eq(taskDependencies.id, recordId)) + }, + }, + users: { + table: users, + idColumn: users.id, + fetchRecord: async (db, recordId) => { + const records = await db + .select() + .from(users) + .where(eq(users.id, recordId)) + .limit(1) + return (records[0] as Record) ?? null + }, + applyInsert: async (db, recordId, payload, now) => { + await db.insert(users).values({ + id: recordId, + email: payload.email as string, + firstName: payload.firstName as string | null ?? null, + lastName: payload.lastName as string | null ?? null, + displayName: payload.displayName as string | null ?? null, + avatarUrl: payload.avatarUrl as string | null ?? null, + role: (payload.role as string) ?? "office", + googleEmail: payload.googleEmail as string | null ?? null, + isActive: payload.isActive as boolean ?? true, + lastLoginAt: payload.lastLoginAt as string | null ?? null, + createdAt: now, + updatedAt: now, + }) + }, + applyUpdate: async (db, recordId, payload, now) => { + const updateData: Record = { ...payload, updatedAt: now } + delete updateData.id + delete updateData.createdAt + await db.update(users).set(updateData).where(eq(users.id, recordId)) + }, + applyDelete: async (db, recordId) => { + await db.delete(users).where(eq(users.id, recordId)) + }, + }, + organizations: { + table: organizations, + idColumn: organizations.id, + fetchRecord: async (db, recordId) => { + const records = await db + .select() + .from(organizations) + .where(eq(organizations.id, recordId)) + .limit(1) + return (records[0] as Record) ?? null + }, + applyInsert: async (db, recordId, payload, now) => { + await db.insert(organizations).values({ + id: recordId, + name: payload.name as string, + slug: payload.slug as string, + type: payload.type as string, + logoUrl: payload.logoUrl as string | null ?? null, + isActive: payload.isActive as boolean ?? true, + createdAt: now, + updatedAt: now, + }) + }, + applyUpdate: async (db, recordId, payload, now) => { + const updateData: Record = { ...payload, updatedAt: now } + delete updateData.id + delete updateData.createdAt + await db.update(organizations).set(updateData).where(eq(organizations.id, recordId)) + }, + applyDelete: async (db, recordId) => { + await db.delete(organizations).where(eq(organizations.id, recordId)) + }, + }, + teams: { + table: teams, + idColumn: teams.id, + fetchRecord: async (db, recordId) => { + const records = await db + .select() + .from(teams) + .where(eq(teams.id, recordId)) + .limit(1) + return (records[0] as Record) ?? null + }, + applyInsert: async (db, recordId, payload, now) => { + await db.insert(teams).values({ + id: recordId, + organizationId: payload.organizationId as string, + name: payload.name as string, + description: payload.description as string | null ?? null, + createdAt: now, + }) + }, + applyUpdate: async (db, recordId, payload, _now) => { + const updateData: Record = { ...payload } + delete updateData.id + delete updateData.createdAt + await db.update(teams).set(updateData).where(eq(teams.id, recordId)) + }, + applyDelete: async (db, recordId) => { + await db.delete(teams).where(eq(teams.id, recordId)) + }, + }, + groups: { + table: groups, + idColumn: groups.id, + fetchRecord: async (db, recordId) => { + const records = await db + .select() + .from(groups) + .where(eq(groups.id, recordId)) + .limit(1) + return (records[0] as Record) ?? null + }, + applyInsert: async (db, recordId, payload, now) => { + await db.insert(groups).values({ + id: recordId, + organizationId: payload.organizationId as string, + name: payload.name as string, + description: payload.description as string | null ?? null, + color: payload.color as string | null ?? null, + createdAt: now, + }) + }, + applyUpdate: async (db, recordId, payload, _now) => { + const updateData: Record = { ...payload } + delete updateData.id + delete updateData.createdAt + await db.update(groups).set(updateData).where(eq(groups.id, recordId)) + }, + applyDelete: async (db, recordId) => { + await db.delete(groups).where(eq(groups.id, recordId)) + }, + }, +} + +export async function POST(request: NextRequest): Promise> { + const user = await getCurrentUser() + if (!user) { + return NextResponse.json({ error: "unauthorized" }, { status: 401 }) + } + + let body: unknown + try { + body = await request.json() + } catch { + return NextResponse.json( + { error: "invalid request", details: "Invalid JSON body" }, + { status: 400 }, + ) + } + + const parseResult = MutateRequestSchema.safeParse(body) + if (!parseResult.success) { + return NextResponse.json( + { error: "invalid request", details: parseResult.error.issues }, + { status: 400 }, + ) + } + + const { operation, table, recordId, payload, vectorClock } = parseResult.data + + if (!SYNCABLE_TABLES.has(table)) { + return NextResponse.json( + { error: "table not supported" }, + { status: 400 }, + ) + } + + const handler = TABLE_HANDLERS[table] + if (!handler) { + return NextResponse.json( + { error: "table not supported" }, + { status: 400 }, + ) + } + + // Filter and validate payload to prevent mass assignment + let filteredPayload: Record = {} + if (payload) { + filteredPayload = filterPayloadFields(table, payload) + const validation = validatePayload(table, filteredPayload) + if (!validation.success) { + return NextResponse.json( + { error: "invalid request", details: validation.error }, + { status: 400 }, + ) + } + filteredPayload = validation.data + } + + const { env } = await getCloudflareContext() + const db = drizzle(env.DB) + + // Authorization check + const authResult = await checkResourceAuthorization( + db, + user.id, + table, + operation, + filteredPayload, + recordId, + ) + if (!authResult.authorized) { + return NextResponse.json( + { error: "forbidden", reason: authResult.reason }, + { status: 403 }, + ) + } + + try { + const existingMetadata = await db + .select() + .from(localSyncMetadata) + .where( + and( + eq(localSyncMetadata.tableName, table), + eq(localSyncMetadata.recordId, recordId), + ), + ) + .limit(1) + + const serverMetadata = existingMetadata[0] + + if (serverMetadata) { + const conflictResult = detectConflict( + serverMetadata.vectorClock, + serializeClock(vectorClock), + ) + + if (conflictResult.hasConflict) { + const serverRecord = await handler.fetchRecord(db, recordId) + + const resolution = resolveConflict( + ConflictStrategy.NEWEST_WINS, + serverRecord ?? {}, + filteredPayload, + parseClock(serverMetadata.vectorClock), + vectorClock, + serverMetadata.lastModifiedAt, + new Date().toISOString(), + ) + + if (resolution.resolution === "flag_manual") { + return NextResponse.json( + { + error: "conflict", + serverData: { + data: serverRecord, + vectorClock: parseClock(serverMetadata.vectorClock), + lastModifiedAt: serverMetadata.lastModifiedAt, + }, + }, + { status: 409 }, + ) + } + + if (resolution.resolution === "use_local") { + await applyMutationWithHandler( + db, + handler, + table, + operation, + recordId, + filteredPayload, + vectorClock, + ) + return NextResponse.json({ success: true }) + } + } + } + + await applyMutationWithHandler( + db, + handler, + table, + operation, + recordId, + filteredPayload, + vectorClock, + ) + + return NextResponse.json({ success: true }) + } catch (err) { + console.error("Mutation error:", err) + return NextResponse.json( + { error: "internal error", message: err instanceof Error ? err.message : "Unknown error" }, + { status: 500 }, + ) + } +} + +async function applyMutationWithHandler( + db: ReturnType, + handler: TableHandler, + tableName: string, + operation: "insert" | "update" | "delete", + recordId: string, + payload: Record | null, + vectorClock: VectorClockValue, +): Promise { + const now = new Date().toISOString() + const clockJson = serializeClock(vectorClock) + + if (operation === "delete") { + await handler.applyDelete(db, recordId) + + const existingMetadata = await db + .select() + .from(localSyncMetadata) + .where( + and( + eq(localSyncMetadata.tableName, tableName), + eq(localSyncMetadata.recordId, recordId), + ), + ) + .limit(1) + + if (existingMetadata[0]) { + await db + .update(localSyncMetadata) + .set({ + vectorClock: clockJson, + lastModifiedAt: now, + syncStatus: SyncStatus.SYNCED, + }) + .where(eq(localSyncMetadata.id, existingMetadata[0].id)) + } + return + } + + if (operation === "insert") { + await handler.applyInsert(db, recordId, payload ?? {}, now) + + await db.insert(localSyncMetadata).values({ + tableName, + recordId, + vectorClock: clockJson, + lastModifiedAt: now, + syncStatus: SyncStatus.SYNCED, + createdAt: now, + }) + return + } + + if (operation === "update") { + await handler.applyUpdate(db, recordId, payload ?? {}, now) + + const existingMetadata = await db + .select() + .from(localSyncMetadata) + .where( + and( + eq(localSyncMetadata.tableName, tableName), + eq(localSyncMetadata.recordId, recordId), + ), + ) + .limit(1) + + if (existingMetadata[0]) { + await db + .update(localSyncMetadata) + .set({ + vectorClock: clockJson, + lastModifiedAt: now, + syncStatus: SyncStatus.SYNCED, + }) + .where(eq(localSyncMetadata.id, existingMetadata[0].id)) + } else { + await db.insert(localSyncMetadata).values({ + tableName, + recordId, + vectorClock: clockJson, + lastModifiedAt: now, + syncStatus: SyncStatus.SYNCED, + createdAt: now, + }) + } + } +} diff --git a/src/app/dashboard/conversations/[channelId]/page.tsx b/src/app/dashboard/conversations/[channelId]/page.tsx new file mode 100644 index 0000000..818ab89 --- /dev/null +++ b/src/app/dashboard/conversations/[channelId]/page.tsx @@ -0,0 +1,44 @@ +import { notFound } from "next/navigation" +import { getChannel } from "@/app/actions/conversations" +import { getMessages } from "@/app/actions/chat-messages" +import { ChannelHeader } from "@/components/conversations/channel-header" +import { MessageList } from "@/components/conversations/message-list" +import { MessageComposer } from "@/components/conversations/message-composer" +import { ThreadPanel } from "@/components/conversations/thread-panel" + +export default async function ChannelPage({ + params, +}: { + readonly params: Promise<{ readonly channelId: string }> +}) { + const { channelId } = await params + const [channelResult, messagesResult] = await Promise.all([ + getChannel(channelId), + getMessages(channelId), + ]) + + if (!channelResult.success || !channelResult.data) { + notFound() + } + + const channel = channelResult.data + const messages = messagesResult.success && messagesResult.data ? messagesResult.data : [] + + return ( + <> +
+ + + +
+ + + ) +} diff --git a/src/app/dashboard/conversations/layout.tsx b/src/app/dashboard/conversations/layout.tsx new file mode 100644 index 0000000..d11ba5f --- /dev/null +++ b/src/app/dashboard/conversations/layout.tsx @@ -0,0 +1,86 @@ +"use client" + +import * as React from "react" + +type ThreadMessage = { + readonly id: string + readonly channelId: string + readonly threadId: string | null + readonly content: string + readonly contentHtml: string | null + readonly editedAt: string | null + readonly deletedAt: string | null + readonly isPinned: boolean + readonly replyCount: number + readonly lastReplyAt: string | null + readonly createdAt: string + readonly user: { + readonly id: string + readonly displayName: string | null + readonly email: string + readonly avatarUrl: string | null + } | null +} + +type ConversationsContextType = { + readonly threadOpen: boolean + readonly threadMessageId: string | null + readonly threadParentMessage: ThreadMessage | null + readonly openThread: (messageId: string, parentMessage: ThreadMessage) => void + readonly closeThread: () => void +} + +const ConversationsContext = React.createContext( + undefined +) + +export function useConversations() { + const context = React.useContext(ConversationsContext) + if (!context) { + throw new Error("useConversations must be used within ConversationsProvider") + } + return context +} + +export type { ThreadMessage } + +export default function ConversationsLayout({ + children, +}: { + readonly children: React.ReactNode +}) { + const [threadOpen, setThreadOpen] = React.useState(false) + const [threadMessageId, setThreadMessageId] = React.useState(null) + const [threadParentMessage, setThreadParentMessage] = React.useState(null) + + const openThread = React.useCallback((messageId: string, parentMessage: ThreadMessage) => { + setThreadMessageId(messageId) + setThreadParentMessage(parentMessage) + setThreadOpen(true) + }, []) + + const closeThread = React.useCallback(() => { + setThreadOpen(false) + setThreadMessageId(null) + setThreadParentMessage(null) + }, []) + + const value = React.useMemo( + () => ({ + threadOpen, + threadMessageId, + threadParentMessage, + openThread, + closeThread, + }), + [threadOpen, threadMessageId, threadParentMessage, openThread, closeThread] + ) + + return ( + +
+ {children} +
+
+ ) +} diff --git a/src/app/dashboard/conversations/page.tsx b/src/app/dashboard/conversations/page.tsx new file mode 100644 index 0000000..9ec16d9 --- /dev/null +++ b/src/app/dashboard/conversations/page.tsx @@ -0,0 +1,25 @@ +import { redirect } from "next/navigation" +import { MessageSquare } from "lucide-react" +import { listChannels } from "@/app/actions/conversations" +import { CreateChannelButton } from "@/components/conversations/create-channel-button" + +export default async function ConversationsPage() { + const result = await listChannels() + + if (result.success && result.data && result.data.length > 0) { + redirect(`/dashboard/conversations/${result.data[0].id}`) + } + + return ( +
+ +
+

No channels yet

+

+ Create your first channel to start conversations with your team +

+
+ +
+ ) +} diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx index f846e05..f95dc45 100755 --- a/src/app/dashboard/layout.tsx +++ b/src/app/dashboard/layout.tsx @@ -24,6 +24,8 @@ import { BiometricGuard } from "@/components/native/biometric-guard" import { OfflineBanner } from "@/components/native/offline-banner" import { NativeShell } from "@/components/native/native-shell" import { PushNotificationRegistrar } from "@/hooks/use-native-push" +import { DesktopShell } from "@/components/desktop/desktop-shell" +import { DesktopOfflineBanner } from "@/components/desktop/offline-banner" export default async function DashboardLayout({ children, @@ -48,6 +50,7 @@ export default async function DashboardLayout({ + +
@@ -80,6 +84,7 @@ export default async function DashboardLayout({

+ diff --git a/src/app/globals.css b/src/app/globals.css index f23da72..a4bcc24 100755 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -220,4 +220,13 @@ *::-webkit-scrollbar { display: none; /* chrome, safari, opera */ +} + +/* tiptap editor placeholder */ +.tiptap p.is-editor-empty:first-child::before { + content: attr(data-placeholder); + color: color-mix(in oklch, currentColor 40%, transparent); + pointer-events: none; + float: left; + height: 0; } \ No newline at end of file diff --git a/src/components/agent/main-content.tsx b/src/components/agent/main-content.tsx index 291aa26..a7f370b 100755 --- a/src/components/agent/main-content.tsx +++ b/src/components/agent/main-content.tsx @@ -14,6 +14,7 @@ export function MainContent({ const hasRenderedUI = !!spec?.root || isRendering const isCollapsed = pathname === "/dashboard" && !hasRenderedUI + const isConversations = pathname?.startsWith("/dashboard/conversations") return (
-
+
{children}
diff --git a/src/components/ai/status-indicator.tsx b/src/components/ai/status-indicator.tsx new file mode 100644 index 0000000..bea07e2 --- /dev/null +++ b/src/components/ai/status-indicator.tsx @@ -0,0 +1,179 @@ +"use client" + +import { + BrainIcon, + CheckCircleIcon, + ChevronDownIcon, + LoaderIcon, + XCircleIcon, +} from "lucide-react" +import type { ComponentProps, ReactNode } from "react" +import { cn } from "@/lib/utils" + +export type StatusState = "streaming" | "complete" | "error" | "pending" + +export interface StatusIndicatorProps { + state: StatusState + icon?: ReactNode + label: ReactNode + chevronDirection?: "up" | "down" | "none" + className?: string +} + +const getStatusIcon = (state: StatusState, customIcon?: ReactNode): ReactNode => { + if (customIcon) return customIcon + + switch (state) { + case "streaming": + case "pending": + return + case "complete": + return + case "error": + return + default: + return + } +} + +export function StatusIndicator({ + state, + icon, + label, + chevronDirection = "down", + className, +}: StatusIndicatorProps) { + return ( + + {getStatusIcon(state, icon)} + {label} + {chevronDirection !== "none" && ( + + )} + + ) +} + +export interface ThinkingIndicatorProps { + isStreaming: boolean + duration?: number + className?: string +} + +export function ThinkingIndicator({ + isStreaming, + duration, + className, +}: ThinkingIndicatorProps) { + const label = isStreaming + ? "Thinking..." + : duration === undefined + ? "Thought for a few seconds" + : `Thought for ${duration} seconds` + + return ( + + {isStreaming ? ( + + ) : ( + + )} + {label} + + + ) +} + +export interface CollapsibleIndicatorProps extends ComponentProps<"button"> { + isStreaming?: boolean + isOpen?: boolean + icon?: ReactNode + label: ReactNode + variant?: "thinking" | "tool" | "default" +} + +export function CollapsibleIndicator({ + isStreaming = false, + isOpen = false, + icon, + label, + variant = "default", + className, + ...props +}: CollapsibleIndicatorProps) { + const displayIcon = isStreaming ? ( + + ) : ( + icon + ) + + return ( + + ) +} + +/** Demo component for preview */ +export default function StatusIndicatorDemo() { + return ( +
+

Status Indicators

+
+ + + + +
+ +

Thinking Indicators

+
+ + + +
+ +

With Icons

+
+ } + label="Analyzed" + chevronDirection="down" + /> +
+
+ ) +} diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx index 012147b..2e250c1 100755 --- a/src/components/app-sidebar.tsx +++ b/src/components/app-sidebar.tsx @@ -19,6 +19,7 @@ import { NavDashboards } from "@/components/nav-dashboards" import { NavSecondary } from "@/components/nav-secondary" import { NavFiles } from "@/components/nav-files" import { NavProjects } from "@/components/nav-projects" +import { NavConversations } from "@/components/nav-conversations" import { NavUser } from "@/components/nav-user" import { useSettings } from "@/components/settings-provider" import { openFeedbackDialog } from "@/components/feedback-widget" @@ -51,6 +52,11 @@ const data = { url: "/dashboard/projects/demo-project-1/schedule", icon: IconCalendarStats, }, + { + title: "Conversations", + url: "/dashboard/conversations", + icon: IconMessageCircle, + }, { title: "Files", url: "/dashboard/files", @@ -96,6 +102,7 @@ function SidebarNav({ const { open: openSettings } = useSettings() const isExpanded = state === "expanded" const isFilesMode = pathname?.startsWith("/dashboard/files") + const isConversationsMode = pathname?.startsWith("/dashboard/conversations") const isProjectMode = /^\/dashboard\/projects\/[^/]+/.test( pathname ?? "" ) @@ -107,13 +114,15 @@ function SidebarNav({ // } // }, [isFilesMode, isProjectMode, isExpanded, setOpen]) - const showContext = isExpanded && (isFilesMode || isProjectMode) + const showContext = isExpanded && (isFilesMode || isProjectMode || isConversationsMode) const mode = showContext && isFilesMode ? "files" - : showContext && isProjectMode - ? "projects" - : "main" + : showContext && isConversationsMode + ? "conversations" + : showContext && isProjectMode + ? "projects" + : "main" const secondaryItems = [ ...data.navSecondary.map((item) => @@ -130,6 +139,11 @@ function SidebarNav({ )} + {mode === "conversations" && ( + + + + )} {mode === "projects" && } {mode === "main" && ( <> diff --git a/src/components/conversations/channel-header.tsx b/src/components/conversations/channel-header.tsx new file mode 100644 index 0000000..956bcfb --- /dev/null +++ b/src/components/conversations/channel-header.tsx @@ -0,0 +1,44 @@ +"use client" + +import { IconHash, IconSearch, IconPin, IconUsers } from "@tabler/icons-react" +import { Button } from "@/components/ui/button" + +type ChannelHeaderProps = { + readonly name: string + readonly description?: string + readonly memberCount: number +} + +export function ChannelHeader({ + name, + description, + memberCount, +}: ChannelHeaderProps) { + return ( +
+
+ +
+

{name}

+ {description && ( +

+ {description} +

+ )} +
+
+
+
+ + {memberCount} +
+ + +
+
+ ) +} diff --git a/src/components/conversations/create-channel-button.tsx b/src/components/conversations/create-channel-button.tsx new file mode 100644 index 0000000..a2650f2 --- /dev/null +++ b/src/components/conversations/create-channel-button.tsx @@ -0,0 +1,20 @@ +"use client" + +import * as React from "react" +import { Plus } from "lucide-react" +import { Button } from "@/components/ui/button" +import { CreateChannelDialog } from "./create-channel-dialog" + +export function CreateChannelButton() { + const [open, setOpen] = React.useState(false) + + return ( + <> + + + + ) +} diff --git a/src/components/conversations/create-channel-dialog.tsx b/src/components/conversations/create-channel-dialog.tsx new file mode 100644 index 0000000..5feed8c --- /dev/null +++ b/src/components/conversations/create-channel-dialog.tsx @@ -0,0 +1,347 @@ +"use client" + +import * as React from "react" +import { useRouter } from "next/navigation" +import { useForm } from "react-hook-form" +import { z } from "zod/v4" +import { zodResolver } from "@hookform/resolvers/zod" +import { Hash, Volume2, Megaphone, Lock, FolderOpen } from "lucide-react" +import { + Dialog, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Input } from "@/components/ui/input" +import { Button } from "@/components/ui/button" +import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group" +import { Switch } from "@/components/ui/switch" +import { cn } from "@/lib/utils" +import { createChannel } from "@/app/actions/conversations" +import { listCategories } from "@/app/actions/channel-categories" + +const channelSchema = z.object({ + name: z + .string() + .min(2, "Name must be at least 2 characters") + .max(50, "Name must be less than 50 characters") + .regex( + /^[a-z0-9-]+$/, + "Lowercase letters, numbers, and hyphens only" + ), + type: z.enum(["text", "voice", "announcement"]), + categoryId: z.string().nullable(), + isPrivate: z.boolean(), +}) + +type ChannelFormData = z.infer + +const channelTypes = [ + { + value: "text", + label: "Text", + icon: Hash, + description: "Send messages, images, GIFs, and files", + disabled: false, + }, + { + value: "voice", + label: "Voice", + icon: Volume2, + description: "Hang out together with voice, video, and screen share", + disabled: true, + }, + { + value: "announcement", + label: "Announcement", + icon: Megaphone, + description: "Important updates that only admins can post", + disabled: false, + }, +] as const + +type CategoryData = { + readonly id: string + readonly name: string + readonly position: number + readonly channelCount: number +} + +type CreateChannelDialogProps = { + readonly open: boolean + readonly onOpenChange: (open: boolean) => void +} + +export function CreateChannelDialog({ + open, + onOpenChange, +}: CreateChannelDialogProps) { + const router = useRouter() + const [isSubmitting, setIsSubmitting] = React.useState(false) + const [categories, setCategories] = React.useState([]) + const [loadingCategories, setLoadingCategories] = React.useState(true) + + React.useEffect(() => { + async function loadCategories() { + if (open) { + const result = await listCategories() + if (result.success && result.data) { + setCategories( + result.data.map((cat) => ({ + id: cat.id, + name: cat.name, + position: cat.position, + channelCount: cat.channelCount, + })) + ) + } + setLoadingCategories(false) + } + } + loadCategories() + }, [open]) + + const form = useForm({ + resolver: zodResolver(channelSchema), + defaultValues: { + name: "", + type: "text", + categoryId: null, + isPrivate: false, + }, + }) + + const onSubmit = async (data: ChannelFormData) => { + setIsSubmitting(true) + + const result = await createChannel({ + name: data.name, + type: data.type, + categoryId: data.categoryId, + isPrivate: data.isPrivate, + }) + + if (result.success && result.data) { + form.reset() + onOpenChange(false) + router.push(`/dashboard/conversations/${result.data.channelId}`) + router.refresh() + } else { + form.setError("root", { + message: result.error ?? "Failed to create channel", + }) + } + + setIsSubmitting(false) + } + + return ( + + + + Create Channel + + +
+ + {/* channel type - vertical radio cards */} + ( + + + Channel Type + + + + {channelTypes.map((ct) => { + const Icon = ct.icon + const selected = field.value === ct.value + return ( + + ) + })} + + + + + )} + /> + + {/* channel name with # prefix */} + ( + + + Channel Name + + +
+ + + field.onChange( + e.target.value + .toLowerCase() + .replace(/[^a-z0-9-]/g, "-") + ) + } + /> +
+
+ +
+ )} + /> + + {/* category selector */} + ( + + + Category + + + + + + + )} + /> + + {/* private toggle */} + ( + + +
+ + Private Channel + +

+ Only selected members and roles will be able + to view this channel. +

+
+ + + +
+ )} + /> + + {form.formState.errors.root && ( +

+ {form.formState.errors.root.message} +

+ )} + + + + + + + +
+
+ ) +} diff --git a/src/components/conversations/member-sidebar.tsx b/src/components/conversations/member-sidebar.tsx new file mode 100644 index 0000000..484f00c --- /dev/null +++ b/src/components/conversations/member-sidebar.tsx @@ -0,0 +1,301 @@ +"use client" + +import * as React from "react" +import { IconX, IconCrown, IconShield } from "@tabler/icons-react" +import { getChannelMembersWithPresence } from "@/app/actions/presence" +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { ScrollArea } from "@/components/ui/scroll-area" +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, +} from "@/components/ui/sheet" +import { cn } from "@/lib/utils" + +type MemberWithPresence = { + readonly id: string + readonly displayName: string | null + readonly avatarUrl: string | null + readonly role: string + readonly status: string + readonly statusMessage: string | null +} + +type MemberSidebarProps = { + channelId: string + isOpen: boolean + onClose: () => void +} + +type MemberGroupProps = { + readonly title: string + readonly members: readonly MemberWithPresence[] + readonly statusColor: string +} + +function getStatusColor(status: string): string { + switch (status) { + case "online": + return "bg-green-500" + case "idle": + return "bg-yellow-500" + case "dnd": + return "bg-red-500" + default: + return "bg-gray-400" + } +} + +function getInitials(name: string | null): string { + if (!name) return "?" + const parts = name.trim().split(/\s+/) + if (parts.length === 1) { + return parts[0].charAt(0).toUpperCase() + } + return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase() +} + +function getRoleBadgeVariant( + role: string +): "default" | "secondary" | "outline" { + switch (role) { + case "owner": + return "default" + case "moderator": + return "secondary" + default: + return "outline" + } +} + +function RoleIcon({ role }: { readonly role: string }) { + if (role === "owner") { + return + } + if (role === "moderator") { + return + } + return null +} + +function MemberGroup({ title, members, statusColor }: MemberGroupProps) { + if (members.length === 0) return null + + return ( +
+

+ {title} — {members.length} +

+
    + {members.map((member) => ( +
  • + +
  • + ))} +
+
+ ) +} + +function MemberListContent({ + channelId, + isOpen, +}: { + readonly channelId: string + readonly isOpen: boolean +}) { + const [members, setMembers] = React.useState<{ + online: MemberWithPresence[] + idle: MemberWithPresence[] + dnd: MemberWithPresence[] + offline: MemberWithPresence[] + } | null>(null) + const [loading, setLoading] = React.useState(true) + const [error, setError] = React.useState(null) + + React.useEffect(() => { + let mounted = true + + async function fetchMembers() { + try { + const result = await getChannelMembersWithPresence(channelId) + if (!mounted) return + + if (result.success) { + setMembers(result.data) + } else { + setError(result.error) + } + } catch (err) { + if (!mounted) return + setError(err instanceof Error ? err.message : "Failed to load members") + } finally { + if (mounted) setLoading(false) + } + } + + fetchMembers() + + // 10-second polling interval when sidebar is open + let pollInterval: ReturnType | null = null + if (isOpen) { + pollInterval = setInterval(fetchMembers, 10_000) + } + + return () => { + mounted = false + if (pollInterval) { + clearInterval(pollInterval) + } + } + }, [channelId, isOpen]) + + if (loading) { + return ( +
+
+
+ ) + } + + if (error) { + return ( +
+ {error} +
+ ) + } + + if (!members) { + return ( +
+ No members found +
+ ) + } + + const totalMembers = + members.online.length + + members.idle.length + + members.dnd.length + + members.offline.length + + return ( + +
+
+ {totalMembers} member{totalMembers !== 1 ? "s" : ""} +
+ + + + + +
+
+ ) +} + +export function MemberSidebar({ + channelId, + isOpen, + onClose, +}: MemberSidebarProps) { + return ( + <> + {/* Desktop sidebar */} + + + {/* Mobile sheet */} + !open && onClose()}> + + +
+ Members +
+
+
+ +
+
+
+ + ) +} diff --git a/src/components/conversations/message-composer.tsx b/src/components/conversations/message-composer.tsx new file mode 100644 index 0000000..892c780 --- /dev/null +++ b/src/components/conversations/message-composer.tsx @@ -0,0 +1,225 @@ +"use client" + +import * as React from "react" +import { useEditor, EditorContent } from "@tiptap/react" +import StarterKit from "@tiptap/starter-kit" +import Placeholder from "@tiptap/extension-placeholder" +import Link from "@tiptap/extension-link" +import { Bold, Italic, Code, Link as LinkIcon, List, ListOrdered, Send, Paperclip, Smile } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Separator } from "@/components/ui/separator" +import { sendMessage } from "@/app/actions/chat-messages" +import { setTyping } from "@/app/actions/conversations-realtime" +import { useRouter } from "next/navigation" +import { cn } from "@/lib/utils" + +type MessageComposerProps = { + readonly channelId: string + readonly channelName: string + readonly threadId?: string + readonly placeholder?: string + readonly onSent?: () => void +} + +export function MessageComposer({ + channelId, + channelName, + threadId, + placeholder, + onSent, +}: MessageComposerProps) { + const router = useRouter() + const [isSending, setIsSending] = React.useState(false) + const [error, setError] = React.useState(null) + + // typing indicator - debounce to avoid spamming server + const lastTypingSentRef = React.useRef(0) + const TYPING_DEBOUNCE_MS = 3000 + + const sendTypingIndicator = React.useCallback(() => { + const now = Date.now() + if (now - lastTypingSentRef.current >= TYPING_DEBOUNCE_MS) { + lastTypingSentRef.current = now + setTyping(channelId).catch((err) => { + console.error("[MessageComposer] typing indicator error:", err) + }) + } + }, [channelId]) + + const editor = useEditor({ + immediatelyRender: false, + extensions: [ + StarterKit.configure({ + heading: false, + horizontalRule: false, + blockquote: false, + }), + Placeholder.configure({ + placeholder: placeholder ?? `Message #${channelName}...`, + }), + Link.configure({ + openOnClick: false, + HTMLAttributes: { + class: "text-primary underline underline-offset-2", + }, + }), + ], + editorProps: { + attributes: { + class: "prose prose-sm max-w-none focus:outline-none min-h-[80px] p-3", + }, + }, + onUpdate: () => { + setError(null) + sendTypingIndicator() + }, + }) + + const handleSend = React.useCallback(async () => { + if (!editor || isSending) return + + const content = editor.getText().trim() + if (!content) return + + setIsSending(true) + setError(null) + + try { + const result = await sendMessage({ + channelId, + content, + threadId, + }) + + if (result.success) { + editor.commands.clearContent() + router.refresh() + onSent?.() + } else { + setError(result.error ?? "Failed to send message") + } + } catch (err) { + setError(err instanceof Error ? err.message : "Failed to send message") + } finally { + setIsSending(false) + } + }, [editor, channelId, threadId, router, onSent, isSending]) + + React.useEffect(() => { + if (!editor) return + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Enter" && !event.shiftKey) { + event.preventDefault() + handleSend() + } + } + + const editorElement = editor.view.dom + editorElement.addEventListener("keydown", handleKeyDown) + + return () => { + editorElement.removeEventListener("keydown", handleKeyDown) + } + }, [editor, handleSend]) + + return ( +
+
+ + + {editor && ( +
+
+ + + + + + + + + + +
+ + +
+ )} +
+ + {error && ( +

{error}

+ )} + +

+ Enter to send,{" "} + Shift+Enter for new line +

+
+ ) +} diff --git a/src/components/conversations/message-item.tsx b/src/components/conversations/message-item.tsx new file mode 100644 index 0000000..fc9e6e4 --- /dev/null +++ b/src/components/conversations/message-item.tsx @@ -0,0 +1,255 @@ +"use client" + +import * as React from "react" +import { formatDistanceToNow, format, parseISO } from "date-fns" +import { + MessageSquare, + Smile, + Edit2, + Trash2, + MoreHorizontal, +} from "lucide-react" +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Textarea } from "@/components/ui/textarea" +import { MarkdownRenderer } from "@/components/ui/markdown-renderer" +import { cn } from "@/lib/utils" +import { useConversations } from "@/app/dashboard/conversations/layout" +import { editMessage, deleteMessage, addReaction } from "@/app/actions/chat-messages" +import { useRouter } from "next/navigation" + +type MessageData = { + readonly id: string + readonly channelId: string + readonly threadId: string | null + readonly content: string + readonly contentHtml: string | null + readonly editedAt: string | null + readonly deletedAt: string | null + readonly isPinned: boolean + readonly replyCount: number + readonly lastReplyAt: string | null + readonly createdAt: string + readonly user: { + readonly id: string + readonly displayName: string | null + readonly email: string + readonly avatarUrl: string | null + } | null +} + +type MessageItemProps = { + readonly message: MessageData +} + +function getRoleBadge(email: string) { + if (email.includes("admin")) return { label: "Admin", variant: "destructive" as const } + if (email.includes("bot") || email.includes("claude")) return { label: "Bot", variant: "secondary" as const } + if (email.includes("office")) return { label: "Office", variant: "outline" as const } + if (email.includes("field")) return { label: "Field", variant: "default" as const } + if (email.includes("client")) return { label: "Client", variant: "secondary" as const } + return null +} + +function arePropsEqual(prev: MessageItemProps, next: MessageItemProps): boolean { + const prevMsg = prev.message + const nextMsg = next.message + return ( + prevMsg.id === nextMsg.id && + prevMsg.content === nextMsg.content && + prevMsg.editedAt === nextMsg.editedAt && + prevMsg.isPinned === nextMsg.isPinned && + prevMsg.replyCount === nextMsg.replyCount && + prevMsg.deletedAt === nextMsg.deletedAt + ) +} + +export const MessageItem = React.memo(function MessageItem({ message }: MessageItemProps) { + const [isEditing, setIsEditing] = React.useState(false) + const [editContent, setEditContent] = React.useState(message.content) + const [isHovered, setIsHovered] = React.useState(false) + const [isFocused, setIsFocused] = React.useState(false) + const { openThread } = useConversations() + const router = useRouter() + + const user = message.user + const displayName = user?.displayName ?? user?.email.split("@")[0] ?? "Unknown" + const avatarFallback = displayName.substring(0, 2).toUpperCase() + const roleBadge = user ? getRoleBadge(user.email) : null + + const timestamp = parseISO(message.createdAt) + const isRecent = Date.now() - timestamp.getTime() < 24 * 60 * 60 * 1000 + const timeDisplay = isRecent + ? formatDistanceToNow(timestamp, { addSuffix: true }) + : format(timestamp, "MMM d 'at' h:mm a") + + const handleEdit = async () => { + if (editContent.trim() === message.content) { + setIsEditing(false) + return + } + + const result = await editMessage(message.id, editContent.trim()) + if (result.success) { + setIsEditing(false) + router.refresh() + } + } + + const handleDelete = async () => { + if (!confirm("Delete this message?")) return + const result = await deleteMessage(message.id) + if (result.success) { + router.refresh() + } + } + + const handleReply = () => { + openThread(message.id, message) + } + + if (message.deletedAt) { + return ( +
+ + {avatarFallback} + +
+
+ {displayName} + {timeDisplay} +
+

[Message deleted]

+
+
+ ) + } + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + onFocus={() => setIsFocused(true)} + onBlur={() => setIsFocused(false)} + > + + {user?.avatarUrl && } + {avatarFallback} + + +
+
+ {displayName} + {roleBadge && ( + + {roleBadge.label} + + )} + {timeDisplay} + {message.editedAt && ( + (edited) + )} +
+ + {isEditing ? ( +
+