diff --git a/CLAUDE.md b/CLAUDE.md index 6b1e76cb..65a78b6d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -86,8 +86,11 @@ batch/ # CLI batch jobs for data processing └── provider/ # GitHub API integration db/ -├── schema.sql # Declarative schema (Atlas source) -├── migrations/ # Atlas versioned migrations +├── shared.sql # Shared DB declarative schema (Atlas source) +├── tenant.sql # Per-org tenant DB declarative schema (Atlas source) +├── migrations/ +│ ├── shared/ # Shared DB versioned migrations +│ └── tenant/ # Tenant DB versioned migrations └── seed.ts # Seed data ``` @@ -109,7 +112,7 @@ Atlas + Kysely setup: - **Kysely**: Runtime queries and type generation via kysely-codegen ```bash -# Generate new migration from schema.sql changes +# Generate new migration from shared.sql / tenant.sql changes pnpm db:migrate # Apply migrations to local database diff --git a/app/routes/$orgSlug/settings/data-management/index.tsx b/app/routes/$orgSlug/settings/data-management/index.tsx index f1dd2a90..442f57cf 100644 --- a/app/routes/$orgSlug/settings/data-management/index.tsx +++ b/app/routes/$orgSlug/settings/data-management/index.tsx @@ -11,13 +11,10 @@ import { Stack, } from '~/app/components/ui' import { Progress } from '~/app/components/ui/progress' -import { useTimezone } from '~/app/hooks/use-timezone' -import dayjs from '~/app/libs/dayjs' import { orgContext } from '~/app/middleware/context' import { durably } from '~/app/services/durably' import { durably as serverDurably } from '~/app/services/durably.server' import type { JobSteps } from '~/app/services/jobs/shared-steps.server' -import { getTenantDb } from '~/app/services/tenant-db.server' import ContentSection from '../+components/content-section' import type { Route } from './+types/index' @@ -28,20 +25,6 @@ export const handle = { }), } -export const loader = async ({ context }: Route.LoaderArgs) => { - const { organization } = context.get(orgContext) - - const tenantDb = getTenantDb(organization.id) - const organizationSetting = await tenantDb - .selectFrom('organizationSettings') - .select(['refreshRequestedAt']) - .executeTakeFirst() - - return { - refreshRequestedAt: organizationSetting?.refreshRequestedAt ?? null, - } -} - export const action = async ({ request, context }: Route.ActionArgs) => { const { organization: org } = context.get(orgContext) const formData = await request.formData() @@ -49,13 +32,21 @@ export const action = async ({ request, context }: Route.ActionArgs) => { return match(intent) .with('refresh', async () => { - const tenantDb = getTenantDb(org.id) - await tenantDb - .updateTable('organizationSettings') - .set({ refreshRequestedAt: new Date().toISOString() }) - .execute() - - return data({ intent: 'refresh' as const, ok: true }) + try { + const run = await serverDurably.jobs.crawl.trigger( + { organizationId: org.id, refresh: true }, + { + concurrencyKey: `crawl:${org.id}`, + labels: { organizationId: org.id }, + }, + ) + return data({ intent: 'refresh' as const, ok: true, runId: run.id }) + } catch { + return data( + { intent: 'refresh' as const, error: 'Failed to start refresh' }, + { status: 500 }, + ) + } }) .with('recalculate', async () => { const selectedSteps = formData.getAll('steps').map(String) @@ -75,73 +66,169 @@ export const action = async ({ request, context }: Route.ActionArgs) => { ) } - const run = await serverDurably.jobs.recalculate.trigger( - { organizationId: org.id, steps }, - { - concurrencyKey: `recalculate:${org.id}`, - labels: { organizationId: org.id }, - }, - ) - - return data({ - intent: 'recalculate' as const, - ok: true, - runId: run.id, - }) + try { + const run = await serverDurably.jobs.recalculate.trigger( + { organizationId: org.id, steps }, + { + concurrencyKey: `recalculate:${org.id}`, + labels: { organizationId: org.id }, + }, + ) + return data({ + intent: 'recalculate' as const, + ok: true, + runId: run.id, + }) + } catch { + return data( + { + intent: 'recalculate' as const, + error: 'Failed to start recalculation', + }, + { status: 500 }, + ) + } }) .otherwise(() => data({ error: 'Invalid intent' }, { status: 400 })) } -// --- Refresh Section --- +// --- Shared Run Status Alerts --- -function RefreshSection({ - refreshRequestedAt, +function RunStatusAlerts({ + label, + progress, + output, + runError, + triggerError, + isRunning, + isCompleted, + isFailed, }: { - refreshRequestedAt: string | null + label: string + progress: { message?: string; current?: number; total?: number } | null + output: { pullCount?: number } | null + runError: string | null + triggerError: string | null + isRunning: boolean + isCompleted: boolean + isFailed: boolean }) { - const timezone = useTimezone() + if (isRunning && progress) { + return ( + + +
+

{progress.message ?? 'Processing...'}

+ {progress.current != null && + progress.total != null && + progress.total > 0 && ( + + )} +
+
+
+ ) + } + + if (isRunning) { + return ( + + Starting {label}... + + ) + } + + const capitalizedLabel = label.charAt(0).toUpperCase() + label.slice(1) + + if (isCompleted) { + return ( + + + {capitalizedLabel} completed.{' '} + {output?.pullCount != null && `${output.pullCount} PRs updated.`} + + + ) + } + + if (isFailed) { + return ( + + + {capitalizedLabel} failed. {runError} + + + ) + } + + if (triggerError) { + return ( + + {triggerError} + + ) + } + + return null +} + +// --- Refresh Section --- + +function RefreshSection() { const fetcher = useFetcher() const isSubmitting = fetcher.state !== 'idle' - const isScheduled = - refreshRequestedAt != null || - (fetcher.data?.intent === 'refresh' && fetcher.data?.ok === true) + + const runId = + fetcher.data?.intent === 'refresh' && fetcher.data?.ok + ? fetcher.data.runId + : null + const { + progress, + output, + error: runError, + isPending, + isLeased, + isCompleted, + isFailed, + } = durably.crawl.useRun(runId) + + const isRunning = isPending || isLeased return (

- Schedule Full Refresh - {isScheduled && Scheduled} + Full Refresh + {isRunning && Running}

- Re-fetch all PR data from GitHub on the next hourly crawl. + Re-fetch all PR data from GitHub immediately.

-
- {isScheduled && refreshRequestedAt && ( - - - Scheduled at{' '} - {dayjs - .utc(refreshRequestedAt) - .tz(timezone) - .format('YYYY-MM-DD HH:mm:ss')} - . It will run on the next crawl job. - - - )} - {fetcher.data?.error && ( - - {fetcher.data.error} - - )} + +
) } @@ -155,7 +242,6 @@ function RecalculateSection() { const [exportData, setExportData] = useState(false) const noneSelected = !upsert && !classify && !exportData - // After form submission, track the durably run via SSE const runId = fetcher.data?.intent === 'recalculate' && fetcher.data?.ok ? fetcher.data.runId @@ -237,53 +323,18 @@ function RecalculateSection() { - {/* Progress */} - {isRunning && progress && ( - - -
-

{progress.message ?? 'Processing...'}

- {progress.current != null && - progress.total != null && - progress.total > 0 && ( - - )} -
-
-
- )} - - {isRunning && !progress && ( - - Starting recalculation... - - )} - - {/* Success */} - {isCompleted && ( - - - Recalculation completed.{' '} - {output?.pullCount != null && `${output.pullCount} PRs updated.`} - - - )} - - {/* Error */} - {isFailed && ( - - Recalculation failed. {runError} - - )} - - {fetcher.data?.intent === 'recalculate' && fetcher.data?.error && ( - - {fetcher.data.error} - - )} + ) } @@ -333,7 +384,6 @@ function ExportDataSection({ orgSlug }: { orgSlug: string }) { // --- Page --- export default function DataManagementPage({ - loaderData: { refreshRequestedAt }, params: { orgSlug }, }: Route.ComponentProps) { return ( @@ -342,7 +392,7 @@ export default function DataManagementPage({ desc="Manage data refresh and recalculation for this organization." > - + diff --git a/app/services/jobs/crawl.server.ts b/app/services/jobs/crawl.server.ts index d694e99d..e7b18e1f 100644 --- a/app/services/jobs/crawl.server.ts +++ b/app/services/jobs/crawl.server.ts @@ -1,7 +1,6 @@ import { defineJob } from '@coji/durably' import { z } from 'zod' import { clearOrgCache } from '~/app/services/cache.server' -import { getTenantDb } from '~/app/services/tenant-db.server' import type { OrganizationId } from '~/app/types/organization' import { getOrganization } from '~/batch/db/queries' import { createFetcher } from '~/batch/github/fetcher' @@ -151,17 +150,6 @@ export const crawlJob = defineJob({ return { fetchedRepos: repoCount, pullCount: 0 } } - // Consume refresh flag - if (input.refresh) { - await step.run('consume-refresh-flag', async () => { - const tenantDb = getTenantDb(orgId) - await tenantDb - .updateTable('organizationSettings') - .set({ refreshRequestedAt: null }) - .execute() - }) - } - // Steps 3-7: Analyze → Upsert → Classify → Export → Finalize const { pullCount } = await analyzeAndFinalizeSteps( step, diff --git a/app/services/tenant-type.ts b/app/services/tenant-type.ts index ae69b36e..4f072942 100644 --- a/app/services/tenant-type.ts +++ b/app/services/tenant-type.ts @@ -74,7 +74,6 @@ export interface OrganizationSettings { id: string; isActive: Generated<0 | 1>; language: Generated<"en" | "ja">; - refreshRequestedAt: string | null; releaseDetectionKey: Generated; releaseDetectionMethod: Generated<"branch" | "tags">; timezone: Generated; diff --git a/batch/db/queries.ts b/batch/db/queries.ts index 7594a671..b0452661 100644 --- a/batch/db/queries.ts +++ b/batch/db/queries.ts @@ -23,7 +23,6 @@ async function getTenantData(organizationId: OrganizationId) { 'releaseDetectionKey', 'isActive', 'excludedUsers', - 'refreshRequestedAt', ]) .executeTakeFirst(), tenantDb diff --git a/batch/job-scheduler.ts b/batch/job-scheduler.ts index 1baeba72..e3ae9d38 100644 --- a/batch/job-scheduler.ts +++ b/batch/job-scheduler.ts @@ -24,11 +24,10 @@ export const createJobScheduler = () => { if (!org.integration) continue const orgId = org.id as OrganizationId - const refresh = org.organizationSetting.refreshRequestedAt != null try { await durably.jobs.crawl.trigger( - { organizationId: orgId, refresh }, + { organizationId: orgId, refresh: false }, { concurrencyKey: `crawl:${orgId}`, labels: { organizationId: orgId }, diff --git a/db/migrations/tenant/20260317120000_drop_refresh_requested_at.sql b/db/migrations/tenant/20260317120000_drop_refresh_requested_at.sql new file mode 100644 index 00000000..bcdcdf0a --- /dev/null +++ b/db/migrations/tenant/20260317120000_drop_refresh_requested_at.sql @@ -0,0 +1,3 @@ +-- Drop column "refresh_requested_at" from table: "organization_settings" +-- Refresh scheduling is now handled directly via durably jobs instead of DB flag polling +ALTER TABLE `organization_settings` DROP COLUMN `refresh_requested_at`; diff --git a/db/migrations/tenant/atlas.sum b/db/migrations/tenant/atlas.sum index 58fa97bc..6d774d25 100644 --- a/db/migrations/tenant/atlas.sum +++ b/db/migrations/tenant/atlas.sum @@ -1,4 +1,4 @@ -h1:d+Mk98SCf1068WLcRDzr1IE3NMfkpjfAcxDaVbGKYFE= +h1:57BFTZmY6MHNsyxYn2I1qlUPbtDkdrAtNqeUx2hu+Sg= 20260226112249_initial_tenant.sql h1:dIhBg2gzyh+ZjLzPXdHYafd5e62yIEjk1eFlllEyYX0= 20260226233619_add_teams.sql h1:n8MRMUA4BgeXYEnL9HJPc8mnXh8lqIfrCcdYtFFoWqw= 20260227163239.sql h1:ENMZUW7zHK8UjG2TdYlBOZSVPPUCXftIw5U5k2C54oo= @@ -17,3 +17,4 @@ h1:d+Mk98SCf1068WLcRDzr1IE3NMfkpjfAcxDaVbGKYFE= 20260313141746.sql h1:6TvDF7n2gonCaV9ueQSGpYnbaZgltFIeUZEnA/mZbHk= 20260315050936.sql h1:/o/ku2qrlT14mxxhETs26eHguobD5wPYES2khLSN2wA= 20260315120000_add_language.sql h1:O1oFQ+aUAI9+uGdIuhjEV9bM8ImXKMMwRQAw3vYhcVM= +20260317120000_drop_refresh_requested_at.sql h1:R4jHtMkCpdY09orFA4RPvtLeUJ2Z7S4WPMmg/bDJuGg= diff --git a/db/schema.sql b/db/schema.sql deleted file mode 100644 index 5a39c6d4..00000000 --- a/db/schema.sql +++ /dev/null @@ -1,258 +0,0 @@ --- Create "users" table -CREATE TABLE `users` ( - `id` text NOT NULL, - `name` text NOT NULL, - `email` text NOT NULL, - `email_verified` boolean NOT NULL, - `image` text NULL, - `role` text NOT NULL, - `banned` boolean NULL, - `ban_reason` text NULL, - `ban_expires` datetime NULL, - `created_at` datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), - `updated_at` datetime NOT NULL, - PRIMARY KEY (`id`) -); --- Create index "users_email_key" to table: "users" -CREATE UNIQUE INDEX `users_email_key` ON `users` (`email`); --- Create "organizations" table -CREATE TABLE `organizations` ( - `id` text NOT NULL, - `name` text NOT NULL, - `slug` text NOT NULL, - `logo` text NULL, - `created_at` datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), - `metadata` text NULL, - PRIMARY KEY (`id`) -); --- Create index "organizations_slug_key" to table: "organizations" -CREATE UNIQUE INDEX `organizations_slug_key` ON `organizations` (`slug`); --- Create "sessions" table -CREATE TABLE `sessions` ( - `id` text NOT NULL, - `expires_at` datetime NOT NULL, - `token` text NOT NULL, - `created_at` datetime NOT NULL, - `updated_at` datetime NOT NULL, - `ip_address` text NULL, - `user_agent` text NULL, - `user_id` text NOT NULL, - `impersonated_by` text NULL, - `active_organization_id` text NULL, - `active_team_id` text NULL, - PRIMARY KEY (`id`), - CONSTRAINT `sessions_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "sessions_token_key" to table: "sessions" -CREATE UNIQUE INDEX `sessions_token_key` ON `sessions` (`token`); --- Create "accounts" table -CREATE TABLE `accounts` ( - `id` text NOT NULL, - `account_id` text NOT NULL, - `provider_id` text NOT NULL, - `user_id` text NOT NULL, - `access_token` text NULL, - `refresh_token` text NULL, - `id_token` text NULL, - `access_token_expires_at` datetime NULL, - `refresh_token_expires_at` datetime NULL, - `scope` text NULL, - `password` text NULL, - `created_at` datetime NOT NULL, - `updated_at` datetime NOT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `accounts_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create "verifications" table -CREATE TABLE `verifications` ( - `id` text NOT NULL, - `identifier` text NOT NULL, - `value` text NOT NULL, - `expires_at` datetime NOT NULL, - `created_at` datetime NULL, - `updated_at` datetime NULL, - PRIMARY KEY (`id`) -); --- Create "members" table -CREATE TABLE `members` ( - `id` text NOT NULL, - `organization_id` text NOT NULL, - `user_id` text NOT NULL, - `role` text NOT NULL, - `created_at` datetime NOT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `members_organization_id_fkey` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT `members_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create "invitations" table -CREATE TABLE `invitations` ( - `id` text NOT NULL, - `organization_id` text NOT NULL, - `email` text NOT NULL, - `role` text NULL, - `status` text NOT NULL, - `expires_at` datetime NOT NULL, - `inviter_id` text NOT NULL, - `created_at` datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), - `team_id` text NULL, - PRIMARY KEY (`id`), - CONSTRAINT `invitations_organization_id_fkey` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT `invitations_inviter_id_fkey` FOREIGN KEY (`inviter_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT `invitations_team_id_fkey` FOREIGN KEY (`team_id`) REFERENCES `teams` (`id`) ON UPDATE CASCADE ON DELETE SET NULL -); --- Create "organization_settings" table -CREATE TABLE `organization_settings` ( - `id` text NOT NULL, - `organization_id` text NOT NULL, - `release_detection_method` text NOT NULL DEFAULT 'branch', - `release_detection_key` text NOT NULL DEFAULT 'production', - `is_active` boolean NOT NULL DEFAULT true, - `excluded_users` text NOT NULL DEFAULT '', - `refresh_requested_at` datetime NULL, - `updated_at` datetime NOT NULL, - `created_at` datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), - PRIMARY KEY (`id`), - CONSTRAINT `organization_settings_organization_id_fkey` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "organization_settings_organization_id_key" to table: "organization_settings" -CREATE UNIQUE INDEX `organization_settings_organization_id_key` ON `organization_settings` (`organization_id`); --- Create "export_settings" table -CREATE TABLE `export_settings` ( - `id` text NOT NULL, - `sheet_id` text NOT NULL, - `client_email` text NOT NULL, - `private_key` text NOT NULL, - `updated_at` datetime NOT NULL, - `created_at` datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), - `organization_id` text NOT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `export_settings_organization_id_fkey` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "export_settings_organization_id_key" to table: "export_settings" -CREATE UNIQUE INDEX `export_settings_organization_id_key` ON `export_settings` (`organization_id`); --- Create "integrations" table -CREATE TABLE `integrations` ( - `id` text NOT NULL, - `provider` text NOT NULL, - `method` text NOT NULL, - `private_token` text NULL, - `organization_id` text NOT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `integrations_organization_id_fkey` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "integrations_organization_id_key" to table: "integrations" -CREATE UNIQUE INDEX `integrations_organization_id_key` ON `integrations` (`organization_id`); --- Create index "integrations_organization_id_idx" to table: "integrations" -CREATE INDEX `integrations_organization_id_idx` ON `integrations` (`organization_id`); --- Create "repositories" table -CREATE TABLE `repositories` ( - `id` text NOT NULL, - `integration_id` text NOT NULL, - `provider` text NOT NULL, - `owner` text NOT NULL, - `repo` text NOT NULL, - `release_detection_method` text NOT NULL DEFAULT 'branch', - `release_detection_key` text NOT NULL DEFAULT 'production', - `updated_at` datetime NOT NULL, - `created_at` datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), - `organization_id` text NOT NULL, - PRIMARY KEY (`id`), - CONSTRAINT `repositories_integration_id_fkey` FOREIGN KEY (`integration_id`) REFERENCES `integrations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT `repositories_organization_id_fkey` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "repositories_organization_id_idx" to table: "repositories" -CREATE INDEX `repositories_organization_id_idx` ON `repositories` (`organization_id`); --- Create index "repositories_organization_id_integration_id_owner_repo_key" to table: "repositories" -CREATE UNIQUE INDEX `repositories_organization_id_integration_id_owner_repo_key` ON `repositories` (`organization_id`, `integration_id`, `owner`, `repo`); --- Create "pull_requests" table -CREATE TABLE `pull_requests` ( - `repo` text NOT NULL, - `number` integer NOT NULL, - `source_branch` text NOT NULL, - `target_branch` text NOT NULL, - `state` text NOT NULL, - `author` text NOT NULL, - `title` text NOT NULL, - `url` text NOT NULL, - `first_committed_at` text NULL, - `pull_request_created_at` text NOT NULL, - `first_reviewed_at` text NULL, - `merged_at` text NULL, - `released_at` text NULL, - `coding_time` real NULL, - `pickup_time` real NULL, - `review_time` real NULL, - `deploy_time` real NULL, - `total_time` real NULL, - `repository_id` text NOT NULL, - `updated_at` text NULL, - `additions` integer NULL, - `deletions` integer NULL, - `changed_files` integer NULL, - PRIMARY KEY (`number`, `repository_id`), - CONSTRAINT `pull_requests_repository_id_fkey` FOREIGN KEY (`repository_id`) REFERENCES `repositories` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create "pull_request_reviews" table -CREATE TABLE `pull_request_reviews` ( - `id` text NOT NULL, - `pull_request_number` integer NOT NULL, - `repository_id` text NOT NULL, - `reviewer` text NOT NULL, - `state` text NOT NULL, - `submitted_at` text NOT NULL, - `url` text NOT NULL, - CONSTRAINT `pull_request_reviews_pk` PRIMARY KEY (`id`), - CONSTRAINT `pull_request_reviews_pr_fkey` FOREIGN KEY (`pull_request_number`, `repository_id`) REFERENCES `pull_requests` (`number`, `repository_id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "pull_request_reviews_pr_idx" to table: "pull_request_reviews" -CREATE INDEX `pull_request_reviews_pr_idx` ON `pull_request_reviews` (`pull_request_number`, `repository_id`); --- Create "pull_request_reviewers" table -CREATE TABLE `pull_request_reviewers` ( - `pull_request_number` integer NOT NULL, - `repository_id` text NOT NULL, - `reviewer` text NOT NULL, - `requested_at` text NULL, - CONSTRAINT `pull_request_reviewers_pk` PRIMARY KEY (`pull_request_number`, `repository_id`, `reviewer`), - CONSTRAINT `pull_request_reviewers_pr_fkey` FOREIGN KEY (`pull_request_number`, `repository_id`) REFERENCES `pull_requests` (`number`, `repository_id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create "company_github_users" table -CREATE TABLE `company_github_users` ( - `user_id` text NULL, - `login` text NOT NULL, - `name` text NULL, - `email` text NULL, - `display_name` text NOT NULL, - `updated_at` datetime NOT NULL, - `created_at` datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), - `organization_id` text NOT NULL, - PRIMARY KEY (`login`, `organization_id`), - CONSTRAINT `company_github_users_organization_id_fkey` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "company_github_users_organization_id_idx" to table: "company_github_users" -CREATE INDEX `company_github_users_organization_id_idx` ON `company_github_users` (`organization_id`); --- Create "teams" table -CREATE TABLE `teams` ( - `id` text NOT NULL, - `name` text NOT NULL, - `organization_id` text NOT NULL, - `created_at` date NOT NULL, - `updated_at` date NULL, - PRIMARY KEY (`id`), - CONSTRAINT `teams_organization_id_fkey` FOREIGN KEY (`organization_id`) REFERENCES `organizations` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "teams_organization_id_idx" to table: "teams" -CREATE INDEX `teams_organization_id_idx` ON `teams` (`organization_id`); --- Create "team_members" table -CREATE TABLE `team_members` ( - `id` text NOT NULL, - `team_id` text NOT NULL, - `user_id` text NOT NULL, - `created_at` date NULL, - PRIMARY KEY (`id`), - CONSTRAINT `team_members_user_id_fkey` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE ON DELETE CASCADE, - CONSTRAINT `team_members_team_id_fkey` FOREIGN KEY (`team_id`) REFERENCES `teams` (`id`) ON UPDATE CASCADE ON DELETE CASCADE -); --- Create index "team_members_team_id_idx" to table: "team_members" -CREATE INDEX `team_members_team_id_idx` ON `team_members` (`team_id`); --- Create index "team_members_user_id_idx" to table: "team_members" -CREATE INDEX `team_members_user_id_idx` ON `team_members` (`user_id`); diff --git a/db/tenant.sql b/db/tenant.sql index 3cc02470..84404135 100644 --- a/db/tenant.sql +++ b/db/tenant.sql @@ -6,7 +6,6 @@ CREATE TABLE `organization_settings` ( `is_active` boolean NOT NULL DEFAULT true, `excluded_users` text NOT NULL DEFAULT '', `timezone` text NOT NULL DEFAULT 'Asia/Tokyo', - `refresh_requested_at` datetime NULL, `updated_at` datetime NOT NULL, `created_at` datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), PRIMARY KEY (`id`)