Migrate database upon successful merges to main (#72)

This commit is contained in:
Chris Estreich 2025-06-03 09:51:05 -07:00 committed by GitHub
parent 25f003c459
commit fd132d5d71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 762 additions and 43 deletions

View file

@ -18,27 +18,22 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Build Next.js
run: npx dotenvx run -f .env.test -- pnpm build
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15.4
@ -53,75 +48,63 @@ jobs:
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- name: Install dependencies
run: pnpm install
# - name: Build Next.js for E2E tests
# run: pnpm build
# env:
# SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
- name: Run linter
run: pnpm lint
- name: Run type checker
run: pnpm check-types
- name: Setup database schema
run: pnpm db:test:push --force
env:
DATABASE_URL: postgres://postgres:password@localhost:5432/roo_code_test
- name: Run unit tests
run: pnpm test # -- --coverage
run: pnpm test
env:
DATABASE_URL: postgres://postgres:password@localhost:5432/roo_code_test
# - name: Upload coverage reports to Codecov
# uses: codecov/codecov-action@v4
# env:
# CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
# - name: Install Playwright (used for Storybook and E2E tests)
# run: npx playwright install --with-deps
# - name: Run storybook tests
# run: pnpm test-storybook:ci
# - name: Run E2E tests
# run: npx percy exec -- npm run test:e2e
# env:
# PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
# CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
# - uses: actions/upload-artifact@v4
# if: always()
# with:
# name: test-results
# path: test-results/
# retention-days: 7
check:
needs: [build, test]
runs-on: ubuntu-latest
steps:
- name: NO-OP
run: echo "All checks passed."
db_migrate:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
needs: [build, test]
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: ${{ env.PNPM_VERSION }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: pnpm
- name: Install dependencies
run: pnpm install
- name: Migrate production database
run: npx drizzle-kit migrate
env:
DATABASE_URL: ${{ secrets.PRODUCTION_DATABASE_URL }}

View file

@ -3,4 +3,5 @@ export enum AuditLogTargetType {
DEFAULT_PARAMETERS = 2,
MEMBER_CHANGE = 3, // TODO: Currently no logs of this type are collected.
CLOUD_SETTINGS = 4,
TASK_SHARE = 5,
}

View file

@ -0,0 +1,19 @@
CREATE TABLE "task_shares" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"task_id" text NOT NULL,
"organization_id" text NOT NULL,
"created_by_user_id" text NOT NULL,
"share_token" text NOT NULL,
"expires_at" timestamp,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL,
CONSTRAINT "task_shares_share_token_unique" UNIQUE("share_token")
);
--> statement-breakpoint
ALTER TABLE "task_shares" ADD CONSTRAINT "task_shares_organization_id_organizations_id_fk" FOREIGN KEY ("organization_id") REFERENCES "public"."organizations"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "task_shares" ADD CONSTRAINT "task_shares_created_by_user_id_users_id_fk" FOREIGN KEY ("created_by_user_id") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "task_shares_share_token_idx" ON "task_shares" USING btree ("share_token");--> statement-breakpoint
CREATE INDEX "task_shares_task_id_idx" ON "task_shares" USING btree ("task_id");--> statement-breakpoint
CREATE INDEX "task_shares_org_id_idx" ON "task_shares" USING btree ("organization_id");--> statement-breakpoint
CREATE INDEX "task_shares_expires_at_idx" ON "task_shares" USING btree ("expires_at");--> statement-breakpoint
CREATE INDEX "task_shares_created_by_user_id_idx" ON "task_shares" USING btree ("created_by_user_id");

View file

@ -0,0 +1,668 @@
{
"id": "9cf3a792-c153-4ad4-a3f5-0f8054abf28d",
"prevId": "5c95f3bf-5048-4ba1-9f71-2110879fc161",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.audit_logs": {
"name": "audit_logs",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"organization_id": {
"name": "organization_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"target_type": {
"name": "target_type",
"type": "integer",
"primaryKey": false,
"notNull": true
},
"target_id": {
"name": "target_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"new_value": {
"name": "new_value",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"description": {
"name": "description",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"audit_logs_user_id_idx": {
"name": "audit_logs_user_id_idx",
"columns": [
{
"expression": "user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"audit_logs_organization_id_idx": {
"name": "audit_logs_organization_id_idx",
"columns": [
{
"expression": "organization_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"audit_logs_target_idx": {
"name": "audit_logs_target_idx",
"columns": [
{
"expression": "target_type",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "target_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"audit_logs_created_at_idx": {
"name": "audit_logs_created_at_idx",
"columns": [
{
"expression": "created_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"audit_logs_user_id_users_id_fk": {
"name": "audit_logs_user_id_users_id_fk",
"tableFrom": "audit_logs",
"tableTo": "users",
"columnsFrom": ["user_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
},
"audit_logs_organization_id_organizations_id_fk": {
"name": "audit_logs_organization_id_organizations_id_fk",
"tableFrom": "audit_logs",
"tableTo": "organizations",
"columnsFrom": ["organization_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.organization_settings": {
"name": "organization_settings",
"schema": "",
"columns": {
"organization_id": {
"name": "organization_id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"version": {
"name": "version",
"type": "integer",
"primaryKey": false,
"notNull": true,
"default": 1
},
"cloud_settings": {
"name": "cloud_settings",
"type": "jsonb",
"primaryKey": false,
"notNull": true,
"default": "'{}'::jsonb"
},
"default_settings": {
"name": "default_settings",
"type": "jsonb",
"primaryKey": false,
"notNull": true,
"default": "'{}'::jsonb"
},
"allow_list": {
"name": "allow_list",
"type": "jsonb",
"primaryKey": false,
"notNull": true,
"default": "'{\"allowAll\":true,\"providers\":{}}'::jsonb"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"organization_settings_created_at_idx": {
"name": "organization_settings_created_at_idx",
"columns": [
{
"expression": "created_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"organization_settings_organization_id_organizations_id_fk": {
"name": "organization_settings_organization_id_organizations_id_fk",
"tableFrom": "organization_settings",
"tableTo": "organizations",
"columnsFrom": ["organization_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.organizations": {
"name": "organizations",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"slug": {
"name": "slug",
"type": "text",
"primaryKey": false,
"notNull": true
},
"image_url": {
"name": "image_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"entity": {
"name": "entity",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"last_sync_at": {
"name": "last_sync_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"organizations_slug_idx": {
"name": "organizations_slug_idx",
"columns": [
{
"expression": "slug",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"organizations_created_at_idx": {
"name": "organizations_created_at_idx",
"columns": [
{
"expression": "created_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.task_shares": {
"name": "task_shares",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"task_id": {
"name": "task_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"organization_id": {
"name": "organization_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"created_by_user_id": {
"name": "created_by_user_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"share_token": {
"name": "share_token",
"type": "text",
"primaryKey": false,
"notNull": true
},
"expires_at": {
"name": "expires_at",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"task_shares_share_token_idx": {
"name": "task_shares_share_token_idx",
"columns": [
{
"expression": "share_token",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"task_shares_task_id_idx": {
"name": "task_shares_task_id_idx",
"columns": [
{
"expression": "task_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"task_shares_org_id_idx": {
"name": "task_shares_org_id_idx",
"columns": [
{
"expression": "organization_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"task_shares_expires_at_idx": {
"name": "task_shares_expires_at_idx",
"columns": [
{
"expression": "expires_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"task_shares_created_by_user_id_idx": {
"name": "task_shares_created_by_user_id_idx",
"columns": [
{
"expression": "created_by_user_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"task_shares_organization_id_organizations_id_fk": {
"name": "task_shares_organization_id_organizations_id_fk",
"tableFrom": "task_shares",
"tableTo": "organizations",
"columnsFrom": ["organization_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
},
"task_shares_created_by_user_id_users_id_fk": {
"name": "task_shares_created_by_user_id_users_id_fk",
"tableFrom": "task_shares",
"tableTo": "users",
"columnsFrom": ["created_by_user_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"task_shares_share_token_unique": {
"name": "task_shares_share_token_unique",
"nullsNotDistinct": false,
"columns": ["share_token"]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "text",
"primaryKey": true,
"notNull": true
},
"organization_id": {
"name": "organization_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"organization_role": {
"name": "organization_role",
"type": "text",
"primaryKey": false,
"notNull": false
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"image_url": {
"name": "image_url",
"type": "text",
"primaryKey": false,
"notNull": true
},
"entity": {
"name": "entity",
"type": "jsonb",
"primaryKey": false,
"notNull": true
},
"last_sync_at": {
"name": "last_sync_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {
"users_organization_id_idx": {
"name": "users_organization_id_idx",
"columns": [
{
"expression": "organization_id",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"users_organization_role_idx": {
"name": "users_organization_role_idx",
"columns": [
{
"expression": "organization_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "organization_role",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"users_email_idx": {
"name": "users_email_idx",
"columns": [
{
"expression": "email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"users_created_at_idx": {
"name": "users_created_at_idx",
"columns": [
{
"expression": "created_at",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {
"users_organization_id_organizations_id_fk": {
"name": "users_organization_id_organizations_id_fk",
"tableFrom": "users",
"tableTo": "organizations",
"columnsFrom": ["organization_id"],
"columnsTo": ["id"],
"onDelete": "no action",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View file

@ -36,6 +36,13 @@
"when": 1748555520729,
"tag": "0004_overjoyed_bucky",
"breakpoints": true
},
{
"idx": 5,
"version": "7",
"when": 1748916566563,
"tag": "0005_watery_sphinx",
"breakpoints": true
}
]
}

View file

@ -76,6 +76,7 @@ export const orgs = pgTable(
export const orgsRelations = relations(orgs, ({ many, one }) => ({
users: many(users),
auditLogs: many(auditLogs),
taskShares: many(taskShares),
orgSettings: one(orgSettings, {
fields: [orgs.id],
references: [orgSettings.orgId],
@ -159,3 +160,43 @@ export const auditLogsRelations = relations(auditLogs, ({ one }) => ({
references: [orgs.id],
}),
}));
/**
* task_shares
*/
export const taskShares = pgTable(
'task_shares',
{
id: uuid('id').primaryKey().defaultRandom(),
taskId: text('task_id').notNull(),
orgId: text('organization_id')
.notNull()
.references(() => orgs.id),
createdByUserId: text('created_by_user_id')
.notNull()
.references(() => users.id),
shareToken: text('share_token').notNull().unique(),
expiresAt: timestamp('expires_at'),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
},
(table) => [
index('task_shares_share_token_idx').on(table.shareToken),
index('task_shares_task_id_idx').on(table.taskId),
index('task_shares_org_id_idx').on(table.orgId),
index('task_shares_expires_at_idx').on(table.expiresAt),
index('task_shares_created_by_user_id_idx').on(table.createdByUserId),
],
);
export const taskSharesRelations = relations(taskShares, ({ one }) => ({
org: one(orgs, {
fields: [taskShares.orgId],
references: [orgs.id],
}),
createdByUser: one(users, {
fields: [taskShares.createdByUserId],
references: [users.id],
}),
}));