diff --git a/.github/workflows/dashboard-feature-env.yml b/.github/workflows/dashboard-feature-env.yml index c1dbe2de..f2cb6385 100644 --- a/.github/workflows/dashboard-feature-env.yml +++ b/.github/workflows/dashboard-feature-env.yml @@ -26,16 +26,25 @@ jobs: with: creds: ${{ secrets.AZURE_CREDENTIALS }} - - name: Create env file + - name: Create Neon Branch + id: create-branch + uses: neondatabase/create-branch-action@v4 + with: + project_id: ${{ env.NEON_PROJECT_ID }} + branch_name: preview/pr-${{ github.event.number }} + username: ${{ env.NEON_DATABASE_USERNAME }} + api_key: ${{ secrets.NEON_API_KEY }} + + - name: Setup environment run: | echo "${{ secrets.AZURE_BLOB_STORAGE_ACCOUNT }}" >> ./ethereal-nexus-dashboard/.env echo "${{ secrets.AZURE_BLOB_STORAGE_SECRET }}" >> ./ethereal-nexus-dashboard/.env - echo "${{ secrets.PGHOST }}" >> ./ethereal-nexus-dashboard/.env - echo "${{ secrets.PGUSER }}" >> ./ethereal-nexus-dashboard/.env - echo "${{ secrets.PGPORT }}" >> ./ethereal-nexus-dashboard/.env - echo "${{ secrets.PGPASSWORD }}" >> ./ethereal-nexus-dashboard/.env - echo "PGSSL=true" >> ./ethereal-nexus-dashboard/.env + echo "DRIZZLE_DATABASE_TYPE=neon" >> ./ethereal-nexus-dashboard/.env + echo "DRIZZLE_DATABASE_URL=${{ steps.create-branch.outputs.db_url }}?sslmode=require" >> ./ethereal-nexus-dashboard/.env echo "${{ secrets.NEXT_AUTH_SECRET }}" >> ./ethereal-nexus-dashboard/.env + + cd ./ethereal-nexus-dashboard + npx drizzle-kit push:pg - name: Deploy Container App uses: azure/container-apps-deploy-action@v1 diff --git a/ethereal-nexus-dashboard/drizzle.config.ts b/ethereal-nexus-dashboard/drizzle.config.ts index 40e2d3b6..8511d7d3 100644 --- a/ethereal-nexus-dashboard/drizzle.config.ts +++ b/ethereal-nexus-dashboard/drizzle.config.ts @@ -12,11 +12,6 @@ export default { out: "./drizzle", driver: 'pg', dbCredentials: { - host: process.env.PGHOST!, - port: Number(process.env.PGPORT), - user: process.env.PGUSER, - password: process.env.PGPASSWORD, - database: process.env.PGDATABASE || 'postgres', - ssl: process.env.PGSSL === 'true', + connectionString: process.env.DRIZZLE_DATABASE_URL! }, } satisfies Config; diff --git a/ethereal-nexus-dashboard/package-lock.json b/ethereal-nexus-dashboard/package-lock.json index 397f810b..861cfad0 100644 --- a/ethereal-nexus-dashboard/package-lock.json +++ b/ethereal-nexus-dashboard/package-lock.json @@ -11,6 +11,7 @@ "@azure/storage-blob": "^12.15.0", "@epic-web/remember": "^1.0.2", "@hookform/resolvers": "^3.3.1", + "@neondatabase/serverless": "^0.8.1", "@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-avatar": "^1.0.3", "@radix-ui/react-checkbox": "^1.0.4", @@ -38,7 +39,7 @@ "next-auth": "^5.0.0-beta.4", "next-swagger-doc": "^0.4.0", "next-themes": "^0.2.1", - "postgres": "^3.3.5", + "postgres": "3.3.5", "react": "18.2.0", "react-dom": "18.2.0", "react-hook-form": "^7.46.1", @@ -553,6 +554,14 @@ "version": "7.1.3", "license": "MIT" }, + "node_modules/@neondatabase/serverless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-0.8.1.tgz", + "integrity": "sha512-nxZfTLbGqvDrw0W9WnQxzoPn4KC6SLjkvK4grdf6eWVMQSc24X+8udz9inZWOGu8f0O3wJAq586fCZ32r22lwg==", + "dependencies": { + "@types/pg": "8.6.6" + } + }, "node_modules/@next/env": { "version": "14.1.0", "resolved": "https://registry.npmjs.org/@next/env/-/env-14.1.0.tgz", @@ -2079,6 +2088,16 @@ "form-data": "^4.0.0" } }, + "node_modules/@types/pg": { + "version": "8.6.6", + "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.6.tgz", + "integrity": "sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==", + "dependencies": { + "@types/node": "*", + "pg-protocol": "*", + "pg-types": "^2.2.0" + } + }, "node_modules/@types/prop-types": { "version": "15.7.5", "license": "MIT" @@ -3549,7 +3568,8 @@ }, "node_modules/drizzle-orm": { "version": "0.29.3", - "license": "Apache-2.0", + "resolved": "https://registry.npmjs.org/drizzle-orm/-/drizzle-orm-0.29.3.tgz", + "integrity": "sha512-uSE027csliGSGYD0pqtM+SAQATMREb3eSM/U8s6r+Y0RFwTKwftnwwSkqx3oS65UBgqDOM0gMTl5UGNpt6lW0A==", "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=3", @@ -6353,6 +6373,34 @@ "node": ">=8" } }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.6.0.tgz", + "integrity": "sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/picocolors": { "version": "1.0.0", "license": "ISC" @@ -6514,6 +6562,41 @@ "url": "https://github.com/sponsors/porsager" } }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/preact": { "version": "10.11.3", "license": "MIT", diff --git a/ethereal-nexus-dashboard/package.json b/ethereal-nexus-dashboard/package.json index aec8c511..987b318a 100644 --- a/ethereal-nexus-dashboard/package.json +++ b/ethereal-nexus-dashboard/package.json @@ -14,6 +14,7 @@ "@azure/storage-blob": "^12.15.0", "@epic-web/remember": "^1.0.2", "@hookform/resolvers": "^3.3.1", + "@neondatabase/serverless": "^0.8.1", "@radix-ui/react-alert-dialog": "^1.0.4", "@radix-ui/react-avatar": "^1.0.3", "@radix-ui/react-checkbox": "^1.0.4", diff --git a/ethereal-nexus-dashboard/src/data/components/schema.ts b/ethereal-nexus-dashboard/src/data/components/schema.ts index 2f170311..29682bf7 100644 --- a/ethereal-nexus-dashboard/src/data/components/schema.ts +++ b/ethereal-nexus-dashboard/src/data/components/schema.ts @@ -7,6 +7,8 @@ export const components = pgTable("component", { id: uuid('id').primaryKey().notNull().defaultRandom(), slug: text('slug').unique(), name: text("name").notNull(), + title: text("title"), + description: text("description"), }) export const componentsRelations = relations(components, ({ many }) => ({ versions: many(componentVersions), diff --git a/ethereal-nexus-dashboard/src/db/index.ts b/ethereal-nexus-dashboard/src/db/index.ts index f5e9948b..45f4fb3a 100644 --- a/ethereal-nexus-dashboard/src/db/index.ts +++ b/ethereal-nexus-dashboard/src/db/index.ts @@ -1,30 +1,45 @@ -import { drizzle } from 'drizzle-orm/postgres-js'; +import { drizzle as drizzlePg } from 'drizzle-orm/postgres-js'; +import { drizzle as drizzleNeon } from 'drizzle-orm/neon-http'; import postgres from 'postgres'; +import { neon } from '@neondatabase/serverless'; +import { remember } from '@epic-web/remember'; import * as users from '@/data/users/schema'; import * as projects from '@/data/projects/schema'; import * as member from '@/data/member/schema'; import * as components from '@/data/components/schema'; -import { remember } from '@epic-web/remember'; -const queryClient = postgres( - `postgres://${process.env.PGUSER}:${process.env.PGPASSWORD}@${ - process.env.PGHOST - }:${process.env.PGPORT}/${process.env.PGDATABASE || 'postgres'}`, - { - max: 30, - idle_timeout: 20, - ssl: process.env.PGSSL === 'true', - }, -); +function clientFactory() { + let drizzle, client; -export const db = remember('db', () => - drizzle(queryClient, { + switch (process.env.DRIZZLE_DATABASE_TYPE) { + case 'neon': + drizzle = drizzleNeon; + client = neon(process.env.DRIZZLE_DATABASE_URL!); + break; + default: + drizzle = drizzlePg; + let connectionString = + client = postgres( + process.env.DRIZZLE_DATABASE_URL!, + { + max: 30, + idle_timeout: 20, + ssl: process.env.PGSSL === 'true' + } + ); + } + + return drizzle(client, { schema: { ...users, ...projects, ...member, - ...components, - }, - }), -); + ...components + } + }) +} + +export const db = remember('db', () => + clientFactory() +); \ No newline at end of file diff --git a/ethereal-nexus-dashboard/src/db/migrate.ts b/ethereal-nexus-dashboard/src/db/migrate.ts index 7459e74d..e445db9c 100644 --- a/ethereal-nexus-dashboard/src/db/migrate.ts +++ b/ethereal-nexus-dashboard/src/db/migrate.ts @@ -2,8 +2,7 @@ import { drizzle } from "drizzle-orm/postgres-js"; import { migrate } from "drizzle-orm/postgres-js/migrator"; import postgres from "postgres"; -const connectionString = `postgres://${process.env.PGUSER}:${process.env.PGPASSWORD}@${process.env.PGHOST}:${process.env.PGPORT}/postgres` -const sql = postgres(connectionString, { max: 1, ssl: true }) +const sql = postgres(process.env.DRIZZLE_DATABASE_URL!, { max: 1, ssl: true }) const db = drizzle(sql); await migrate(db, { migrationsFolder: "drizzle" });