From 8a6e443996f8f84e76f80e0168260f216b17763e Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 20:22:04 +0200 Subject: [PATCH 01/17] Add translations --- .../(app)/explore/_components/place-list.tsx | 16 ++++++---- src/i18n.ts | 21 ++++++++++++-- src/schemas/places.ts | 9 +++--- src/server/api/router/places.ts | 25 ++++++++++++---- src/server/db/schema/places.ts | 29 +++++++++++++++++-- src/server/db/utilities.ts | 8 +++++ 6 files changed, 87 insertions(+), 21 deletions(-) create mode 100644 src/server/db/utilities.ts diff --git a/src/app/[locale]/(app)/explore/_components/place-list.tsx b/src/app/[locale]/(app)/explore/_components/place-list.tsx index d8b65b49..0d8caf0a 100644 --- a/src/app/[locale]/(app)/explore/_components/place-list.tsx +++ b/src/app/[locale]/(app)/explore/_components/place-list.tsx @@ -1,12 +1,12 @@ 'use client' -import { cn } from '~/helpers/cn' -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' +import { useLocale, useTranslations } from 'next-intl' +import type { FC, HTMLAttributes } from 'react' +import { cn } from '~/helpers/cn' +import { localeOrDefault } from '~/i18n' +import { trpc } from '~/trpc' function makeImageUrl(s3key: T | null) { if (!s3key) { @@ -20,7 +20,11 @@ export const PlaceList: FC, 'children'>> = ({ ...props }) => { const t = useTranslations('explore.list') - const { data: places, isInitialLoading } = trpc.places.list.useQuery() + const locale = useLocale() + + const { data: places, isInitialLoading } = trpc.places.list.useQuery({ + locale: localeOrDefault(locale), + }) if (isInitialLoading) { return ( diff --git a/src/i18n.ts b/src/i18n.ts index 0fe5bb1e..cf563a2f 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1,5 +1,22 @@ -export const locales = ['ca', 'en'] as const -export const defaultLocale = locales[0] +export const localesInDatabase = [ + 'ca', + 'en', +] as const satisfies readonly string[] +export const locales = ['ca', 'en'] as const satisfies readonly [ + ...typeof localesInDatabase, + ...string[], +] +export const defaultLocale = 'ca' satisfies (typeof locales)[number] + +function isLocaleInDatabase( + locale: string +): locale is (typeof localesInDatabase)[number] { + return localesInDatabase.includes(locale as any) +} + +export function localeOrDefault(locale: T) { + return isLocaleInDatabase(locale) ? locale : defaultLocale +} const localePathPattern = /^\/(?[^/\s]+)/ diff --git a/src/schemas/places.ts b/src/schemas/places.ts index 93d636b5..e255fca2 100644 --- a/src/schemas/places.ts +++ b/src/schemas/places.ts @@ -1,7 +1,8 @@ import { z } from 'zod' +import { localesInDatabase } from '~/i18n' -// TODO: This file is unused, I just left it here as an example +export const listPlacesSchema = z.object({ + locale: z.enum(localesInDatabase), +}) -export const addPlaceInputSchema = z.object({ title: z.string().min(1) }) - -export type AddPlaceInputData = z.infer +export type ListPlacesInputData = z.infer diff --git a/src/server/api/router/places.ts b/src/server/api/router/places.ts index 20351078..1a7b5271 100644 --- a/src/server/api/router/places.ts +++ b/src/server/api/router/places.ts @@ -1,14 +1,27 @@ import 'server-only' -import { desc } from 'drizzle-orm' +import { asc, eq, sql } from 'drizzle-orm' import { db } from '~/server/db/db' -import { places } from '~/server/db/schema/places' +import { placesData, placesTranslations } from '~/server/db/schema/places' import { procedure, router } from '~/server/trpc' +import { listPlacesSchema } from '~/schemas/places' + +// TODO: Handle when a specific place doesn't have a translation for the given locale while others do. +// Right now what happens is that the place without the locale is not returned in the list +const getAllPlaces = db + .select({ + id: placesData.id, + mainImage: placesData.mainImage, + name: placesTranslations.name, + }) + .from(placesData) + .innerJoin(placesTranslations, eq(placesData.id, placesTranslations.placeId)) + .where(eq(placesTranslations.locale, sql.placeholder('locale'))) + .orderBy(asc(placesTranslations.name)) + .prepare() export const placesRouter = router({ - list: procedure.query(async () => { - return await db.query.places.findMany({ - orderBy: [desc(places.name)], - }) + list: procedure.input(listPlacesSchema).query(async ({ input }) => { + return await getAllPlaces.execute({ locale: input.locale }) }), }) diff --git a/src/server/db/schema/places.ts b/src/server/db/schema/places.ts index da49d79d..8a168c93 100644 --- a/src/server/db/schema/places.ts +++ b/src/server/db/schema/places.ts @@ -1,7 +1,30 @@ -import { mysqlTable, serial, text, varchar } from 'drizzle-orm/mysql-core' +import { relations } from 'drizzle-orm' +import { mysqlTable, serial, text } from 'drizzle-orm/mysql-core' +import { locale, s3ObjectKey } from '../utilities' -export const places = mysqlTable('place', { +export const placesData = mysqlTable('place_data', { id: serial('id').primaryKey(), + mainImage: s3ObjectKey('mainImage'), +}) + +export const placesTranslations = mysqlTable('place_translation', { + id: serial('id').primaryKey(), + placeId: serial('place_id').notNull(), + locale: locale('locale').notNull(), + name: text('name').notNull(), - mainImage: varchar('mainImage', { length: 1024 }), }) + +export const placesDataRelations = relations(placesData, ({ many }) => ({ + translations: many(placesTranslations), +})) + +export const placesTranslationsRelations = relations( + placesTranslations, + ({ one }) => ({ + data: one(placesData, { + fields: [placesTranslations.placeId], + references: [placesData.id], + }), + }) +) diff --git a/src/server/db/utilities.ts b/src/server/db/utilities.ts new file mode 100644 index 00000000..999107fc --- /dev/null +++ b/src/server/db/utilities.ts @@ -0,0 +1,8 @@ +import { varchar } from 'drizzle-orm/mysql-core' +import { localesInDatabase } from '../../i18n' + +export const s3ObjectKey = (name: T) => + varchar(name, { length: 1024 }) + +export const locale = (name: T) => + varchar(name, { length: 10, enum: localesInDatabase }) From 0b318824732a997893c808172108e804a2443603 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 20:22:16 +0200 Subject: [PATCH 02/17] Update schema --- drizzle/0004_perpetual_madelyne_pryor.sql | 10 + drizzle/meta/0004_snapshot.json | 324 ++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + 3 files changed, 341 insertions(+) create mode 100644 drizzle/0004_perpetual_madelyne_pryor.sql create mode 100644 drizzle/meta/0004_snapshot.json diff --git a/drizzle/0004_perpetual_madelyne_pryor.sql b/drizzle/0004_perpetual_madelyne_pryor.sql new file mode 100644 index 00000000..811bcddc --- /dev/null +++ b/drizzle/0004_perpetual_madelyne_pryor.sql @@ -0,0 +1,10 @@ +CREATE TABLE `place_translation` ( + `id` serial AUTO_INCREMENT NOT NULL, + `place_id` serial AUTO_INCREMENT NOT NULL, + `locale` varchar(10) NOT NULL, + `name` text NOT NULL, + CONSTRAINT `place_translation_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +RENAME TABLE `place` TO `place_data`;--> statement-breakpoint +ALTER TABLE `place_data` DROP COLUMN `name`; \ No newline at end of file diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json new file mode 100644 index 00000000..8e14c3e4 --- /dev/null +++ b/drizzle/meta/0004_snapshot.json @@ -0,0 +1,324 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "1dca75ae-bdb7-460a-87f1-ac5f212c63f6", + "prevId": "aea0cf0a-32d1-453e-b0e7-2419b3070b31", + "tables": { + "place_data": { + "name": "place_data", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "mainImage": { + "name": "mainImage", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_data_id": { + "name": "place_data_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "place_translation": { + "name": "place_translation", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "place_id": { + "name": "place_id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "locale": { + "name": "locale", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_translation_id": { + "name": "place_translation_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": { + "\"place\"": "\"place_data\"" + }, + "columns": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index aab3873a..2836ab71 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1698187240633, "tag": "0003_colorful_fat_cobra", "breakpoints": true + }, + { + "idx": 4, + "version": "5", + "when": 1698257912124, + "tag": "0004_perpetual_madelyne_pryor", + "breakpoints": true } ] } \ No newline at end of file From fa3f6145805638a3fceeb4dfd0c17af0a01f4721 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 20:38:16 +0200 Subject: [PATCH 03/17] Fix sql --- src/server/db/schema/places.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/db/schema/places.ts b/src/server/db/schema/places.ts index 8a168c93..6c549880 100644 --- a/src/server/db/schema/places.ts +++ b/src/server/db/schema/places.ts @@ -1,5 +1,5 @@ import { relations } from 'drizzle-orm' -import { mysqlTable, serial, text } from 'drizzle-orm/mysql-core' +import { int, mysqlTable, serial, text } from 'drizzle-orm/mysql-core' import { locale, s3ObjectKey } from '../utilities' export const placesData = mysqlTable('place_data', { @@ -9,7 +9,7 @@ export const placesData = mysqlTable('place_data', { export const placesTranslations = mysqlTable('place_translation', { id: serial('id').primaryKey(), - placeId: serial('place_id').notNull(), + placeId: int('place_id').notNull(), locale: locale('locale').notNull(), name: text('name').notNull(), From bef2f4f2fe5ac7110b677efbb3c5c6c96123934a Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 20:41:31 +0200 Subject: [PATCH 04/17] add env to comands --- .env.example | 4 ---- package.json | 7 ++++--- src/server/db/migrate.mjs | 3 +-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 3bf65e15..4ce47ade 100644 --- a/.env.example +++ b/.env.example @@ -21,10 +21,6 @@ DATABASE_LABEL=stage DATABASE_USERNAME=toBeChanged DATABASE_PASSWORD=toBeChanged -# Local DB -# DATABASE_LABEL=local -# USE_LOCAL_DB=true - # AWS S3 connection settings AWS_ACCESS_KEY_ID=toBeChanged AWS_SECRET_ACCESS_KEY=toBeChanged diff --git a/package.json b/package.json index 52e5f69d..0b219dc0 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,10 @@ "test:e2e:dev": "playwright test --ui", "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", - "db-local:run-app": "USE_LOCAL_DB=true next dev", + "db:studio": "drizzle-kit studio", + "db-local:studio": "USE_LOCAL_DB=true drizzle-kit studio", + "db-local:migrate": "USE_LOCAL_DB=true node ./src/server/db/migrate.mjs", + "db-local:run-app": "USE_LOCAL_DB=true DATABASE_LABEL=local next dev", "db-local:run-db": "docker run --name descobreix-begur-app-database -e MYSQL_ROOT_PASSWORD=unsafePaswordOnlyForLocalhost -e MYSQL_DATABASE=descobreix-begur-app -p 3306:3306 mysql" }, "dependencies": { diff --git a/src/server/db/migrate.mjs b/src/server/db/migrate.mjs index ece9f906..abf36e0d 100644 --- a/src/server/db/migrate.mjs +++ b/src/server/db/migrate.mjs @@ -3,9 +3,8 @@ import { migrate } from 'drizzle-orm/mysql2/migrator' import { drizzle as drizzleMysql } from 'drizzle-orm/mysql2' import { createConnection } from 'mysql2' -import { env } from '../../env.mjs' -if (env.USE_LOCAL_DB !== 'true') { +if (process.env.USE_LOCAL_DB !== 'true') { throw new Error( 'Migrations are only allowed on local database. (this is a custom error)' ) From 2d6174c352da181e4918a012861a1e87cb97d2b3 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 20:41:47 +0200 Subject: [PATCH 05/17] Revert "Update schema" This reverts commit 0b318824732a997893c808172108e804a2443603. --- drizzle/0004_perpetual_madelyne_pryor.sql | 10 - drizzle/meta/0004_snapshot.json | 324 ---------------------- drizzle/meta/_journal.json | 7 - 3 files changed, 341 deletions(-) delete mode 100644 drizzle/0004_perpetual_madelyne_pryor.sql delete mode 100644 drizzle/meta/0004_snapshot.json diff --git a/drizzle/0004_perpetual_madelyne_pryor.sql b/drizzle/0004_perpetual_madelyne_pryor.sql deleted file mode 100644 index 811bcddc..00000000 --- a/drizzle/0004_perpetual_madelyne_pryor.sql +++ /dev/null @@ -1,10 +0,0 @@ -CREATE TABLE `place_translation` ( - `id` serial AUTO_INCREMENT NOT NULL, - `place_id` serial AUTO_INCREMENT NOT NULL, - `locale` varchar(10) NOT NULL, - `name` text NOT NULL, - CONSTRAINT `place_translation_id` PRIMARY KEY(`id`) -); ---> statement-breakpoint -RENAME TABLE `place` TO `place_data`;--> statement-breakpoint -ALTER TABLE `place_data` DROP COLUMN `name`; \ No newline at end of file diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json deleted file mode 100644 index 8e14c3e4..00000000 --- a/drizzle/meta/0004_snapshot.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "1dca75ae-bdb7-460a-87f1-ac5f212c63f6", - "prevId": "aea0cf0a-32d1-453e-b0e7-2419b3070b31", - "tables": { - "place_data": { - "name": "place_data", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": false, - "notNull": true, - "autoincrement": true - }, - "mainImage": { - "name": "mainImage", - "type": "varchar(1024)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "place_data_id": { - "name": "place_data_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {} - }, - "place_translation": { - "name": "place_translation", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": false, - "notNull": true, - "autoincrement": true - }, - "place_id": { - "name": "place_id", - "type": "serial", - "primaryKey": false, - "notNull": true, - "autoincrement": true - }, - "locale": { - "name": "locale", - "type": "varchar(10)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "place_translation_id": { - "name": "place_translation_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": { - "\"place\"": "\"place_data\"" - }, - "columns": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 2836ab71..aab3873a 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -29,13 +29,6 @@ "when": 1698187240633, "tag": "0003_colorful_fat_cobra", "breakpoints": true - }, - { - "idx": 4, - "version": "5", - "when": 1698257912124, - "tag": "0004_perpetual_madelyne_pryor", - "breakpoints": true } ] } \ No newline at end of file From e48dba46abdecf56c11c0b3cc83b4e3660eca4d9 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 20:42:17 +0200 Subject: [PATCH 06/17] Update schema 2 --- drizzle/0004_colorful_black_widow.sql | 10 + drizzle/meta/0004_snapshot.json | 324 ++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + 3 files changed, 341 insertions(+) create mode 100644 drizzle/0004_colorful_black_widow.sql create mode 100644 drizzle/meta/0004_snapshot.json diff --git a/drizzle/0004_colorful_black_widow.sql b/drizzle/0004_colorful_black_widow.sql new file mode 100644 index 00000000..4b3af706 --- /dev/null +++ b/drizzle/0004_colorful_black_widow.sql @@ -0,0 +1,10 @@ +CREATE TABLE `place_translation` ( + `id` serial AUTO_INCREMENT NOT NULL, + `place_id` int NOT NULL, + `locale` varchar(10) NOT NULL, + `name` text NOT NULL, + CONSTRAINT `place_translation_id` PRIMARY KEY(`id`) +); +--> statement-breakpoint +RENAME TABLE `place` TO `place_data`;--> statement-breakpoint +ALTER TABLE `place_data` DROP COLUMN `name`; \ No newline at end of file diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json new file mode 100644 index 00000000..8e5d2e29 --- /dev/null +++ b/drizzle/meta/0004_snapshot.json @@ -0,0 +1,324 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "f499ec69-31cc-41e0-a681-e3259ddd180b", + "prevId": "aea0cf0a-32d1-453e-b0e7-2419b3070b31", + "tables": { + "place_data": { + "name": "place_data", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "mainImage": { + "name": "mainImage", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_data_id": { + "name": "place_data_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "place_translation": { + "name": "place_translation", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "place_id": { + "name": "place_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_translation_id": { + "name": "place_translation_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": { + "\"place\"": "\"place_data\"" + }, + "columns": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index aab3873a..55d9051d 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -29,6 +29,13 @@ "when": 1698187240633, "tag": "0003_colorful_fat_cobra", "breakpoints": true + }, + { + "idx": 4, + "version": "5", + "when": 1698259327196, + "tag": "0004_colorful_black_widow", + "breakpoints": true } ] } \ No newline at end of file From 18a4d5a681d418f5a15604e4c3a8c565ff4d9e44 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 20:44:06 +0200 Subject: [PATCH 07/17] Rename script --- package.json | 8 ++++---- playwright.config.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 0b219dc0..e76198c2 100644 --- a/package.json +++ b/package.json @@ -14,10 +14,10 @@ "db:generate": "SKIP_ENV_VALIDATION=true drizzle-kit generate:mysql", "db:push": "drizzle-kit push:mysql", "db:studio": "drizzle-kit studio", - "db-local:studio": "USE_LOCAL_DB=true drizzle-kit studio", - "db-local:migrate": "USE_LOCAL_DB=true node ./src/server/db/migrate.mjs", - "db-local:run-app": "USE_LOCAL_DB=true DATABASE_LABEL=local next dev", - "db-local:run-db": "docker run --name descobreix-begur-app-database -e MYSQL_ROOT_PASSWORD=unsafePaswordOnlyForLocalhost -e MYSQL_DATABASE=descobreix-begur-app -p 3306:3306 mysql" + "db:local:studio": "USE_LOCAL_DB=true drizzle-kit studio", + "db:local:migrate": "USE_LOCAL_DB=true node ./src/server/db/migrate.mjs", + "db:local:run-app": "USE_LOCAL_DB=true DATABASE_LABEL=local next dev", + "db:local:run-db": "docker run --name descobreix-begur-app-database -e MYSQL_ROOT_PASSWORD=unsafePaswordOnlyForLocalhost -e MYSQL_DATABASE=descobreix-begur-app -p 3306:3306 mysql" }, "dependencies": { "@auth/core": "0.12.0", diff --git a/playwright.config.ts b/playwright.config.ts index 5c7694d8..773ee544 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -59,12 +59,12 @@ export default defineConfig({ /* Run your local dev server before starting the tests */ // webServer: [ // { - // command: 'pnpm db-local:run-app', + // command: 'pnpm db:local:run-app', // url: 'http://localhost:3000', // reuseExistingServer: !process.env.CI, // }, // { - // command: 'pnpm db-local:run-db', + // command: 'pnpm db:local:run-db', // url: 'http://localhost:3306', // reuseExistingServer: !process.env.CI, // }, From 56aca7202682a4d0b36e289c1505fd71c50c24f0 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 21:02:34 +0200 Subject: [PATCH 08/17] Temporary fix type error --- src/server/db/db.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/server/db/db.ts b/src/server/db/db.ts index b08d8e07..996db45e 100644 --- a/src/server/db/db.ts +++ b/src/server/db/db.ts @@ -2,14 +2,17 @@ import 'server-only' import { env } from '~/env.mjs' import { drizzle as drizzleMysql } from 'drizzle-orm/mysql2' -import { drizzle as drizzlePlanetscale } from 'drizzle-orm/planetscale-serverless' +import { + PlanetScaleDatabase, + drizzle as drizzlePlanetscale, +} from 'drizzle-orm/planetscale-serverless' import { connect } from '@planetscale/database' import { createConnection } from 'mysql2' import * as schema from './schema' export const db = env.USE_LOCAL_DB === 'true' - ? drizzleMysql( + ? (drizzleMysql( createConnection({ host: '127.0.0.1', user: 'root', @@ -20,7 +23,7 @@ export const db = schema, mode: 'planetscale', } - ) + ) as unknown as PlanetScaleDatabase) : drizzlePlanetscale( connect({ host: env.DATABASE_HOST, From f8140e291944acbaee90f098a68b43d70ff71521 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 22:12:37 +0200 Subject: [PATCH 09/17] Don't use any --- src/i18n.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n.ts b/src/i18n.ts index cf563a2f..b5cd7f1a 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -11,7 +11,7 @@ export const defaultLocale = 'ca' satisfies (typeof locales)[number] function isLocaleInDatabase( locale: string ): locale is (typeof localesInDatabase)[number] { - return localesInDatabase.includes(locale as any) + return localesInDatabase.some((l) => l === locale) } export function localeOrDefault(locale: T) { From 6725cd08ed4614b4f6653f14eabebfe95f567d40 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 22:50:39 +0200 Subject: [PATCH 10/17] Fix get items not translated --- src/server/api/router/places.ts | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/server/api/router/places.ts b/src/server/api/router/places.ts index 1a7b5271..57303765 100644 --- a/src/server/api/router/places.ts +++ b/src/server/api/router/places.ts @@ -1,13 +1,12 @@ import 'server-only' -import { asc, eq, sql } from 'drizzle-orm' +import { and, asc, eq, notExists, or, sql } from 'drizzle-orm' import { db } from '~/server/db/db' import { placesData, placesTranslations } from '~/server/db/schema/places' import { procedure, router } from '~/server/trpc' import { listPlacesSchema } from '~/schemas/places' +import { defaultLocale } from '~/i18n' -// TODO: Handle when a specific place doesn't have a translation for the given locale while others do. -// Right now what happens is that the place without the locale is not returned in the list const getAllPlaces = db .select({ id: placesData.id, @@ -15,8 +14,26 @@ const getAllPlaces = db name: placesTranslations.name, }) .from(placesData) - .innerJoin(placesTranslations, eq(placesData.id, placesTranslations.placeId)) - .where(eq(placesTranslations.locale, sql.placeholder('locale'))) + .leftJoin(placesTranslations, eq(placesData.id, placesTranslations.placeId)) + .where( + or( + eq(placesTranslations.locale, sql.placeholder('locale')), + and( + eq(placesTranslations.locale, defaultLocale), + notExists( + db + .select() + .from(placesTranslations) + .where( + and( + eq(placesTranslations.placeId, placesData.id), + eq(placesTranslations.locale, sql.placeholder('locale')) + ) + ) + ) + ) + ) + ) .orderBy(asc(placesTranslations.name)) .prepare() From 269e6c51939e27ce36228a1a16ec22b19ed2e2b3 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Wed, 25 Oct 2023 22:51:56 +0200 Subject: [PATCH 11/17] Forgot innerJoin --- src/server/api/router/places.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/api/router/places.ts b/src/server/api/router/places.ts index 57303765..15d07feb 100644 --- a/src/server/api/router/places.ts +++ b/src/server/api/router/places.ts @@ -14,7 +14,7 @@ const getAllPlaces = db name: placesTranslations.name, }) .from(placesData) - .leftJoin(placesTranslations, eq(placesData.id, placesTranslations.placeId)) + .innerJoin(placesTranslations, eq(placesData.id, placesTranslations.placeId)) .where( or( eq(placesTranslations.locale, sql.placeholder('locale')), From 137c86f879b05a9aa106ffa7fb344bc466293e99 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Thu, 26 Oct 2023 04:15:12 +0200 Subject: [PATCH 12/17] Typescript magic --- drizzle/0005_known_vapor.sql | 2 + drizzle/meta/0005_snapshot.json | 331 +++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/helpers/types.ts | 3 + src/server/api/router/places.ts | 44 ++-- src/server/db/schema/places.ts | 45 ++-- src/server/helpers/translations.ts | 158 ++++++++++++++ 7 files changed, 538 insertions(+), 52 deletions(-) create mode 100644 drizzle/0005_known_vapor.sql create mode 100644 drizzle/meta/0005_snapshot.json create mode 100644 src/helpers/types.ts create mode 100644 src/server/helpers/translations.ts diff --git a/drizzle/0005_known_vapor.sql b/drizzle/0005_known_vapor.sql new file mode 100644 index 00000000..31e21c7d --- /dev/null +++ b/drizzle/0005_known_vapor.sql @@ -0,0 +1,2 @@ +RENAME TABLE `place_data` TO `place`;--> statement-breakpoint +ALTER TABLE `place` ADD `name` text NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json new file mode 100644 index 00000000..f465f636 --- /dev/null +++ b/drizzle/meta/0005_snapshot.json @@ -0,0 +1,331 @@ +{ + "version": "5", + "dialect": "mysql", + "id": "2d504b13-ac16-4b2a-871b-e766f1da987c", + "prevId": "f499ec69-31cc-41e0-a681-e3259ddd180b", + "tables": { + "place": { + "name": "place", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "mainImage": { + "name": "mainImage", + "type": "varchar(1024)", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_id": { + "name": "place_id", + "columns": [ + "id" + ] + } + }, + "uniqueConstraints": {} + }, + "place_translation": { + "name": "place_translation", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": false, + "notNull": true, + "autoincrement": true + }, + "place_id": { + "name": "place_id", + "type": "int", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "locale": { + "name": "locale", + "type": "varchar(10)", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "place_translation_id": { + "name": "place_translation_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": { + "\"place_data\"": "\"place\"" + }, + "columns": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 55d9051d..f464b233 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -36,6 +36,13 @@ "when": 1698259327196, "tag": "0004_colorful_black_widow", "breakpoints": true + }, + { + "idx": 5, + "version": "5", + "when": 1698286460431, + "tag": "0005_known_vapor", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/helpers/types.ts b/src/helpers/types.ts new file mode 100644 index 00000000..7ebc71ea --- /dev/null +++ b/src/helpers/types.ts @@ -0,0 +1,3 @@ +export type Prettify = { + [K in keyof T]: T[K] +} & {} diff --git a/src/server/api/router/places.ts b/src/server/api/router/places.ts index 15d07feb..24134337 100644 --- a/src/server/api/router/places.ts +++ b/src/server/api/router/places.ts @@ -1,37 +1,32 @@ import 'server-only' -import { and, asc, eq, notExists, or, sql } from 'drizzle-orm' +import { asc, eq, isNull, or, sql } from 'drizzle-orm' +import { listPlacesSchema } from '~/schemas/places' import { db } from '~/server/db/db' -import { placesData, placesTranslations } from '~/server/db/schema/places' +import { places, placesTranslations } from '~/server/db/schema/places' import { procedure, router } from '~/server/trpc' -import { listPlacesSchema } from '~/schemas/places' -import { defaultLocale } from '~/i18n' +import { + flattenTranslations, + selectTranslations, +} from '../../helpers/translations' const getAllPlaces = db .select({ - id: placesData.id, - mainImage: placesData.mainImage, - name: placesTranslations.name, + id: places.id, + mainImage: places.mainImage, + + ...selectTranslations({ + fields: ['name'], + normalTable: places, + translationsTable: placesTranslations, + }), }) - .from(placesData) - .innerJoin(placesTranslations, eq(placesData.id, placesTranslations.placeId)) + .from(places) + .leftJoin(placesTranslations, eq(places.id, placesTranslations.placeId)) .where( or( eq(placesTranslations.locale, sql.placeholder('locale')), - and( - eq(placesTranslations.locale, defaultLocale), - notExists( - db - .select() - .from(placesTranslations) - .where( - and( - eq(placesTranslations.placeId, placesData.id), - eq(placesTranslations.locale, sql.placeholder('locale')) - ) - ) - ) - ) + isNull(placesTranslations.locale) ) ) .orderBy(asc(placesTranslations.name)) @@ -39,6 +34,7 @@ const getAllPlaces = db export const placesRouter = router({ list: procedure.input(listPlacesSchema).query(async ({ input }) => { - return await getAllPlaces.execute({ locale: input.locale }) + const result = await getAllPlaces.execute({ locale: input.locale }) + return result.map(flattenTranslations) }), }) diff --git a/src/server/db/schema/places.ts b/src/server/db/schema/places.ts index 6c549880..1be70299 100644 --- a/src/server/db/schema/places.ts +++ b/src/server/db/schema/places.ts @@ -1,30 +1,19 @@ -import { relations } from 'drizzle-orm' -import { int, mysqlTable, serial, text } from 'drizzle-orm/mysql-core' -import { locale, s3ObjectKey } from '../utilities' +import { serial, text } from 'drizzle-orm/mysql-core' +import { mysqlTableWithTranslations } from '../../helpers/translations' +import { s3ObjectKey } from '../utilities' -export const placesData = mysqlTable('place_data', { - id: serial('id').primaryKey(), - mainImage: s3ObjectKey('mainImage'), +export const { + normalTable: places, + translationsTable: placesTranslations, + normalTableRelations: placesDataRelations, + translationsTableRelations: placesTranslationsRelations, +} = mysqlTableWithTranslations({ + name: 'place', + normalColumns: { + id: serial('id').primaryKey(), + mainImage: s3ObjectKey('mainImage'), + }, + translatableColumns: { + name: text('name').notNull(), + }, }) - -export const placesTranslations = mysqlTable('place_translation', { - id: serial('id').primaryKey(), - placeId: int('place_id').notNull(), - locale: locale('locale').notNull(), - - name: text('name').notNull(), -}) - -export const placesDataRelations = relations(placesData, ({ many }) => ({ - translations: many(placesTranslations), -})) - -export const placesTranslationsRelations = relations( - placesTranslations, - ({ one }) => ({ - data: one(placesData, { - fields: [placesTranslations.placeId], - references: [placesData.id], - }), - }) -) diff --git a/src/server/helpers/translations.ts b/src/server/helpers/translations.ts new file mode 100644 index 00000000..110f0560 --- /dev/null +++ b/src/server/helpers/translations.ts @@ -0,0 +1,158 @@ +import { relations } from 'drizzle-orm' +import { + MySqlColumn, + MySqlColumnBuilderBase, + MySqlTableWithColumns, + int, + mysqlTable, + serial, +} from 'drizzle-orm/mysql-core' +import { Prettify } from '../../helpers/types' +import { locale } from '../db/utilities' + +/** + * Places the fields in the translatedContent object into the root level. + * If there is no translated content, it fallbacks to originalContent. + */ +export function flattenTranslations< + O extends Record, + T extends O | null, + R extends Record, +>({ + translatedContent, + originalContent, + ...otherFields +}: R & { + translatedContent: T + originalContent: O +}) { + const result = { + ...otherFields, + ...originalContent, + ...translatedContent, + } + const prettyResult: Prettify = result + return prettyResult +} + +interface CustomTableConfig< + KeyTableConfig extends string, + TColumn extends MySqlColumn = MySqlColumn, +> { + name: string + schema: string | undefined + columns: Record + dialect: string +} + +/** + * Selects the translatable fields from the normal table and the translations table + * @param fields - Fields to select + * @param normalTable - Normal table + * @param translationsTable - Translations table + * + * @example Example usage of selecting fields `image` and `name`. + * db + * .select({ + * id: normalTable.id, + * image: normalTable.image, + * + * ...selectTranslations({ + * fields: ['name'], + * normalTable: normalTable, + * translationsTable: translationsTable, + * }), + * }) + * .from(normalTable) + * .leftJoin(translationsTable, eq(normalTable.id, translationsTable.normalTableItemId)) + * .where( + * or( + * eq(translationsTable.locale, sql.placeholder('locale')), + * isNull(translationsTable.locale) + * ) + * ) + * .prepare() + */ +export function selectTranslations< + K extends string, + TTC extends CustomTableConfig, + TT extends MySqlTableWithColumns, + DTC extends CustomTableConfig, + DT extends MySqlTableWithColumns, +>({ + fields, + normalTable, + translationsTable, +}: { + fields: readonly K[] + normalTable: DT + translationsTable: TT +}) { + return { + originalContent: Object.fromEntries( + fields.map((field) => [field, normalTable[field]]) + ) as { [key in K]: DT[K] }, + translatedContent: Object.fromEntries( + fields.map((field) => [field, translationsTable[field]]) + ) as { [key in K]: TT[K] }, + } +} + +function typeDynamicKey(key: K, value: V) { + return { [key]: value } as Record +} + +/** + * Creates tables for the translations and it's relations + * + * normalTable - Table with normal and translatable columns + * + * translationsTable - Table with just translatable columns + * + * normalTableRelations - Relations for the normal table + * + * translationsTableRelations - Relations for the translations table + * + */ +export function mysqlTableWithTranslations< + Name extends string, + NC extends Record, + TC extends Record, +>({ + name, + normalColumns, + translatableColumns, +}: { + name: Name + normalColumns: NC + translatableColumns: TC +}) { + const normalTable = mysqlTable(name, { + ...normalColumns, + ...translatableColumns, + }) + const translationsTable = mysqlTable(`${name}_translation`, { + id: serial('id').primaryKey(), + ...typeDynamicKey(`${name}Id`, int(`${name}_id`).notNull()), + locale: locale('locale').notNull(), + ...translatableColumns, + }) + const normalTableRelations = relations(normalTable, ({ many }) => ({ + translations: many(translationsTable), + })) + const translationsTableRelations = relations( + translationsTable, + ({ one }) => ({ + data: one(normalTable, { + fields: [translationsTable[`${name}Id` as string]], + references: [normalTable.id], + }), + }) + ) + return { + normalTable, + translationsTable, + normalTableRelations, + translationsTableRelations, + } +} From 9e6a7b21dce5b56c233072209cbb7dad9c54643e Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Thu, 26 Oct 2023 04:17:05 +0200 Subject: [PATCH 13/17] Merge migrations --- ..._widow.sql => 0004_curvy_colleen_wing.sql} | 3 - drizzle/0005_known_vapor.sql | 2 - drizzle/meta/0004_snapshot.json | 21 +- drizzle/meta/0005_snapshot.json | 331 ------------------ drizzle/meta/_journal.json | 11 +- 5 files changed, 15 insertions(+), 353 deletions(-) rename drizzle/{0004_colorful_black_widow.sql => 0004_curvy_colleen_wing.sql} (61%) delete mode 100644 drizzle/0005_known_vapor.sql delete mode 100644 drizzle/meta/0005_snapshot.json diff --git a/drizzle/0004_colorful_black_widow.sql b/drizzle/0004_curvy_colleen_wing.sql similarity index 61% rename from drizzle/0004_colorful_black_widow.sql rename to drizzle/0004_curvy_colleen_wing.sql index 4b3af706..a4cd8e89 100644 --- a/drizzle/0004_colorful_black_widow.sql +++ b/drizzle/0004_curvy_colleen_wing.sql @@ -5,6 +5,3 @@ CREATE TABLE `place_translation` ( `name` text NOT NULL, CONSTRAINT `place_translation_id` PRIMARY KEY(`id`) ); ---> statement-breakpoint -RENAME TABLE `place` TO `place_data`;--> statement-breakpoint -ALTER TABLE `place_data` DROP COLUMN `name`; \ No newline at end of file diff --git a/drizzle/0005_known_vapor.sql b/drizzle/0005_known_vapor.sql deleted file mode 100644 index 31e21c7d..00000000 --- a/drizzle/0005_known_vapor.sql +++ /dev/null @@ -1,2 +0,0 @@ -RENAME TABLE `place_data` TO `place`;--> statement-breakpoint -ALTER TABLE `place` ADD `name` text NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json index 8e5d2e29..0775ac2d 100644 --- a/drizzle/meta/0004_snapshot.json +++ b/drizzle/meta/0004_snapshot.json @@ -1,11 +1,11 @@ { "version": "5", "dialect": "mysql", - "id": "f499ec69-31cc-41e0-a681-e3259ddd180b", + "id": "5ab66feb-1129-4822-99c3-4e3bb4ac5de2", "prevId": "aea0cf0a-32d1-453e-b0e7-2419b3070b31", "tables": { - "place_data": { - "name": "place_data", + "place": { + "name": "place", "columns": { "id": { "name": "id", @@ -20,13 +20,20 @@ "primaryKey": false, "notNull": false, "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false } }, "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": { - "place_data_id": { - "name": "place_data_id", + "place_id": { + "name": "place_id", "columns": [ "id" ] @@ -316,9 +323,7 @@ "schemas": {}, "_meta": { "schemas": {}, - "tables": { - "\"place\"": "\"place_data\"" - }, + "tables": {}, "columns": {} } } \ No newline at end of file diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json deleted file mode 100644 index f465f636..00000000 --- a/drizzle/meta/0005_snapshot.json +++ /dev/null @@ -1,331 +0,0 @@ -{ - "version": "5", - "dialect": "mysql", - "id": "2d504b13-ac16-4b2a-871b-e766f1da987c", - "prevId": "f499ec69-31cc-41e0-a681-e3259ddd180b", - "tables": { - "place": { - "name": "place", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": false, - "notNull": true, - "autoincrement": true - }, - "mainImage": { - "name": "mainImage", - "type": "varchar(1024)", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "place_id": { - "name": "place_id", - "columns": [ - "id" - ] - } - }, - "uniqueConstraints": {} - }, - "place_translation": { - "name": "place_translation", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": false, - "notNull": true, - "autoincrement": true - }, - "place_id": { - "name": "place_id", - "type": "int", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "locale": { - "name": "locale", - "type": "varchar(10)", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": { - "place_translation_id": { - "name": "place_translation_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": { - "\"place_data\"": "\"place\"" - }, - "columns": {} - } -} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index f464b233..b05c3342 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -33,15 +33,8 @@ { "idx": 4, "version": "5", - "when": 1698259327196, - "tag": "0004_colorful_black_widow", - "breakpoints": true - }, - { - "idx": 5, - "version": "5", - "when": 1698286460431, - "tag": "0005_known_vapor", + "when": 1698286606173, + "tag": "0004_curvy_colleen_wing", "breakpoints": true } ] From 50717e45331d4e6f68aaab2ad8167e7c74b37544 Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Thu, 26 Oct 2023 05:05:03 +0200 Subject: [PATCH 14/17] Fix missing catalan translations --- src/server/api/router/places.ts | 21 ++++++++++++--------- src/server/helpers/translations.ts | 20 +++----------------- 2 files changed, 15 insertions(+), 26 deletions(-) diff --git a/src/server/api/router/places.ts b/src/server/api/router/places.ts index 24134337..960440b6 100644 --- a/src/server/api/router/places.ts +++ b/src/server/api/router/places.ts @@ -1,6 +1,6 @@ import 'server-only' -import { asc, eq, isNull, or, sql } from 'drizzle-orm' +import { asc, eq, sql } from 'drizzle-orm' import { listPlacesSchema } from '~/schemas/places' import { db } from '~/server/db/db' import { places, placesTranslations } from '~/server/db/schema/places' @@ -10,6 +10,12 @@ import { selectTranslations, } from '../../helpers/translations' +const placeTranslationsInLocale = db + .select() + .from(placesTranslations) + .where(eq(placesTranslations.locale, sql.placeholder('locale'))) + .as('placeTranslationsInLocale') + const getAllPlaces = db .select({ id: places.id, @@ -18,18 +24,15 @@ const getAllPlaces = db ...selectTranslations({ fields: ['name'], normalTable: places, - translationsTable: placesTranslations, + translationsTable: placeTranslationsInLocale, }), }) .from(places) - .leftJoin(placesTranslations, eq(places.id, placesTranslations.placeId)) - .where( - or( - eq(placesTranslations.locale, sql.placeholder('locale')), - isNull(placesTranslations.locale) - ) + .leftJoin( + placeTranslationsInLocale, + eq(places.id, placeTranslationsInLocale.placeId) ) - .orderBy(asc(placesTranslations.name)) + .orderBy(asc(placeTranslationsInLocale.name)) .prepare() export const placesRouter = router({ diff --git a/src/server/helpers/translations.ts b/src/server/helpers/translations.ts index 110f0560..630be58c 100644 --- a/src/server/helpers/translations.ts +++ b/src/server/helpers/translations.ts @@ -1,8 +1,6 @@ import { relations } from 'drizzle-orm' import { - MySqlColumn, MySqlColumnBuilderBase, - MySqlTableWithColumns, int, mysqlTable, serial, @@ -35,16 +33,6 @@ export function flattenTranslations< return prettyResult } -interface CustomTableConfig< - KeyTableConfig extends string, - TColumn extends MySqlColumn = MySqlColumn, -> { - name: string - schema: string | undefined - columns: Record - dialect: string -} - /** * Selects the translatable fields from the normal table and the translations table * @param fields - Fields to select @@ -74,11 +62,9 @@ interface CustomTableConfig< * .prepare() */ export function selectTranslations< - K extends string, - TTC extends CustomTableConfig, - TT extends MySqlTableWithColumns, - DTC extends CustomTableConfig, - DT extends MySqlTableWithColumns, + K extends Exclude, + TT extends Record, + DT extends Record, >({ fields, normalTable, From 72cbd7adf6917ba4b132534ff83fb5df1edbcfbb Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Thu, 26 Oct 2023 05:21:31 +0200 Subject: [PATCH 15/17] Change translatable locales --- .../(app)/explore/_components/place-list.tsx | 4 ++-- src/helpers/types.ts | 2 +- src/i18n.ts | 23 ++++++++----------- src/schemas/places.ts | 4 ++-- src/server/db/utilities.ts | 4 ++-- src/server/helpers/translations.ts | 1 + 6 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/app/[locale]/(app)/explore/_components/place-list.tsx b/src/app/[locale]/(app)/explore/_components/place-list.tsx index 0d8caf0a..c0d7d048 100644 --- a/src/app/[locale]/(app)/explore/_components/place-list.tsx +++ b/src/app/[locale]/(app)/explore/_components/place-list.tsx @@ -5,7 +5,7 @@ import { Image } from '@nextui-org/image' import { useLocale, useTranslations } from 'next-intl' import type { FC, HTMLAttributes } from 'react' import { cn } from '~/helpers/cn' -import { localeOrDefault } from '~/i18n' +import { onlyTranslatableLocales } from '~/i18n' import { trpc } from '~/trpc' function makeImageUrl(s3key: T | null) { @@ -23,7 +23,7 @@ export const PlaceList: FC, 'children'>> = ({ const locale = useLocale() const { data: places, isInitialLoading } = trpc.places.list.useQuery({ - locale: localeOrDefault(locale), + locale: onlyTranslatableLocales(locale), }) if (isInitialLoading) { diff --git a/src/helpers/types.ts b/src/helpers/types.ts index 7ebc71ea..47632080 100644 --- a/src/helpers/types.ts +++ b/src/helpers/types.ts @@ -1,3 +1,3 @@ export type Prettify = { [K in keyof T]: T[K] -} & {} +} & unknown diff --git a/src/i18n.ts b/src/i18n.ts index b5cd7f1a..166251ad 100644 --- a/src/i18n.ts +++ b/src/i18n.ts @@ -1,21 +1,18 @@ -export const localesInDatabase = [ - 'ca', - 'en', +export const translatableLocales = ['en'] as const satisfies readonly string[] +export const defaultLocale = 'ca' satisfies string +export const locales = [ + defaultLocale, + ...translatableLocales, ] as const satisfies readonly string[] -export const locales = ['ca', 'en'] as const satisfies readonly [ - ...typeof localesInDatabase, - ...string[], -] -export const defaultLocale = 'ca' satisfies (typeof locales)[number] -function isLocaleInDatabase( +function isTranslatableLocale( locale: string -): locale is (typeof localesInDatabase)[number] { - return localesInDatabase.some((l) => l === locale) +): locale is (typeof translatableLocales)[number] { + return translatableLocales.some((l) => l === locale) } -export function localeOrDefault(locale: T) { - return isLocaleInDatabase(locale) ? locale : defaultLocale +export function onlyTranslatableLocales(locale: T) { + return isTranslatableLocale(locale) ? locale : null } const localePathPattern = /^\/(?[^/\s]+)/ diff --git a/src/schemas/places.ts b/src/schemas/places.ts index e255fca2..52d1a7ce 100644 --- a/src/schemas/places.ts +++ b/src/schemas/places.ts @@ -1,8 +1,8 @@ import { z } from 'zod' -import { localesInDatabase } from '~/i18n' +import { translatableLocales } from '~/i18n' export const listPlacesSchema = z.object({ - locale: z.enum(localesInDatabase), + locale: z.enum(translatableLocales).nullable(), }) export type ListPlacesInputData = z.infer diff --git a/src/server/db/utilities.ts b/src/server/db/utilities.ts index 999107fc..a12a294b 100644 --- a/src/server/db/utilities.ts +++ b/src/server/db/utilities.ts @@ -1,8 +1,8 @@ import { varchar } from 'drizzle-orm/mysql-core' -import { localesInDatabase } from '../../i18n' +import { translatableLocales } from '../../i18n' export const s3ObjectKey = (name: T) => varchar(name, { length: 1024 }) export const locale = (name: T) => - varchar(name, { length: 10, enum: localesInDatabase }) + varchar(name, { length: 10, enum: translatableLocales }) diff --git a/src/server/helpers/translations.ts b/src/server/helpers/translations.ts index 630be58c..4f1d837f 100644 --- a/src/server/helpers/translations.ts +++ b/src/server/helpers/translations.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { relations } from 'drizzle-orm' import { MySqlColumnBuilderBase, From b2b1eddf2e488c158d8ee00e2e62def77afe123e Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Thu, 26 Oct 2023 05:25:33 +0200 Subject: [PATCH 16/17] Add comment issue --- src/server/db/db.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/server/db/db.ts b/src/server/db/db.ts index 996db45e..38ee1529 100644 --- a/src/server/db/db.ts +++ b/src/server/db/db.ts @@ -23,6 +23,8 @@ export const db = schema, mode: 'planetscale', } + // TODO: Remove this unsafe type cast when this issue is fixed: + // https://github.com/drizzle-team/drizzle-orm/issues/1129 ) as unknown as PlanetScaleDatabase) : drizzlePlanetscale( connect({ From 6964e6c3c62ffb12c7c92a8bd958157fda99ff8a Mon Sep 17 00:00:00 2001 From: mauriciabad Date: Thu, 26 Oct 2023 05:29:04 +0200 Subject: [PATCH 17/17] Include Id in mysqlTableWithTranslations --- src/server/db/schema/places.ts | 3 +-- src/server/helpers/translations.ts | 13 +++++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/src/server/db/schema/places.ts b/src/server/db/schema/places.ts index 1be70299..386e74df 100644 --- a/src/server/db/schema/places.ts +++ b/src/server/db/schema/places.ts @@ -1,4 +1,4 @@ -import { serial, text } from 'drizzle-orm/mysql-core' +import { text } from 'drizzle-orm/mysql-core' import { mysqlTableWithTranslations } from '../../helpers/translations' import { s3ObjectKey } from '../utilities' @@ -10,7 +10,6 @@ export const { } = mysqlTableWithTranslations({ name: 'place', normalColumns: { - id: serial('id').primaryKey(), mainImage: s3ObjectKey('mainImage'), }, translatableColumns: { diff --git a/src/server/helpers/translations.ts b/src/server/helpers/translations.ts index 4f1d837f..3ae5a364 100644 --- a/src/server/helpers/translations.ts +++ b/src/server/helpers/translations.ts @@ -90,15 +90,11 @@ function typeDynamicKey(key: K, value: V) { } /** - * Creates tables for the translations and it's relations + * Creates tables for the translations and it's relations. + * The id columns is added automatically. * - * normalTable - Table with normal and translatable columns - * - * translationsTable - Table with just translatable columns - * - * normalTableRelations - Relations for the normal table - * - * translationsTableRelations - Relations for the translations table + * The normalTable contains normal and translatable columns + * The translationsTable contains just translatable columns * */ export function mysqlTableWithTranslations< @@ -115,6 +111,7 @@ export function mysqlTableWithTranslations< translatableColumns: TC }) { const normalTable = mysqlTable(name, { + id: serial('id').primaryKey(), ...normalColumns, ...translatableColumns, })