From 92107ba071858b5a25b4dc6ad7a7e709d9ad6f84 Mon Sep 17 00:00:00 2001 From: chev Date: Sun, 21 Jun 2026 13:41:34 -0500 Subject: [PATCH 1/4] feat: add shared profile field validation helpers --- src/constants/countries.ts | 3 +++ src/lib/profileFields.ts | 47 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/lib/profileFields.ts diff --git a/src/constants/countries.ts b/src/constants/countries.ts index adc22b6..b2b738e 100644 --- a/src/constants/countries.ts +++ b/src/constants/countries.ts @@ -371,6 +371,9 @@ export const countries: Country[] = [ { code: 'ZW', name_en: 'Zimbabwe', name_ja: 'ジンバブエ' }, ]; +export const isValidCountryCode = (code: string): boolean => + /^[A-Z]{2}$/.test(code); + export const countryOptions = (locale: string) => { const nameKey = `name_${locale}` as `name_${string}`; const fallbackKey: `name_${string}` = 'name_en'; diff --git a/src/lib/profileFields.ts b/src/lib/profileFields.ts new file mode 100644 index 0000000..a512d0e --- /dev/null +++ b/src/lib/profileFields.ts @@ -0,0 +1,47 @@ +import { z } from 'zod'; +import { isValidCountryCode } from '@/constants/countries'; +import { isValidLanguageCode } from '@/constants/languages'; + +export const BIO_MIN = 10; +export const BIO_MAX = 500; +export const TAG_MIN = 2; +export const TAG_MAX = 20; + +export const languageCodeField = (messages: { + required?: string; + invalid: string; +}) => { + const base = messages.required + ? z.string().min(1, { message: messages.required }) + : z.string(); + return base.refine(isValidLanguageCode, { message: messages.invalid }); +}; + +export const optionalCountryField = (message: string) => + z + .string() + .refine((value) => !value || isValidCountryCode(value), { message }) + .optional(); + +export const bioField = (messages: { + required?: string; + tooShort: string; + tooLong: string; +}) => { + const base = messages.required + ? z.string().trim().min(1, { message: messages.required }) + : z.string().trim(); + return base + .min(BIO_MIN, { message: messages.tooShort }) + .max(BIO_MAX, { message: messages.tooLong }); +}; + +export const tagItemField = (messages: { tooShort: string; tooLong: string }) => + z + .string() + .trim() + .min(TAG_MIN, { message: messages.tooShort }) + .max(TAG_MAX, { message: messages.tooLong }); + +export const hasUniqueTags = (items: string[]): boolean => + new Set(items.map((item) => item.toLowerCase())).size === items.length; From 7eda1f1909a0f820d6b8512c2db9aeecacd10403 Mon Sep 17 00:00:00 2001 From: chev Date: Sun, 21 Jun 2026 13:41:42 -0500 Subject: [PATCH 2/4] fix: validate profile language, country, bio, and tags consistently --- src/features/Profile/schema.ts | 44 ++++++++++++++++++---------------- src/locales/en.json | 3 +++ src/locales/ja.json | 3 +++ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/features/Profile/schema.ts b/src/features/Profile/schema.ts index d9696df..898c26a 100644 --- a/src/features/Profile/schema.ts +++ b/src/features/Profile/schema.ts @@ -1,5 +1,12 @@ import { z } from 'zod'; import { isValidIANATimezone, isValidProficiency } from '@/constants/languages'; +import { + bioField, + hasUniqueTags, + languageCodeField, + optionalCountryField, + tagItemField, +} from '@/lib/profileFields'; export const FREE_LANGUAGE_CAP = 2; export const PREMIUM_LANGUAGE_CAP = 10; @@ -12,12 +19,18 @@ export const profileSchema = z.object({ displayTimezone: z.boolean(), displayAvailability: z.boolean(), - primaryLanguage: z.string().min(1, { message: 'primaryLanguageRequired' }), + primaryLanguage: languageCodeField({ + required: 'primaryLanguageRequired', + invalid: 'primaryLanguageInvalid', + }), targetLanguages: z .array( z.object({ - language: z.string().min(1, { message: 'targetLanguageRequired' }), + language: languageCodeField({ + required: 'targetLanguageRequired', + invalid: 'targetLanguageInvalid', + }), level: z .string() .min(1, { message: 'proficiencyLevelRequired' }) @@ -36,11 +49,11 @@ export const profileSchema = z.object({ { message: 'duplicateLanguage' }, ), - bio: z - .string() - .min(1, { message: 'bioRequired' }) - .min(10, { message: 'bioTooShort' }) - .max(500, { message: 'bioTooLong' }), + bio: bioField({ + required: 'bioRequired', + tooShort: 'bioTooShort', + tooLong: 'bioTooLong', + }), availability: z .object({ @@ -53,23 +66,12 @@ export const profileSchema = z.object({ .optional(), tags: z - .array( - z - .string() - .min(2, { message: 'tagTooShort' }) - .max(20, { message: 'tagTooLong' }), - ) + .array(tagItemField({ tooShort: 'tagTooShort', tooLong: 'tagTooLong' })) .max(8, { message: 'maxTags' }) - .refine( - (items) => - new Set(items.map((item) => item.toLowerCase())).size === items.length, - { - message: 'duplicateTag', - }, - ) + .refine(hasUniqueTags, { message: 'duplicateTag' }) .optional(), - country: z.string().optional(), + country: optionalCountryField('countryInvalid'), voiceIntroSeconds: z.number().int().min(0).max(20).optional(), diff --git a/src/locales/en.json b/src/locales/en.json index 27be4cd..23ad9de 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -59,7 +59,9 @@ "targetLanguageLabel": "Target Language", "languageSelectPlaceholder": "Enter a language...", "primaryLanguageRequired": "Please select your primary language.", + "primaryLanguageInvalid": "Please select a valid primary language.", "targetLanguageRequired": "Please select your target language.", + "targetLanguageInvalid": "Please select a valid target language.", "targetLanguagesLabel": "Target Languages", "addLanguage": "Add a language", "removeLanguage": "Remove language", @@ -78,6 +80,7 @@ "proficiencyOptionNativeLevel": "Native-level", "countryLabel": "Country", "countryPlaceholder": "Enter a country...", + "countryInvalid": "Please choose a valid country.", "timezoneLabel": "Timezone", "aboutMe": "About Me", "aboutMeDescription": "Keep this specific enough that someone knows why to message you.", diff --git a/src/locales/ja.json b/src/locales/ja.json index 3243d39..a62c3fc 100644 --- a/src/locales/ja.json +++ b/src/locales/ja.json @@ -59,7 +59,9 @@ "targetLanguageLabel": "学習言語", "languageSelectPlaceholder": "言語を入力...", "primaryLanguageRequired": "主要言語を入力ください。", + "primaryLanguageInvalid": "有効な主要言語を選択してください。", "targetLanguageRequired": "学習言語を入力てください。", + "targetLanguageInvalid": "有効な学習言語を選択してください。", "targetLanguagesLabel": "学習言語", "addLanguage": "言語を追加", "removeLanguage": "言語を削除", @@ -78,6 +80,7 @@ "proficiencyOptionNativeLevel": "ネイティブレベル", "countryLabel": "出身国", "countryPlaceholder": "国を入力...", + "countryInvalid": "有効な国を選択してください。", "timezoneLabel": "タイムゾーン", "aboutMe": "自己紹介", "aboutMeDescription": "相手がメッセージを送りやすいように、具体的に書きましょう。", From ad9ad46095ec3da0c8a9cda623d220a38d0420b5 Mon Sep 17 00:00:00 2001 From: chev Date: Sun, 21 Jun 2026 13:41:43 -0500 Subject: [PATCH 3/4] refactor: use shared field helpers in onboarding schema --- src/features/Onboarding/schema.ts | 47 ++++++++++++++----------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/features/Onboarding/schema.ts b/src/features/Onboarding/schema.ts index 943b0f9..dee226c 100644 --- a/src/features/Onboarding/schema.ts +++ b/src/features/Onboarding/schema.ts @@ -3,8 +3,14 @@ import { type Availability, availabilityValues, isValidIANATimezone, - isValidLanguageCode, } from '@/constants/languages'; +import { + bioField, + hasUniqueTags, + languageCodeField, + optionalCountryField, + tagItemField, +} from '@/lib/profileFields'; const proficiencyValues = [ 'beginner', @@ -14,11 +20,11 @@ const proficiencyValues = [ ] as const; export const onboardingSchema = z.object({ - primaryLanguage: z.string().refine(isValidLanguageCode, { - message: 'Please select your primary language.', + primaryLanguage: languageCodeField({ + invalid: 'Please select your primary language.', }), - targetLanguage: z.string().refine(isValidLanguageCode, { - message: 'Please select the language you want to practice.', + targetLanguage: languageCodeField({ + invalid: 'Please select the language you want to practice.', }), proficiencyLevel: z.enum(proficiencyValues, { message: 'Please select your current level.', @@ -32,31 +38,20 @@ export const onboardingSchema = z.object({ message: 'Please choose when you are usually available.', }, ), - bio: z - .string() - .trim() - .min(10, { message: 'Write at least 10 characters about yourself.' }) - .max(500, { message: 'Keep your bio under 500 characters.' }), - country: z - .string() - .refine((value) => !value || /^[A-Z]{2}$/.test(value), { - message: 'Please choose a valid country.', - }) - .optional(), + bio: bioField({ + tooShort: 'Write at least 10 characters about yourself.', + tooLong: 'Keep your bio under 500 characters.', + }), + country: optionalCountryField('Please choose a valid country.'), tags: z .array( - z - .string() - .trim() - .min(2, { message: 'Tags must be at least 2 characters.' }) - .max(20, { message: 'Tags must be 20 characters or fewer.' }), + tagItemField({ + tooShort: 'Tags must be at least 2 characters.', + tooLong: 'Tags must be 20 characters or fewer.', + }), ) .max(6, { message: 'You can add up to 6 tags.' }) - .refine( - (items) => - new Set(items.map((item) => item.toLowerCase())).size === items.length, - { message: 'Each tag can only be added once.' }, - ) + .refine(hasUniqueTags, { message: 'Each tag can only be added once.' }) .optional(), }); From 90f6f85e3957a1289b009ba7ce0d34a258863927 Mon Sep 17 00:00:00 2001 From: chev Date: Sun, 21 Jun 2026 13:41:45 -0500 Subject: [PATCH 4/4] fix: raise profile tag database limit to match premium cap --- drizzle/0006_strong_post.sql | 2 + drizzle/meta/0006_snapshot.json | 624 ++++++++++++++++++++++++++++++++ drizzle/meta/_journal.json | 7 + src/db/schema.ts | 2 +- 4 files changed, 634 insertions(+), 1 deletion(-) create mode 100644 drizzle/0006_strong_post.sql create mode 100644 drizzle/meta/0006_snapshot.json diff --git a/drizzle/0006_strong_post.sql b/drizzle/0006_strong_post.sql new file mode 100644 index 0000000..c8a78f4 --- /dev/null +++ b/drizzle/0006_strong_post.sql @@ -0,0 +1,2 @@ +ALTER TABLE "profiles" DROP CONSTRAINT "profiles_tags_limit_check";--> statement-breakpoint +ALTER TABLE "profiles" ADD CONSTRAINT "profiles_tags_limit_check" CHECK (cardinality("profiles"."tags") <= 8); \ No newline at end of file diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json new file mode 100644 index 0000000..6d6ac58 --- /dev/null +++ b/drizzle/meta/0006_snapshot.json @@ -0,0 +1,624 @@ +{ + "id": "dd63857d-8b83-4444-958d-d41d096f8bbe", + "prevId": "b389cb57-c04b-4c8e-be35-b9780eac65db", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.profile_target_languages": { + "name": "profile_target_languages", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "language": { + "name": "language", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "proficiency_level": { + "name": "proficiency_level", + "type": "proficiency_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "position": { + "name": "position", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": { + "profile_target_languages_profile_id_idx": { + "name": "profile_target_languages_profile_id_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "profile_target_languages_language_idx": { + "name": "profile_target_languages_language_idx", + "columns": [ + { + "expression": "language", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "profile_target_languages_profile_language_idx": { + "name": "profile_target_languages_profile_language_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "language", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "profile_target_languages_profile_position_idx": { + "name": "profile_target_languages_profile_position_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "position", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "profile_target_languages_profile_id_profiles_id_fk": { + "name": "profile_target_languages_profile_id_profiles_id_fk", + "tableFrom": "profile_target_languages", + "tableTo": "profiles", + "columnsFrom": ["profile_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "profile_target_languages_position_check": { + "name": "profile_target_languages_position_check", + "value": "\"profile_target_languages\".\"position\" >= 0" + } + }, + "isRLSEnabled": false + }, + "public.profiles": { + "name": "profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "is_public": { + "name": "is_public", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "allow_anonymous_copy": { + "name": "allow_anonymous_copy", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "display_timezone": { + "name": "display_timezone", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "display_availability": { + "name": "display_availability", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "primary_language": { + "name": "primary_language", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "target_language": { + "name": "target_language", + "type": "varchar(16)", + "primaryKey": false, + "notNull": true + }, + "proficiency_level": { + "name": "proficiency_level", + "type": "proficiency_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "availability": { + "name": "availability", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true, + "default": "'flexible'" + }, + "availability_days": { + "name": "availability_days", + "type": "varchar(16)", + "primaryKey": false, + "notNull": false + }, + "availability_from": { + "name": "availability_from", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "availability_to": { + "name": "availability_to", + "type": "varchar(5)", + "primaryKey": false, + "notNull": false + }, + "availability_any_time": { + "name": "availability_any_time", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "tags": { + "name": "tags", + "type": "text[]", + "primaryKey": false, + "notNull": true, + "default": "'{}'::text[]" + }, + "country": { + "name": "country", + "type": "varchar(2)", + "primaryKey": false, + "notNull": false + }, + "timezone": { + "name": "timezone", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "last_bumped_at": { + "name": "last_bumped_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "profiles_user_id_idx": { + "name": "profiles_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "profiles_public_idx": { + "name": "profiles_public_idx", + "columns": [ + { + "expression": "is_public", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "profiles_primary_language_idx": { + "name": "profiles_primary_language_idx", + "columns": [ + { + "expression": "primary_language", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "profiles_target_language_idx": { + "name": "profiles_target_language_idx", + "columns": [ + { + "expression": "target_language", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "profiles_country_idx": { + "name": "profiles_country_idx", + "columns": [ + { + "expression": "country", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "profiles_last_bumped_at_idx": { + "name": "profiles_last_bumped_at_idx", + "columns": [ + { + "expression": "last_bumped_at", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "profiles_user_id_users_id_fk": { + "name": "profiles_user_id_users_id_fk", + "tableFrom": "profiles", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": { + "profiles_bio_length_check": { + "name": "profiles_bio_length_check", + "value": "char_length(\"profiles\".\"bio\") between 10 and 500" + }, + "profiles_availability_check": { + "name": "profiles_availability_check", + "value": "\"profiles\".\"availability\" in ('weeknights', 'weekends', 'weekday_mornings', 'flexible')" + }, + "profiles_availability_days_check": { + "name": "profiles_availability_days_check", + "value": "\"profiles\".\"availability_days\" is null or \"profiles\".\"availability_days\" in ('any', 'weekdays', 'weekends')" + }, + "profiles_availability_from_check": { + "name": "profiles_availability_from_check", + "value": "\"profiles\".\"availability_from\" is null or \"profiles\".\"availability_from\" ~ '^[0-2][0-9]:[0-5][0-9]$'" + }, + "profiles_availability_to_check": { + "name": "profiles_availability_to_check", + "value": "\"profiles\".\"availability_to\" is null or \"profiles\".\"availability_to\" ~ '^[0-2][0-9]:[0-5][0-9]$'" + }, + "profiles_tags_limit_check": { + "name": "profiles_tags_limit_check", + "value": "cardinality(\"profiles\".\"tags\") <= 8" + } + }, + "isRLSEnabled": false + }, + "public.saved_profiles": { + "name": "saved_profiles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "profile_id": { + "name": "profile_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "saved_profiles_user_profile_idx": { + "name": "saved_profiles_user_profile_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + }, + "saved_profiles_user_id_idx": { + "name": "saved_profiles_user_id_idx", + "columns": [ + { + "expression": "user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + }, + "saved_profiles_profile_id_idx": { + "name": "saved_profiles_profile_id_idx", + "columns": [ + { + "expression": "profile_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "saved_profiles_user_id_users_id_fk": { + "name": "saved_profiles_user_id_users_id_fk", + "tableFrom": "saved_profiles", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "saved_profiles_profile_id_profiles_id_fk": { + "name": "saved_profiles_profile_id_profiles_id_fk", + "tableFrom": "saved_profiles", + "tableTo": "profiles", + "columnsFrom": ["profile_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "discord_user_id": { + "name": "discord_user_id", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "discord_username": { + "name": "discord_username", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": { + "users_discord_user_id_idx": { + "name": "users_discord_user_id_idx", + "columns": [ + { + "expression": "discord_user_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.proficiency_level": { + "name": "proficiency_level", + "schema": "public", + "values": ["beginner", "intermediate", "advanced", "native-level"] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 6490e30..e701710 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -43,6 +43,13 @@ "when": 1781966823877, "tag": "0005_cool_stryfe", "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1782067199140, + "tag": "0006_strong_post", + "breakpoints": true } ] } diff --git a/src/db/schema.ts b/src/db/schema.ts index 311bdfd..b5b2f76 100644 --- a/src/db/schema.ts +++ b/src/db/schema.ts @@ -103,7 +103,7 @@ export const profiles = pgTable( 'profiles_availability_to_check', sql`${table.availabilityTo} is null or ${table.availabilityTo} ~ '^[0-2][0-9]:[0-5][0-9]$'`, ), - check('profiles_tags_limit_check', sql`cardinality(${table.tags}) <= 6`), + check('profiles_tags_limit_check', sql`cardinality(${table.tags}) <= 8`), ], );