diff --git a/.env.example b/.env.example index 83755f2e..3bf65e15 100644 --- a/.env.example +++ b/.env.example @@ -8,14 +8,22 @@ GOOGLE_CLIENT_SECRET=toBeChanged BASE_URL=http://localhost:3000 NEXTAUTH_URL=http://localhost:3000 -# Optional, false by default -USE_LOCAL_DB=false - -# Planetscale DB connection settings DATABASE_HOST=toBeChanged +DATABASE_NAME=toBeChanged + +# Prod DB +# DATABASE_LABEL=prod +# DATABASE_USERNAME=toBeChanged +# DATABASE_PASSWORD=toBeChanged + +# Stage DB +DATABASE_LABEL=stage DATABASE_USERNAME=toBeChanged DATABASE_PASSWORD=toBeChanged -DATABASE_NAME=toBeChanged + +# Local DB +# DATABASE_LABEL=local +# USE_LOCAL_DB=true # AWS S3 connection settings AWS_ACCESS_KEY_ID=toBeChanged diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 475b12fc..f3f06ad8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -45,6 +45,12 @@ jobs: - name: 📦 Install dependencies run: pnpm install + - name: 🆕 Check if migrations are up to date + run: pnpm run db:generate + + - name: 🚨 Fail if new migrations are generated + uses: NathanielHill/fail-if-changes@master + - name: 🔦 Run lint run: pnpm lint --fix diff --git a/.github/workflows/comment-if-db-migrations.yaml b/.github/workflows/comment-if-db-migrations.yaml new file mode 100644 index 00000000..336c7ba7 --- /dev/null +++ b/.github/workflows/comment-if-db-migrations.yaml @@ -0,0 +1,32 @@ +name: Check if db migrations are needed + +on: + pull_request: + branches: [main] + paths: + - 'drizzle/**' + +jobs: + comment-if-db-migrations: + permissions: + pull-requests: write + contents: read + + runs-on: ubuntu-latest + steps: + - name: Comment PR - db migrated + uses: thollander/actions-comment-pull-request@main + with: + message: | + > [!NOTE] + > This PR introduces changes to the database. + + **Remember to:** + + 1. Run the migrations in [stage database branch](https://app.planetscale.com/descobreix-begur-app/descobreix-begur-app/stage) with: + ```bash + pnpm db:push + ``` + 1. Create a [deploy request in Planetscale](https://app.planetscale.com/descobreix-begur-app/descobreix-begur-app/deploy-requests/) + 1. Merge the deploy request from Planetscale just before merging the PR. + comment_tag: execution diff --git a/drizzle.config.ts b/drizzle.config.ts index 08b4b53d..9d62563a 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -5,16 +5,20 @@ import path from 'node:path' dotenv.config({ path: path.resolve(process.cwd(), '.env.local') }) dotenv.config({ path: path.resolve(process.cwd(), '.env') }) -if (process.env.USE_LOCAL_DB !== 'true') { - if ( - !process.env.DATABASE_HOST || - !process.env.DATABASE_USERNAME || - !process.env.DATABASE_PASSWORD || - !process.env.DATABASE_NAME - ) { - throw new Error('Missing environment variables for database') - } +if ( + process.env.USE_LOCAL_DB !== 'true' && + process.env.SKIP_ENV_VALIDATION !== 'true' +) { + if (!process.env.DATABASE_HOST) + throw new Error('Missing environment variable DATABASE_HOST') + if (!process.env.DATABASE_NAME) + throw new Error('Missing environment variable DATABASE_NAME') + if (!process.env.DATABASE_USERNAME) + throw new Error('Missing environment variable DATABASE_USERNAME') + if (!process.env.DATABASE_PASSWORD) + throw new Error('Missing environment variable DATABASE_PASSWORD') } + export default { schema: './src/server/db/schema/*', out: './drizzle', diff --git a/drizzle/0003_colorful_fat_cobra.sql b/drizzle/0003_colorful_fat_cobra.sql new file mode 100644 index 00000000..95ec8f43 --- /dev/null +++ b/drizzle/0003_colorful_fat_cobra.sql @@ -0,0 +1 @@ +ALTER TABLE `place` ADD `mainImage` varchar(1024); \ No newline at end of file diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 00000000..68bb1250 --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,285 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "aea0cf0a-32d1-453e-b0e7-2419b3070b31", + "prevId": "4083d305-aa61-41e2-9e44-89f0efa632c2", + "tables": { + "place": { + "name": "place", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "mainImage": { + "name": "mainImage", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_id": { + "name": "place_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "account": { + "name": "account", + "columns": { + "userId": { + "name": "userId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "providerAccountId": { + "name": "providerAccountId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "int", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_type": { + "name": "token_type", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "session_state": { + "name": "session_state", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "account_provider_providerAccountId": { + "name": "account_provider_providerAccountId", + "columns": [ + "provider", + "providerAccountId" + ] + } + }, + "uniqueConstraints": {} + }, + "session": { + "name": "session", + "columns": { + "sessionToken": { + "name": "sessionToken", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "session_sessionToken": { + "name": "session_sessionToken", + "columns": [ + "sessionToken" + ] + } + }, + "uniqueConstraints": {} + }, + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hashedPassword": { + "name": "hashedPassword", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "emailVerified": { + "name": "emailVerified", + "type": "timestamp(3)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "user_id": { + "name": "user_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": { + "user_email_unique": { + "name": "user_email_unique", + "columns": [ + "email" + ] + } + } + }, + "verificationToken": { + "name": "verificationToken", + "columns": { + "identifier": { + "name": "identifier", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token": { + "name": "verificationToken_identifier_token", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {} + } + }, + "schemas": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index f9009585..aab3873a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -22,6 +22,13 @@ "when": 1697999070080, "tag": "0002_productive_thunderbolt", "breakpoints": true + }, + { + "idx": 3, + "version": "5", + "when": 1698187240633, + "tag": "0003_colorful_fat_cobra", + "breakpoints": true } ] } \ No newline at end of file diff --git a/package.json b/package.json index 77c85034..52e5f69d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "test:integration:dev": "SKIP_ENV_VALIDATION=true jest --watch", "test:e2e": "playwright test", "test:e2e:dev": "playwright test --ui", - "db:generate": "drizzle-kit generate:mysql", + "db:generate": "SKIP_ENV_VALIDATION=true drizzle-kit generate:mysql", "db:push": "drizzle-kit push:mysql", "db-local:studio": "drizzle-kit studio", "db-local:migrate": "node ./src/server/db/migrate.mjs", diff --git a/src/app/[locale]/(app)/explore/_components/place-list.tsx b/src/app/[locale]/(app)/explore/_components/place-list.tsx index 71f95662..d8b65b49 100644 --- a/src/app/[locale]/(app)/explore/_components/place-list.tsx +++ b/src/app/[locale]/(app)/explore/_components/place-list.tsx @@ -5,6 +5,15 @@ import { useTranslations } from 'next-intl' import type { FC, HTMLAttributes } from 'react' import { trpc } from '~/trpc' +import { Card, CardBody, CardFooter } from '@nextui-org/card' +import { Image } from '@nextui-org/image' + +function makeImageUrl(s3key: T | null) { + if (!s3key) { + return 'https://descobreix-begur-app-g3qf4o.s3.eu-west-1.amazonaws.com/static/app/content-placeholder.png' + } + return `https://descobreix-begur-app-g3qf4o.s3.eu-west-1.amazonaws.com/${s3key}` as const +} export const PlaceList: FC, 'children'>> = ({ className, @@ -28,14 +37,32 @@ export const PlaceList: FC, 'children'>> = ({ } return ( -
    +
      {places?.map((place) => ( -
    • console.log('item pressed')} > - {place.name} -
    • + + {place.name} + + + {place.name} + + ))}
    ) diff --git a/src/components/db-environment-tag.tsx b/src/components/db-environment-tag.tsx index 06616b47..10baa4b8 100644 --- a/src/components/db-environment-tag.tsx +++ b/src/components/db-environment-tag.tsx @@ -2,25 +2,33 @@ import type { FC } from 'react' import { env } from '~/env.mjs' import { cn } from '~/helpers/cn' -const isUsingLocalDb = env.USE_LOCAL_DB === 'true' +const dbLabel = env.DATABASE_LABEL + +const dbLabelText = { + local: 'Local DB', + stage: 'Stage DB', + prod: 'Prod DB', +} as const export const DbEnvironmentTag: FC<{ className?: string }> = ({ className }) => { return ( <> - {env.NODE_ENV !== 'production' && ( - - {isUsingLocalDb ? 'Local DB' : 'Prod DB'} - - )} + {(env.NODE_ENV !== 'production' || env.VERCEL_ENV === 'preview') && + dbLabel && ( + + {dbLabelText[dbLabel]} + + )} ) } diff --git a/src/components/navbar/bottom-navbar.tsx b/src/components/navbar/bottom-navbar.tsx index b4f59ca9..d4263278 100644 --- a/src/components/navbar/bottom-navbar.tsx +++ b/src/components/navbar/bottom-navbar.tsx @@ -49,7 +49,7 @@ export const BottomNavbar: FC<{ session ? ( ) : ( diff --git a/src/env.mjs b/src/env.mjs index 8409bce8..ee36753d 100644 --- a/src/env.mjs +++ b/src/env.mjs @@ -8,6 +8,7 @@ import { z } from 'zod' */ const server = z.object({ NODE_ENV: z.enum(['development', 'test', 'production']), + VERCEL_ENV: z.enum(['development', 'preview', 'production']).optional(), GOOGLE_CLIENT_ID: z.string().min(1), GOOGLE_CLIENT_SECRET: z.string().min(1), @@ -19,6 +20,7 @@ const server = z.object({ .regex(/[\w\d-]+(\.[\w\d-]+)*/) .optional(), + DATABASE_LABEL: z.enum(['local', 'stage', 'prod']).optional(), USE_LOCAL_DB: z.union([z.literal('true'), z.literal('false')]).optional(), DATABASE_HOST: z.string().min(1), DATABASE_USERNAME: z.string().min(1), @@ -66,6 +68,7 @@ const FAKE_VALUE_ONLY_FOR_DEVELOPMENT = */ const processEnv = { NODE_ENV: process.env.NODE_ENV, + VERCEL_ENV: process.env.VERCEL_ENV, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID ?? FAKE_VALUE_ONLY_FOR_DEVELOPMENT, @@ -76,6 +79,7 @@ const processEnv = { VERCEL_URL: process.env.VERCEL_URL, NEXTAUTH_URL: process.env.NEXTAUTH_URL ?? VERCEL_URL_WITH_PROTOCOL, + DATABASE_LABEL: process.env.DATABASE_LABEL, USE_LOCAL_DB: process.env.USE_LOCAL_DB, DATABASE_HOST: process.env.DATABASE_HOST, DATABASE_USERNAME: process.env.DATABASE_USERNAME, diff --git a/src/server/db/schema/places.ts b/src/server/db/schema/places.ts index 1ba4804c..da49d79d 100644 --- a/src/server/db/schema/places.ts +++ b/src/server/db/schema/places.ts @@ -1,6 +1,7 @@ -import { mysqlTable, serial, text } from 'drizzle-orm/mysql-core' +import { mysqlTable, serial, text, varchar } from 'drizzle-orm/mysql-core' export const places = mysqlTable('place', { id: serial('id').primaryKey(), name: text('name').notNull(), + mainImage: varchar('mainImage', { length: 1024 }), })