diff --git a/.github/workflows/database_cleanup.yml b/.github/workflows/database_cleanup.yml index f7a9e26..f2fe1d1 100644 --- a/.github/workflows/database_cleanup.yml +++ b/.github/workflows/database_cleanup.yml @@ -3,6 +3,10 @@ on: pull_request: types: - closed + paths-ignore: + - .gitignore + - .github/** + - README.md workflow_dispatch: inputs: pr_number: @@ -13,6 +17,8 @@ on: jobs: cleanup_database: runs-on: ubuntu-latest + env: + TURSO_API_TOKEN: ${{ secrets.TURSO_API_TOKEN }} steps: - name: Install Turso CLI run: | @@ -30,8 +36,5 @@ jobs: echo "DB_NAME=$DB_NAME" >> $GITHUB_ENV - name: Destroy Turso Database - env: - TURSO_API_TOKEN: ${{ secrets.TURSO_API_TOKEN }} - DB_NAME: ${{ env.DB_NAME }} run: | turso db destroy "$DB_NAME" --yes || echo "Database already gone or never existed" diff --git a/.github/workflows/dev_deploy.yml b/.github/workflows/dev_deploy.yml index c4f0dd5..ad3f88d 100644 --- a/.github/workflows/dev_deploy.yml +++ b/.github/workflows/dev_deploy.yml @@ -15,7 +15,7 @@ permissions: concurrency: group: deployment-dev - cancel-in-progress: true + cancel-in-progress: false jobs: deploy: @@ -36,7 +36,7 @@ jobs: TURSO_DATABASE_URL: ${{ secrets.DEV_DB_URL }} TURSO_AUTH_TOKEN: ${{ secrets.DEV_DB_TOKEN }} run: | - npx drizzle-kit push + npx drizzle-kit migrate - name: Make Development Deployment env: @@ -44,4 +44,4 @@ jobs: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} run: | - npx vercel deploy --target preview --yes --token ${{ secrets.VERCEL_TOKEN }} + npx vercel deploy --target preview --yes --token "$VERCEL_TOKEN" diff --git a/.github/workflows/pr_preview.yml b/.github/workflows/pr_preview.yml index 3cb7d00..a5f273a 100644 --- a/.github/workflows/pr_preview.yml +++ b/.github/workflows/pr_preview.yml @@ -17,7 +17,7 @@ on: type: number concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.inputs.pr_number }} cancel-in-progress: true permissions: @@ -59,7 +59,7 @@ jobs: TURSO_API_TOKEN: ${{ secrets.TURSO_API_TOKEN }} run: | # Create a branch from the dev database with the generated safe name - turso db create ${{ env.SAFE_BRANCH_NAME }} --from-db dev || true + turso db show ${{ env.SAFE_BRANCH_NAME }} &> /dev/null || turso db create ${{ env.SAFE_BRANCH_NAME }} --from-db dev - name: Get Database Credentials env: @@ -82,7 +82,7 @@ jobs: TURSO_AUTH_TOKEN: ${{ env.DB_TOKEN }} run: | # Push changes to the created database - npx drizzle-kit push + npx drizzle-kit migrate - name: Make Preview Deployment id: vercel-deployment @@ -92,17 +92,17 @@ jobs: VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} run: | # Make a preview deployment with the created database - DEPLOYMENT_URL=$(npx vercel deploy --target preview --token ${{ secrets.VERCEL_TOKEN }} \ - --build-env TURSO_DATABASE_URL=${{ env.DB_URL }} \ - --build-env TURSO_AUTH_TOKEN=${{ env.DB_TOKEN }} \ - --env TURSO_DATABASE_URL=${{ env.DB_URL }} \ - --env TURSO_AUTH_TOKEN=${{ env.DB_TOKEN }} \ + DEPLOYMENT_URL=$(npx vercel deploy --target preview --token "$VERCEL_TOKEN" \ + --build-env TURSO_DATABASE_URL="$DB_URL" \ + --build-env TURSO_AUTH_TOKEN="$DB_TOKEN" \ + --env TURSO_DATABASE_URL="$DB_URL" \ + --env TURSO_AUTH_TOKEN="$DB_TOKEN" \ --yes --logs) echo "DEPLOYMENT_URL=$DEPLOYMENT_URL" >> $GITHUB_ENV - name: Comment on PR - if: always() + if: always() && env.PR_NUM != '' uses: actions/github-script@v8 env: DEPLOYMENT_URL: ${{ env.DEPLOYMENT_URL }} diff --git a/.github/workflows/production_deploy.yml b/.github/workflows/production_deploy.yml index f369ed6..adecc5a 100644 --- a/.github/workflows/production_deploy.yml +++ b/.github/workflows/production_deploy.yml @@ -10,6 +10,10 @@ on: - README.md workflow_dispatch: +concurrency: + group: deployment-production + cancel-in-progress: false + jobs: deploy: runs-on: ubuntu-latest @@ -37,4 +41,4 @@ jobs: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} run: | - npx vercel deploy --prod --yes --token ${{ secrets.VERCEL_TOKEN }} + npx vercel deploy --prod --yes --token "$VERCEL_TOKEN" diff --git a/drizzle/0001_lucky_lucky_pierre.sql b/drizzle/0001_lucky_lucky_pierre.sql new file mode 100644 index 0000000..b6c6083 --- /dev/null +++ b/drizzle/0001_lucky_lucky_pierre.sql @@ -0,0 +1,24 @@ +ALTER TABLE `database` RENAME COLUMN "user_id" TO "team_id";--> statement-breakpoint +CREATE TABLE `team` ( + `id` text PRIMARY KEY NOT NULL, + `slug` text NOT NULL, + `display_name` text NOT NULL, + `name` text NOT NULL, + `owner_id` text NOT NULL, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + FOREIGN KEY (`owner_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE restrict +); +--> statement-breakpoint +CREATE UNIQUE INDEX `team_slug_unique` ON `team` (`slug`);--> statement-breakpoint +CREATE TABLE `team_member` ( + `user_id` text NOT NULL, + `team_id` text NOT NULL, + `role` text NOT NULL, + `created_at` integer DEFAULT (cast(unixepoch('subsecond') * 1000 as integer)) NOT NULL, + PRIMARY KEY(`user_id`, `team_id`), + FOREIGN KEY (`user_id`) REFERENCES `user`(`id`) ON UPDATE no action ON DELETE cascade, + FOREIGN KEY (`team_id`) REFERENCES `team`(`id`) ON UPDATE no action ON DELETE cascade +); +--> statement-breakpoint +ALTER TABLE `database` ADD `created_by` text REFERENCES user(id);--> statement-breakpoint +ALTER TABLE `database` ALTER COLUMN "team_id" TO "team_id" text NOT NULL REFERENCES team(id) ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/drizzle/meta/0001_snapshot.json b/drizzle/meta/0001_snapshot.json new file mode 100644 index 0000000..39f40df --- /dev/null +++ b/drizzle/meta/0001_snapshot.json @@ -0,0 +1,702 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "64780c8f-1571-433b-86a4-6cdfd2c6d311", + "prevId": "2be82bf6-2b69-47a8-9b52-5fc7172a15c6", + "tables": { + "account": { + "name": "account", + "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 + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "account_user_id_user_id_fk": { + "name": "account_user_id_user_id_fk", + "tableFrom": "account", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "database": { + "name": "database", + "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 + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'S3'" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "endpoint": { + "name": "endpoint", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "region": { + "name": "region", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "bucket_name": { + "name": "bucket_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ak_ciphertext": { + "name": "ak_ciphertext", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ak_iv": { + "name": "ak_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ak_tag": { + "name": "ak_tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sk_ciphertext": { + "name": "sk_ciphertext", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sk_iv": { + "name": "sk_iv", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "sk_tag": { + "name": "sk_tag", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdAt": { + "name": "createdAt", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "created_by": { + "name": "created_by", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "database_team_id_team_id_fk": { + "name": "database_team_id_team_id_fk", + "tableFrom": "database", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "database_created_by_user_id_fk": { + "name": "database_created_by_user_id_fk", + "tableFrom": "database", + "tableTo": "user", + "columnsFrom": [ + "created_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "two_factor_enabled": { + "name": "two_factor_enabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + } + }, + "indexes": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification": { + "name": "verification", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "session_token_unique": { + "name": "session_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "session_user_id_user_id_fk": { + "name": "session_user_id_user_id_fk", + "tableFrom": "session", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "two_factor": { + "name": "two_factor", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "secret": { + "name": "secret", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "backup_codes": { + "name": "backup_codes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "two_factor_user_id_user_id_fk": { + "name": "two_factor_user_id_user_id_fk", + "tableFrom": "two_factor", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team": { + "name": "team", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner_id": { + "name": "owner_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": { + "team_slug_unique": { + "name": "team_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + } + }, + "foreignKeys": { + "team_owner_id_user_id_fk": { + "name": "team_owner_id_user_id_fk", + "tableFrom": "team", + "tableTo": "user", + "columnsFrom": [ + "owner_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team_member": { + "name": "team_member", + "columns": { + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(cast(unixepoch('subsecond') * 1000 as integer))" + } + }, + "indexes": {}, + "foreignKeys": { + "team_member_user_id_user_id_fk": { + "name": "team_member_user_id_user_id_fk", + "tableFrom": "team_member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "team_member_team_id_team_id_fk": { + "name": "team_member_team_id_team_id_fk", + "tableFrom": "team_member", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "team_member_user_id_team_id_pk": { + "columns": [ + "user_id", + "team_id" + ], + "name": "team_member_user_id_team_id_pk" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": { + "\"database\".\"user_id\"": "\"database\".\"team_id\"" + } + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index b64e363..83b6dd1 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1766417651350, "tag": "0000_chemical_the_santerians", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1774627700925, + "tag": "0001_lucky_lucky_pierre", + "breakpoints": true } ] } \ No newline at end of file