Skip to content

Commit

Permalink
build(database): deploy migrations in the ci (#70)
Browse files Browse the repository at this point in the history
build: print branch name

build: add migration for testing

build: comment diff in the pull request

build: ensure the migration diff is displayed

build: global env variable

build: dploy request state

build: apply migration

build: apply migration in prod

fix: tests in the ci
  • Loading branch information
tericcabrel authored Jun 22, 2024
1 parent 7a26b7e commit ebe0175
Show file tree
Hide file tree
Showing 10 changed files with 221 additions and 23 deletions.
98 changes: 98 additions & 0 deletions .github/workflows/migration-db-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Check database migration
on:
pull_request:
branches:
- 'main'
paths:
- 'packages/domain/prisma/migrations/**'

env:
PLANETSCALE_SERVICE_TOKEN_ID: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_ID }}
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}

jobs:
migration-detail:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup pscale
uses: planetscale/setup-pscale-action@v1

- name: Set database branch name
run: echo "PSCALE_BRANCH_NAME=$(echo ${{ github.head_ref }} | tr -cd '[:alnum:]-'| tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV

- name: Create deploy request on development branch
run: |
DEPLOY_REQUEST_STATE=$(pscale deploy-request show ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.PSCALE_BRANCH_NAME }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} -f json | jq -r '.state')
echo "Deploy request State: $DEPLOY_REQUEST_STATE"
if [ "$DEPLOY_REQUEST_STATE" = "open" ]; then
echo "Deploy request exists: skipping creation"
else
echo "Deploy request does not exist : creating..."
pscale deploy-request create ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.PSCALE_BRANCH_NAME }} \
--org ${{ secrets.PLANETSCALE_ORG_NAME }} \
--into dev \
--notes "Apply changes to the dev branch"
fi
- name: Get deploy request number
run: |
DEPLOY_REQUEST_NUMBER=$(pscale deploy-request show ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.PSCALE_BRANCH_NAME }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} -f json | jq -r '.number')
echo "DEPLOY_REQUEST_NUMBER=$DEPLOY_REQUEST_NUMBER" >> $GITHUB_ENV
- name: Check deployment state
continue-on-error: false
run: |
for i in {1..10}; do
DEPLOYMENT_STATE=$(pscale deploy-request show ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.DEPLOY_REQUEST_NUMBER }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} --format json | jq -r '.deployment_state')
echo "Deployment State: $DEPLOYMENT_STATE"
if [ "$DEPLOYMENT_STATE" = "ready" ]; then
echo "Deployment state is ready. Continuing."
echo "DEPLOY_REQUEST_OPENED=true" >> $GITHUB_ENV
break
fi
echo "Deployment state is not ready. Waiting 2 seconds before checking again."
sleep 2
done
- name: Collect the migration diff
continue-on-error: false
if: ${{ env.DEPLOY_REQUEST_OPENED }}
run: |
DEPLOY_DATA=$(pscale api organizations/${{ secrets.PLANETSCALE_ORG_NAME }}/databases/${{ secrets.PLANETSCALE_DATABASE_NAME }}/deploy-requests/${{ env.DEPLOY_REQUEST_NUMBER }}/deployment --org planetscale)
CAN_DROP_DATA=$(echo "$DEPLOY_DATA" | jq -r '.deploy_operations[] | select(.can_drop_data == true) | .can_drop_data')
echo "Deploy request opened: https://app.planetscale.com/${{ secrets.PLANETSCALE_ORG_NAME }}/${{ secrets.PLANETSCALE_DATABASE_NAME }}/deploy-requests/${{ env.DEPLOY_REQUEST_NUMBER }}" >> migration-message.txt
echo "" >> migration-message.txt
if [ "$CAN_DROP_DATA" = "true" ]; then
echo ":rotating_light: You are dropping a column. Before running the migration make sure to do the following:" >> migration-message.txt
echo "" >> migration-message.txt
echo "1. [ ] Deploy app changes to ensure the column is no longer being used." >> migration-message.txt
echo "2. [ ] Once you've verified it's no used, run the deploy request." >> migration-message.txt
echo "" >> migration-message.txt
else
echo "When adding to the schema, the Deploy Request must be run **before** the code is deployed." >> migration-message.txt
echo "Please ensure your schema changes are compatible with the application code currently running in production." >> migration-message.txt
echo "" >> migration-message.txt
echo "1. [ ] Successfully run the Deploy Request" >> migration-message.txt
echo "2. [ ] Deploy this PR" >> migration-message.txt
echo "" >> migration-message.txt
fi
echo "\`\`\`diff" >> migration-message.txt
pscale deploy-request diff ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.DEPLOY_REQUEST_NUMBER }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} -f json | jq -r '.[].raw' >> migration-message.txt
echo "\`\`\`" >> migration-message.txt
- name: Comment pull request with the migration diff
uses: thollander/actions-comment-pull-request@v2
if: ${{ env.DEPLOY_REQUEST_OPENED }}
with:
filePath: migration-message.txt
84 changes: 84 additions & 0 deletions .github/workflows/migration-db-deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Deploy database migration
on:
push:
branches:
- 'main'
paths:
- 'packages/domain/prisma/migrations/**'

env:
PLANETSCALE_SERVICE_TOKEN_ID: ${{ secrets.PLANETSCALE_SERVICE_TOKEN_ID }}
PLANETSCALE_SERVICE_TOKEN: ${{ secrets.PLANETSCALE_SERVICE_TOKEN }}

jobs:
apply-migration:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Setup pscale
uses: planetscale/setup-pscale-action@v1

- name: Set database branch name
run: echo "PSCALE_BRANCH_NAME=$(echo ${{ github.head_ref }} | tr -cd '[:alnum:]-'| tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV

- name: Get the deploy request number in development branch
run: |
DEV_DEPLOY_REQUEST_NUMBER=$(pscale deploy-request show ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.PSCALE_BRANCH_NAME }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} -f json | jq -r '.number')
echo "DEV_DEPLOY_REQUEST_NUMBER=$DEV_DEPLOY_REQUEST_NUMBER" >> $GITHUB_ENV
- name: Check deployment state
continue-on-error: false
run: |
for i in {1..10}; do
DEPLOYMENT_STATE=$(pscale deploy-request show ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.DEV_DEPLOY_REQUEST_NUMBER }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} -f json | jq -r '.deployment_state')
echo "Deployment State: $DEPLOYMENT_STATE"
if [ "$DEPLOYMENT_STATE" = "ready" ]; then
echo "Deployment state is ready. Continuing."
echo "DEPLOY_REQUEST_OPENED=true" >> $GITHUB_ENV
break
fi
echo "Deployment state is not ready. Waiting 2 seconds before checking again."
sleep 2
done
- name: Deploy schema migration in the development branch
continue-on-error: false
run: |
pscale deploy-request deploy ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.DEV_DEPLOY_REQUEST_NUMBER }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} --wait
- name: Deploy schema migration in the production branch
if: ${{ success() }}
continue-on-error: false
run: |
pscale deploy-request create ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.PSCALE_BRANCH_NAME }} \
--org ${{ secrets.PLANETSCALE_ORG_NAME }} \
--into main \
--notes "Apply changes to the production branch"
PROD_DEPLOY_REQUEST_NUMBER=$(pscale deploy-request show ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.PSCALE_BRANCH_NAME }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} -f json | jq -r '.number')
for i in {1..10}; do
DEPLOYMENT_STATE=$(pscale deploy-request show ${{ secrets.PLANETSCALE_DATABASE_NAME }} $PROD_DEPLOY_REQUEST_NUMBER --org ${{ secrets.PLANETSCALE_ORG_NAME }} -f json | jq -r '.deployment_state')
echo "Deployment State: $DEPLOYMENT_STATE"
if [ "$DEPLOYMENT_STATE" = "ready" ]; then
echo "Deployment state is ready. Continuing."
break
fi
echo "Deployment state is not ready. Waiting 2 seconds before checking again."
sleep 2
done
pscale deploy-request deploy ${{ secrets.PLANETSCALE_DATABASE_NAME }} $PROD_DEPLOY_REQUEST_NUMBER --org ${{ secrets.PLANETSCALE_ORG_NAME }} --wait
- name: Delete the database branch
if: ${{ success() }}
run: |
PROD_DEPLOY_REQUEST_NUMBER=$(pscale deploy-request show ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.PSCALE_BRANCH_NAME }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} -f json | jq -r '.number')
pscale deploy-request skip-revert ${{ secrets.PLANETSCALE_DATABASE_NAME }} $PROD_DEPLOY_REQUEST_NUMBER --org ${{ secrets.PLANETSCALE_ORG_NAME }}
pscale branch delete ${{ secrets.PLANETSCALE_DATABASE_NAME }} ${{ env.PSCALE_BRANCH_NAME }} --org ${{ secrets.PLANETSCALE_ORG_NAME }} --force
echo "The branch \"${{ env.PSCALE_BRANCH_NAME }}\" has been successfully."
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE `folders` ADD COLUMN `category` ENUM('visible', 'favorite', 'archived', 'hidden') NOT NULL DEFAULT 'visible';
54 changes: 31 additions & 23 deletions packages/domain/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ generator client {
}

datasource db {
provider = "mysql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
relationMode = "prisma"
provider = "mysql"
url = env("DATABASE_URL")
shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
relationMode = "prisma"
}

enum RoleName {
Expand All @@ -28,6 +28,13 @@ enum SnippetVisibility {
private
}

enum FolderCategory {
visible
favorite
archived
hidden
}

model Role {
id String @id @db.VarChar(50)
name RoleName @unique
Expand Down Expand Up @@ -64,24 +71,25 @@ model User {
}

model Folder {
id String @id @db.VarChar(50)
userId String @map("user_id") @db.VarChar(50)
parentId String? @map("parent_id")
name String @db.VarChar(255)
path String? @db.Text
isFavorite Boolean @default(false) @map("is_favorite")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
parent Folder? @relation(name: "parent_folder", fields: [parentId], references: [id], onDelete: NoAction, onUpdate: NoAction)
subFolders Folder[] @relation("parent_folder")
id String @id @db.VarChar(50)
userId String @map("user_id") @db.VarChar(50)
parentId String? @map("parent_id")
name String @db.VarChar(255)
path String? @db.Text
isFavorite Boolean @default(false) @map("is_favorite")
category FolderCategory @default(visible)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
parent Folder? @relation(name: "parent_folder", fields: [parentId], references: [id], onDelete: NoAction, onUpdate: NoAction)
subFolders Folder[] @relation("parent_folder")
snippets Snippet[]
@@unique([userId, parentId, name], name: "folder_name_unique_constraint")
@@index([name])
@@index([isFavorite])
@@index([userId])
@@index([parentId])
@@unique([userId, parentId, name], name: "folder_name_unique_constraint")
@@map("folders")
}

Expand All @@ -91,12 +99,12 @@ model Snippet {
folderId String @map("folder_id")
name String @db.VarChar(255)
content String @db.Text
contentHtml String? @db.Text @map("content_html")
contentHtml String? @map("content_html") @db.Text
language String @db.VarChar(20)
size Int @default(0) @db.Int
visibility SnippetVisibility @default(public)
description String? @db.Text
lineHighlight String? @db.Text @map("line_highlight")
lineHighlight String? @map("line_highlight") @db.Text
theme String @db.VarChar(20)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
Expand All @@ -112,11 +120,11 @@ model Snippet {
}

model Session {
id String @id @default(cuid())
token String @unique
userId String @map("user_id")
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
id String @id @default(cuid())
token String @unique
userId String @map("user_id")
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@map("sessions")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,7 @@ describe('Test Folder service', () => {
const folderToUpdate = updateFolderInput.toFolder(folder);

expect(updatedFolder).toMatchObject<Folder>({
category: 'visible',
createdAt: expect.any(Date),
id: folder.id,
isFavorite: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('Test Create Folder Input', () => {
const folder = input.toFolder();

expect(folder).toMatchObject<Folder>({
category: 'visible',
createdAt: expect.any(Date),
id: expect.any(String),
isFavorite: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export class CreateFolderInput {

toFolder(): Folder {
return {
category: 'visible',
createdAt: new Date(),
id: this.folderId,
isFavorite: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ describe('Test Create User Root Folder Input', () => {
const folder = input.toFolder();

expect(folder).toMatchObject<Folder>({
category: 'visible',
createdAt: expect.any(Date),
id: expect.any(String),
isFavorite: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export class CreateUserRootFolderInput {

toFolder(): Folder {
return {
category: 'visible',
createdAt: new Date(),
id: this.folderId,
isFavorite: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ describe('Test Update Folder Input', () => {
const folderToUpdate = input.toFolder(currentFolder);

const expectedFolder: Folder = {
category: 'visible',
createdAt: currentFolder.createdAt,
id: currentFolder.id,
isFavorite: currentFolder.isFavorite,
Expand Down

0 comments on commit ebe0175

Please sign in to comment.