From f353e590e15842726ee42d2b13929fbd17149be0 Mon Sep 17 00:00:00 2001 From: MartinBraquet Date: Wed, 17 Sep 2025 15:11:53 +0200 Subject: [PATCH] Rename lovers -> profiles --- backend/api/src/app.ts | 8 +- ...tible-lovers.ts => compatible-profiles.ts} | 10 +- backend/api/src/create-lover.ts | 4 +- backend/api/src/get-likes-and-ships.ts | 20 +-- backend/api/src/get-profiles.ts | 24 +-- backend/api/src/remove-pinned-photo.ts | 2 +- .../src/{ship-lovers.ts => ship-profiles.ts} | 2 +- backend/api/src/update-lover.ts | 4 +- backend/email/emails/functions/mock.ts | 2 - .../2025-04-23-migrate-social-links.ts | 4 +- backend/scripts/find-tiptap-nodes.ts | 2 +- backend/shared/src/love/supabase.ts | 38 ++--- backend/supabase/functions_others.sql | 14 +- backend/supabase/migration.sql | 5 +- .../migrations/20250910_add_bio_text.sql | 22 --- .../migrations/20250910_bio_to_jsonb.sql | 5 - .../supabase/migrations/20250910_pg_trgm.sql | 4 - backend/supabase/{lovers.sql => profiles.sql} | 59 +++++-- common/src/api/schema.ts | 8 +- common/src/filters.ts | 6 +- common/src/love/lover.ts | 4 +- common/src/supabase/schema.ts | 146 +++++++++++++++++- docs/development.md | 3 +- docs/knowledge.md | 8 +- web/components/filters/use-filters.ts | 4 +- web/components/optional-lover-form.tsx | 2 +- web/components/profile-grid.tsx | 10 +- web/components/profiles/profiles-home.tsx | 24 +-- web/components/required-lover-form.tsx | 2 +- web/components/widgets/ship-button.tsx | 4 +- web/hooks/use-lover.ts | 2 +- web/hooks/use-online.ts | 2 +- web/hooks/{use-lovers.ts => use-profiles.ts} | 6 +- web/lib/supabase/answers.ts | 2 +- web/pages/profile.tsx | 2 +- 35 files changed, 301 insertions(+), 163 deletions(-) rename backend/api/src/{compatible-lovers.ts => compatible-profiles.ts} (90%) rename backend/api/src/{ship-lovers.ts => ship-profiles.ts} (96%) delete mode 100644 backend/supabase/migrations/20250910_add_bio_text.sql delete mode 100644 backend/supabase/migrations/20250910_bio_to_jsonb.sql delete mode 100644 backend/supabase/migrations/20250910_pg_trgm.sql rename backend/supabase/{lovers.sql => profiles.sql} (61%) rename web/hooks/{use-lovers.ts => use-profiles.ts} (88%) diff --git a/backend/api/src/app.ts b/backend/api/src/app.ts index 165d0c11..b00514f8 100644 --- a/backend/api/src/app.ts +++ b/backend/api/src/app.ts @@ -9,7 +9,7 @@ import {log} from 'shared/monitoring/log' import {metrics} from 'shared/monitoring/metrics' import {banUser} from './ban-user' import {blockUser, unblockUser} from './block-user' -import {getCompatibleLoversHandler} from './compatible-lovers' +import {getCompatibleLoversHandler} from './compatible-profiles' import {createComment} from './create-comment' import {createCompatibilityQuestion} from './create-compatibility-question' import {createLover} from './create-lover' @@ -31,7 +31,7 @@ import {removePinnedPhoto} from './remove-pinned-photo' import {report} from './report' import {searchLocation} from './search-location' import {searchNearCity} from './search-near-city' -import {shipLovers} from './ship-lovers' +import {shipLovers} from './ship-profiles' import {starLover} from './star-lover' import {updateLover} from './update-lover' import {updateMe} from './update-me' @@ -142,7 +142,7 @@ const handlers: { [k in APIPath]: APIHandler } = { 'me/delete': deleteMe, 'update-lover': updateLover, 'like-lover': likeLover, - 'ship-lovers': shipLovers, + 'ship-profiles': shipLovers, 'get-likes-and-ships': getLikesAndShips, 'has-free-like': hasFreeLike, 'star-lover': starLover, @@ -153,7 +153,7 @@ const handlers: { [k in APIPath]: APIHandler } = { 'create-comment': createComment, 'hide-comment': hideComment, 'create-compatibility-question': createCompatibilityQuestion, - 'compatible-lovers': getCompatibleLoversHandler, + 'compatible-profiles': getCompatibleLoversHandler, 'search-location': searchLocation, 'search-near-city': searchNearCity, 'create-private-user-message': createPrivateUserMessage, diff --git a/backend/api/src/compatible-lovers.ts b/backend/api/src/compatible-profiles.ts similarity index 90% rename from backend/api/src/compatible-lovers.ts rename to backend/api/src/compatible-profiles.ts index 0953d4a8..393b61c0 100644 --- a/backend/api/src/compatible-lovers.ts +++ b/backend/api/src/compatible-profiles.ts @@ -9,7 +9,7 @@ import { import { log } from 'shared/utils' export const getCompatibleLoversHandler: APIHandler< - 'compatible-lovers' + 'compatible-profiles' > = async (props) => { return getCompatibleLovers(props.userId) } @@ -25,17 +25,17 @@ export const getCompatibleLovers = async (userId: string) => { if (!lover) throw new APIError(404, 'Lover not found') - const lovers = await getGenderCompatibleLovers(lover) + const profiles = await getGenderCompatibleLovers(lover) const loverAnswers = await getCompatibilityAnswers([ userId, - ...lovers.map((l) => l.user_id), + ...profiles.map((l) => l.user_id), ]) log('got lover answers ' + loverAnswers.length) const answersByUserId = groupBy(loverAnswers, 'creator_id') const loverCompatibilityScores = Object.fromEntries( - lovers.map( + profiles.map( (l) => [ l.user_id, @@ -48,7 +48,7 @@ export const getCompatibleLovers = async (userId: string) => { ) const sortedCompatibleLovers = sortBy( - lovers, + profiles, (l) => loverCompatibilityScores[l.user_id].score ).reverse() diff --git a/backend/api/src/create-lover.ts b/backend/api/src/create-lover.ts index affc58a4..38180ede 100644 --- a/backend/api/src/create-lover.ts +++ b/backend/api/src/create-lover.ts @@ -12,7 +12,7 @@ export const createLover: APIHandler<'create-lover'> = async (body, auth) => { const pg = createSupabaseDirectClient() const { data: existingUser } = await tryCatch( - pg.oneOrNone<{ id: string }>('select id from lovers where user_id = $1', [ + pg.oneOrNone<{ id: string }>('select id from profiles where user_id = $1', [ auth.uid, ]) ) @@ -31,7 +31,7 @@ export const createLover: APIHandler<'create-lover'> = async (body, auth) => { console.log('body', body) const { data, error } = await tryCatch( - insert(pg, 'lovers', { user_id: auth.uid, ...body }) + insert(pg, 'profiles', { user_id: auth.uid, ...body }) ) if (error) { diff --git a/backend/api/src/get-likes-and-ships.ts b/backend/api/src/get-likes-and-ships.ts index 4c4c2939..5a024999 100644 --- a/backend/api/src/get-likes-and-ships.ts +++ b/backend/api/src/get-likes-and-ships.ts @@ -22,11 +22,11 @@ export const getLikesAndShipsMain = async (userId: string) => { ` select target_id, love_likes.created_time from love_likes - join lovers on lovers.user_id = love_likes.target_id + join profiles on profiles.user_id = love_likes.target_id join users on users.id = love_likes.target_id where creator_id = $1 and looking_for_matches - and lovers.pinned_url is not null + and profiles.pinned_url is not null and (data->>'isBannedFromPosting' != 'true' or data->>'isBannedFromPosting' is null) order by created_time desc `, @@ -44,11 +44,11 @@ export const getLikesAndShipsMain = async (userId: string) => { ` select creator_id, love_likes.created_time from love_likes - join lovers on lovers.user_id = love_likes.creator_id + join profiles on profiles.user_id = love_likes.creator_id join users on users.id = love_likes.creator_id where target_id = $1 and looking_for_matches - and lovers.pinned_url is not null + and profiles.pinned_url is not null and (data->>'isBannedFromPosting' != 'true' or data->>'isBannedFromPosting' is null) order by created_time desc `, @@ -71,11 +71,11 @@ export const getLikesAndShipsMain = async (userId: string) => { target1_id, target2_id, creator_id, love_ships.created_time, target1_id as target_id from love_ships - join lovers on lovers.user_id = love_ships.target1_id + join profiles on profiles.user_id = love_ships.target1_id join users on users.id = love_ships.target1_id where target2_id = $1 - and lovers.looking_for_matches - and lovers.pinned_url is not null + and profiles.looking_for_matches + and profiles.pinned_url is not null and (users.data->>'isBannedFromPosting' != 'true' or users.data->>'isBannedFromPosting' is null) union all @@ -84,11 +84,11 @@ export const getLikesAndShipsMain = async (userId: string) => { target1_id, target2_id, creator_id, love_ships.created_time, target2_id as target_id from love_ships - join lovers on lovers.user_id = love_ships.target2_id + join profiles on profiles.user_id = love_ships.target2_id join users on users.id = love_ships.target2_id where target1_id = $1 - and lovers.looking_for_matches - and lovers.pinned_url is not null + and profiles.looking_for_matches + and profiles.pinned_url is not null and (users.data->>'isBannedFromPosting' != 'true' or users.data->>'isBannedFromPosting' is null) `, [userId], diff --git a/backend/api/src/get-profiles.ts b/backend/api/src/get-profiles.ts index ca2110cc..30d32586 100644 --- a/backend/api/src/get-profiles.ts +++ b/backend/api/src/get-profiles.ts @@ -2,7 +2,7 @@ import {type APIHandler} from 'api/helpers/endpoint' import {convertRow} from 'shared/love/supabase' import {createSupabaseDirectClient} from 'shared/supabase/init' import {from, join, limit, orderBy, renderSql, select, where,} from 'shared/supabase/sql-builder' -import {getCompatibleLovers} from 'api/compatible-lovers' +import {getCompatibleLovers} from 'api/compatible-profiles' import {intersection} from 'lodash' import {MAX_INT, MIN_INT} from "common/constants"; @@ -60,7 +60,7 @@ export const loadProfiles = async (props: profileQueryType) => { } const {compatibleLovers} = await getCompatibleLovers(compatibleWithUserId) - const lovers = compatibleLovers.filter( + const profiles = compatibleLovers.filter( (l) => (!name || l.user.name.toLowerCase().includes(name.toLowerCase())) && (!genders || genders.includes(l.gender)) && @@ -85,19 +85,19 @@ export const loadProfiles = async (props: profileQueryType) => { ) const cursor = after - ? lovers.findIndex((l) => l.id.toString() === after) + 1 + ? profiles.findIndex((l) => l.id.toString() === after) + 1 : 0 console.log(cursor) - if (limitParam) return lovers.slice(cursor, cursor + limitParam) + if (limitParam) return profiles.slice(cursor, cursor + limitParam) - return lovers + return profiles } const query = renderSql( - select('lovers.*, name, username, users.data as user'), - from('lovers'), - join('users on users.id = lovers.user_id'), + select('profiles.*, name, username, users.data as user'), + from('profiles'), + join('users on users.id = profiles.user_id'), where('looking_for_matches = true'), // where(`pinned_url is not null and pinned_url != ''`), where( @@ -149,7 +149,7 @@ export const loadProfiles = async (props: profileQueryType) => { orderBy(`${orderByParam} desc`), after && where( - `lovers.${orderByParam} < (select lovers.${orderByParam} from lovers where id = $(after))`, + `profiles.${orderByParam} < (select profiles.${orderByParam} from profiles where id = $(after))`, {after} ), @@ -165,9 +165,9 @@ export const loadProfiles = async (props: profileQueryType) => { export const getProfiles: APIHandler<'get-profiles'> = async (props, _auth) => { try { - const lovers = await loadProfiles(props) - return {status: 'success', lovers: lovers} + const profiles = await loadProfiles(props) + return {status: 'success', profiles: profiles} } catch { - return {status: 'fail', lovers: []} + return {status: 'fail', profiles: []} } } diff --git a/backend/api/src/remove-pinned-photo.ts b/backend/api/src/remove-pinned-photo.ts index e3764abc..04880e9d 100644 --- a/backend/api/src/remove-pinned-photo.ts +++ b/backend/api/src/remove-pinned-photo.ts @@ -17,7 +17,7 @@ export const removePinnedPhoto: APIHandler<'remove-pinned-photo'> = async ( const pg = createSupabaseDirectClient() const { error } = await tryCatch( - pg.none('update lovers set pinned_url = null where user_id = $1', [userId]) + pg.none('update profiles set pinned_url = null where user_id = $1', [userId]) ) if (error) { diff --git a/backend/api/src/ship-lovers.ts b/backend/api/src/ship-profiles.ts similarity index 96% rename from backend/api/src/ship-lovers.ts rename to backend/api/src/ship-profiles.ts index fa97c71f..3f61ca76 100644 --- a/backend/api/src/ship-lovers.ts +++ b/backend/api/src/ship-profiles.ts @@ -5,7 +5,7 @@ import { log } from 'shared/utils' import { tryCatch } from 'common/util/try-catch' import { insert } from 'shared/supabase/utils' -export const shipLovers: APIHandler<'ship-lovers'> = async (props, auth) => { +export const shipLovers: APIHandler<'ship-profiles'> = async (props, auth) => { const { targetUserId1, targetUserId2, remove } = props const creatorId = auth.uid diff --git a/backend/api/src/update-lover.ts b/backend/api/src/update-lover.ts index 4d9ad004..473fe5b2 100644 --- a/backend/api/src/update-lover.ts +++ b/backend/api/src/update-lover.ts @@ -15,7 +15,7 @@ export const updateLover: APIHandler<'update-lover'> = async ( const pg = createSupabaseDirectClient() const { data: existingLover } = await tryCatch( - pg.oneOrNone>('select * from lovers where user_id = $1', [ + pg.oneOrNone>('select * from profiles where user_id = $1', [ auth.uid, ]) ) @@ -33,7 +33,7 @@ export const updateLover: APIHandler<'update-lover'> = async ( } const { data, error } = await tryCatch( - update(pg, 'lovers', 'user_id', { user_id: auth.uid, ...parsedBody }) + update(pg, 'profiles', 'user_id', { user_id: auth.uid, ...parsedBody }) ) if (error) { diff --git a/backend/email/emails/functions/mock.ts b/backend/email/emails/functions/mock.ts index 23fbef4b..3a04ae69 100644 --- a/backend/email/emails/functions/mock.ts +++ b/backend/email/emails/functions/mock.ts @@ -33,7 +33,6 @@ export const sinclairLover: LoverRow = { created_time: '2023-10-27T00:41:59.851776+00:00', last_online_time: '2024-05-17T02:11:48.83+00:00', last_modification_time: '2024-05-17T02:11:48.83+00:00', - bio_text: 'Hello', city: 'San Francisco', gender: 'trans-female', pref_gender: ['female', 'trans-female'], @@ -132,7 +131,6 @@ export const jamesLover: LoverRow = { created_time: '2023-10-21T21:18:26.691211+00:00', last_online_time: '2024-07-06T17:29:16.833+00:00', last_modification_time: '2024-05-17T02:11:48.83+00:00', - bio_text: 'Hello', city: 'San Francisco', gender: 'male', pref_gender: ['female'], diff --git a/backend/scripts/2025-04-23-migrate-social-links.ts b/backend/scripts/2025-04-23-migrate-social-links.ts index edc65d4b..d04a1a5f 100644 --- a/backend/scripts/2025-04-23-migrate-social-links.ts +++ b/backend/scripts/2025-04-23-migrate-social-links.ts @@ -8,11 +8,11 @@ import { chunk } from 'lodash' runScript(async ({ pg }) => { const directClient = createSupabaseDirectClient() - // Get all users and their corresponding lovers + // Get all users and their corresponding profiles const users = await directClient.manyOrNone(` select u.id, u.data, l.twitter from users u - left join lovers l on l.user_id = u.id + left join profiles l on l.user_id = u.id `) log('Found', users.length, 'users to migrate') diff --git a/backend/scripts/find-tiptap-nodes.ts b/backend/scripts/find-tiptap-nodes.ts index ebabf4d8..3e3b670b 100644 --- a/backend/scripts/find-tiptap-nodes.ts +++ b/backend/scripts/find-tiptap-nodes.ts @@ -59,7 +59,7 @@ const getNodes = async (pg: SupabaseDirectClient, nodeName: string) => { console.log(`\nSearching profiles for ${nodeName}...`) const users = renderSql( select('user_id, bio'), - from('lovers'), + from('profiles'), where(`jsonb_path_exists(bio::jsonb, '$.**.type ? (@ == "${nodeName}")')`) ) diff --git a/backend/shared/src/love/supabase.ts b/backend/shared/src/love/supabase.ts index a0795ff4..af7989fe 100644 --- a/backend/shared/src/love/supabase.ts +++ b/backend/shared/src/love/supabase.ts @@ -20,7 +20,7 @@ export function convertRow(row: LoverAndUserRow | undefined): Lover | null { } as Lover } -const LOVER_COLS = 'lovers.*, name, username, users.data as user' +const LOVER_COLS = 'profiles.*, name, username, users.data as user' export const getLover = async (userId: string) => { const pg = createSupabaseDirectClient() @@ -29,9 +29,9 @@ export const getLover = async (userId: string) => { select ${LOVER_COLS} from - lovers + profiles join - users on users.id = lovers.user_id + users on users.id = profiles.user_id where user_id = $1 `, @@ -47,9 +47,9 @@ export const getProfiles = async (userIds: string[]) => { select ${LOVER_COLS} from - lovers + profiles join - users on users.id = lovers.user_id + users on users.id = profiles.user_id where user_id = any($1) `, @@ -60,24 +60,24 @@ export const getProfiles = async (userIds: string[]) => { export const getGenderCompatibleLovers = async (lover: LoverRow) => { const pg = createSupabaseDirectClient() - const lovers = await pg.map( + const profiles = await pg.map( ` select ${LOVER_COLS} - from lovers + from profiles join - users on users.id = lovers.user_id + users on users.id = profiles.user_id where user_id != $(user_id) and looking_for_matches and (data->>'isBannedFromPosting' != 'true' or data->>'isBannedFromPosting' is null) and (data->>'userDeleted' != 'true' or data->>'userDeleted' is null) - and lovers.pinned_url is not null + and profiles.pinned_url is not null `, { ...lover }, convertRow ) - return lovers.filter((l: Lover) => areGenderCompatible(lover, l)) + return profiles.filter((l: Lover) => areGenderCompatible(lover, l)) } export const getCompatibleLovers = async ( @@ -89,9 +89,9 @@ export const getCompatibleLovers = async ( ` select ${LOVER_COLS} - from lovers + from profiles join - users on users.id = lovers.user_id + users on users.id = profiles.user_id where user_id != $(user_id) and looking_for_matches @@ -99,17 +99,17 @@ export const getCompatibleLovers = async ( and (data->>'userDeleted' != 'true' or data->>'userDeleted' is null) -- Gender - and (lovers.gender = any($(pref_gender)) or lovers.gender = 'non-binary') - and ($(gender) = any(lovers.pref_gender) or $(gender) = 'non-binary') + and (profiles.gender = any($(pref_gender)) or profiles.gender = 'non-binary') + and ($(gender) = any(profiles.pref_gender) or $(gender) = 'non-binary') -- Age - and lovers.age >= $(pref_age_min) - and lovers.age <= $(pref_age_max) - and $(age) >= lovers.pref_age_min - and $(age) <= lovers.pref_age_max + and profiles.age >= $(pref_age_min) + and profiles.age <= $(pref_age_max) + and $(age) >= profiles.pref_age_min + and $(age) <= profiles.pref_age_max -- Location - and calculate_earth_distance_km($(city_latitude), $(city_longitude), lovers.city_latitude, lovers.city_longitude) < $(radiusKm) + and calculate_earth_distance_km($(city_latitude), $(city_longitude), profiles.city_latitude, profiles.city_longitude) < $(radiusKm) `, { ...lover, radiusKm: radiusKm ?? 40_000 }, convertRow diff --git a/backend/supabase/functions_others.sql b/backend/supabase/functions_others.sql index 9ef10db3..77948705 100644 --- a/backend/supabase/functions_others.sql +++ b/backend/supabase/functions_others.sql @@ -20,7 +20,7 @@ END; $function$; create -or replace function public.get_love_question_answers_and_lovers (p_question_id bigint) returns setof record language plpgsql as $function$ +or replace function public.get_love_question_answers_and_profiles (p_question_id bigint) returns setof record language plpgsql as $function$ BEGIN RETURN QUERY SELECT @@ -29,16 +29,16 @@ SELECT love_answers.free_response, love_answers.multiple_choice, love_answers.integer, - lovers.age, - lovers.gender, - lovers.city, + profiles.age, + profiles.gender, + profiles.city, users.data FROM - lovers + profiles JOIN - love_answers ON lovers.user_id = love_answers.creator_id + love_answers ON profiles.user_id = love_answers.creator_id join - users on lovers.user_id = users.id + users on profiles.user_id = users.id WHERE love_answers.question_id = p_question_id order by love_answers.created_time desc; diff --git a/backend/supabase/migration.sql b/backend/supabase/migration.sql index 0a99a135..d5ce3e54 100644 --- a/backend/supabase/migration.sql +++ b/backend/supabase/migration.sql @@ -1,7 +1,7 @@ BEGIN; \i backend/supabase/functions.sql \i backend/supabase/firebase.sql -\i backend/supabase/lovers.sql +\i backend/supabase/profiles.sql \i backend/supabase/users.sql \i backend/supabase/private_user_message_channels.sql \i backend/supabase/private_user_message_channel_members.sql @@ -20,8 +20,5 @@ BEGIN; \i backend/supabase/user_notifications.sql \i backend/supabase/functions_others.sql \i backend/supabase/reports.sql -\i backend/supabase/migrations/20250910_bio_to_jsonb.sql -\i backend/supabase/migrations/20250910_add_bio_text.sql -\i backend/supabase/migrations/20250910_pg_trgm.sql \i backend/supabase/bookmarked_searches.sql COMMIT; diff --git a/backend/supabase/migrations/20250910_add_bio_text.sql b/backend/supabase/migrations/20250910_add_bio_text.sql deleted file mode 100644 index 1a6de3c3..00000000 --- a/backend/supabase/migrations/20250910_add_bio_text.sql +++ /dev/null @@ -1,22 +0,0 @@ -ALTER TABLE lovers ADD COLUMN bio_text tsvector; - -CREATE OR REPLACE FUNCTION lovers_bio_tsvector_update() -RETURNS trigger AS $$ -BEGIN - new.bio_text := to_tsvector( - 'english', - ( - SELECT string_agg(trim(both '"' from x::text), ' ') - FROM jsonb_path_query(new.bio, '$.**.text'::jsonpath) AS x - ) - ); -RETURN new; -END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER lovers_bio_tsvector_trigger - BEFORE INSERT OR UPDATE OF bio ON lovers - FOR EACH ROW EXECUTE FUNCTION lovers_bio_tsvector_update(); - - -create index on lovers using gin(bio_text); diff --git a/backend/supabase/migrations/20250910_bio_to_jsonb.sql b/backend/supabase/migrations/20250910_bio_to_jsonb.sql deleted file mode 100644 index 37354989..00000000 --- a/backend/supabase/migrations/20250910_bio_to_jsonb.sql +++ /dev/null @@ -1,5 +0,0 @@ --- 1. Drop the old column -alter table lovers drop column if exists bio; - --- 2. Add the new column as jsonb -alter table lovers add column bio jsonb; diff --git a/backend/supabase/migrations/20250910_pg_trgm.sql b/backend/supabase/migrations/20250910_pg_trgm.sql deleted file mode 100644 index 3d157f86..00000000 --- a/backend/supabase/migrations/20250910_pg_trgm.sql +++ /dev/null @@ -1,4 +0,0 @@ -create extension if not exists pg_trgm; - -CREATE INDEX lovers_bio_trgm_idx - ON lovers USING gin ((bio::text) gin_trgm_ops); diff --git a/backend/supabase/lovers.sql b/backend/supabase/profiles.sql similarity index 61% rename from backend/supabase/lovers.sql rename to backend/supabase/profiles.sql index a0305f3f..0cda155b 100644 --- a/backend/supabase/lovers.sql +++ b/backend/supabase/profiles.sql @@ -6,9 +6,9 @@ CREATE TYPE lover_visibility AS ENUM ('public', 'member'); END IF; END$$; -CREATE TABLE IF NOT EXISTS lovers ( +CREATE TABLE IF NOT EXISTS profiles ( age INTEGER NULL, - bio JSON, + bio JSONB, born_in_location TEXT, city TEXT NOT NULL, city_latitude NUMERIC(9, 6), @@ -50,34 +50,34 @@ CREATE TABLE IF NOT EXISTS lovers ( visibility lover_visibility DEFAULT 'member'::lover_visibility NOT NULL, wants_kids_strength INTEGER DEFAULT 0 NOT NULL, website TEXT, - CONSTRAINT lovers_pkey PRIMARY KEY (id) + CONSTRAINT profiles_pkey PRIMARY KEY (id) ); -- Row Level Security -ALTER TABLE lovers ENABLE ROW LEVEL SECURITY; +ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; -- Policies -DROP POLICY IF EXISTS "public read" ON lovers; +DROP POLICY IF EXISTS "public read" ON profiles; -CREATE POLICY "public read" ON lovers +CREATE POLICY "public read" ON profiles FOR SELECT USING (true); -DROP POLICY IF EXISTS "self update" ON lovers; +DROP POLICY IF EXISTS "self update" ON profiles; -CREATE POLICY "self update" ON lovers +CREATE POLICY "self update" ON profiles FOR UPDATE WITH CHECK ((user_id = firebase_uid())); -- Indexes -DROP INDEX IF EXISTS lovers_user_id_idx; -CREATE INDEX lovers_user_id_idx ON public.lovers USING btree (user_id); +DROP INDEX IF EXISTS profiles_user_id_idx; +CREATE INDEX profiles_user_id_idx ON public.profiles USING btree (user_id); DROP INDEX IF EXISTS unique_user_id; -CREATE UNIQUE INDEX unique_user_id ON public.lovers USING btree (user_id); +CREATE UNIQUE INDEX unique_user_id ON public.profiles USING btree (user_id); -CREATE INDEX IF NOT EXISTS idx_lovers_last_mod_24h - ON public.lovers USING btree (last_modification_time); +CREATE INDEX IF NOT EXISTS idx_profiles_last_mod_24h + ON public.profiles USING btree (last_modification_time); -- Functions and Triggers CREATE @@ -98,6 +98,37 @@ LANGUAGE plpgsql; CREATE TRIGGER trigger_update_last_mod_time BEFORE UPDATE - ON lovers + ON profiles FOR EACH ROW EXECUTE FUNCTION update_last_modification_time(); + +-- pg_trgm +create extension if not exists pg_trgm; + +CREATE INDEX profiles_bio_trgm_idx + ON profiles USING gin ((bio::text) gin_trgm_ops); + + +--- bio_text +-- ALTER TABLE profiles ADD COLUMN bio_text tsvector; +-- +-- CREATE OR REPLACE FUNCTION profiles_bio_tsvector_update() +-- RETURNS trigger AS $$ +-- BEGIN +-- new.bio_text := to_tsvector( +-- 'english', +-- ( +-- SELECT string_agg(trim(both '"' from x::text), ' ') +-- FROM jsonb_path_query(new.bio, '$.**.text'::jsonpath) AS x +-- ) +-- ); +-- RETURN new; +-- END; +-- $$ LANGUAGE plpgsql; +-- +-- CREATE TRIGGER profiles_bio_tsvector_trigger +-- BEFORE INSERT OR UPDATE OF bio ON profiles +-- FOR EACH ROW EXECUTE FUNCTION profiles_bio_tsvector_update(); +-- +-- create index on profiles using gin(bio_text); + diff --git a/common/src/api/schema.ts b/common/src/api/schema.ts index e992f86f..baaff7f4 100644 --- a/common/src/api/schema.ts +++ b/common/src/api/schema.ts @@ -91,7 +91,7 @@ export const API = (_apiTypeCheck = { 'create-lover': { method: 'POST', authed: true, - returns: {} as Row<'lovers'>, + returns: {} as Row<'profiles'>, props: baseLoversSchema, }, report: { @@ -212,7 +212,7 @@ export const API = (_apiTypeCheck = { }) .strict(), }, - 'compatible-lovers': { + 'compatible-profiles': { method: 'GET', authed: false, props: z.object({ userId: z.string() }), @@ -257,7 +257,7 @@ export const API = (_apiTypeCheck = { status: 'success' }, }, - 'ship-lovers': { + 'ship-profiles': { method: 'POST', authed: true, props: z.object({ @@ -331,7 +331,7 @@ export const API = (_apiTypeCheck = { .strict(), returns: {} as { status: 'success' | 'fail' - lovers: Lover[] + profiles: Lover[] }, }, 'get-lover-answers': { diff --git a/common/src/filters.ts b/common/src/filters.ts index f8070123..17421e2d 100644 --- a/common/src/filters.ts +++ b/common/src/filters.ts @@ -18,12 +18,12 @@ export type FilterFields = { | 'pref_age_max' > export const orderLovers = ( - lovers: Lover[], + profiles: Lover[], starredUserIds: string[] | undefined ) => { - if (!lovers) return + if (!profiles) return - let s = cloneDeep(lovers) + let s = cloneDeep(profiles) if (starredUserIds) { s = filterDefined([ diff --git a/common/src/love/lover.ts b/common/src/love/lover.ts index e25ce511..ddba29e7 100644 --- a/common/src/love/lover.ts +++ b/common/src/love/lover.ts @@ -1,10 +1,10 @@ import { Row, run, SupabaseClient } from 'common/supabase/utils' import { User } from 'common/user' -export type LoverRow = Row<'lovers'> +export type LoverRow = Row<'profiles'> export type Lover = LoverRow & { user: User } export const getLoverRow = async (userId: string, db: SupabaseClient) => { console.log('getLoverRow', userId) - const res = await run(db.from('lovers').select('*').eq('user_id', userId)) + const res = await run(db.from('profiles').select('*').eq('user_id', userId)) return res.data[0] } diff --git a/common/src/supabase/schema.ts b/common/src/supabase/schema.ts index 46c0dc98..877b0afa 100644 --- a/common/src/supabase/schema.ts +++ b/common/src/supabase/schema.ts @@ -448,13 +448,13 @@ export type Database = { } Insert: { created_time?: string - id?: never + id?: number last_updated_time?: string title?: string | null } Update: { created_time?: string - id?: never + id?: number last_updated_time?: string title?: string | null } @@ -542,6 +542,144 @@ export type Database = { } Relationships: [] } + profiles: { + Row: { + age: number | null + bio: Json | null + born_in_location: string | null + city: string + city_latitude: number | null + city_longitude: number | null + comments_enabled: boolean + company: string | null + country: string | null + created_time: string + drinks_per_month: number | null + education_level: string | null + ethnicity: string[] | null + gender: string + geodb_city_id: string | null + has_kids: number | null + height_in_inches: number | null + id: number + is_smoker: boolean | null + is_vegetarian_or_vegan: boolean | null + last_modification_time: string + last_online_time: string + looking_for_matches: boolean + messaging_status: string + occupation: string | null + occupation_title: string | null + photo_urls: string[] | null + pinned_url: string | null + political_beliefs: string[] | null + pref_age_max: number | null + pref_age_min: number | null + pref_gender: string[] + pref_relation_styles: string[] + referred_by_username: string | null + region_code: string | null + religious_belief_strength: number | null + religious_beliefs: string | null + twitter: string | null + university: string | null + user_id: string + visibility: Database['public']['Enums']['lover_visibility'] + wants_kids_strength: number + website: string | null + } + Insert: { + age?: number | null + bio?: Json | null + born_in_location?: string | null + city: string + city_latitude?: number | null + city_longitude?: number | null + comments_enabled?: boolean + company?: string | null + country?: string | null + created_time?: string + drinks_per_month?: number | null + education_level?: string | null + ethnicity?: string[] | null + gender: string + geodb_city_id?: string | null + has_kids?: number | null + height_in_inches?: number | null + id?: never + is_smoker?: boolean | null + is_vegetarian_or_vegan?: boolean | null + last_modification_time?: string + last_online_time?: string + looking_for_matches?: boolean + messaging_status?: string + occupation?: string | null + occupation_title?: string | null + photo_urls?: string[] | null + pinned_url?: string | null + political_beliefs?: string[] | null + pref_age_max?: number | null + pref_age_min?: number | null + pref_gender: string[] + pref_relation_styles: string[] + referred_by_username?: string | null + region_code?: string | null + religious_belief_strength?: number | null + religious_beliefs?: string | null + twitter?: string | null + university?: string | null + user_id: string + visibility?: Database['public']['Enums']['lover_visibility'] + wants_kids_strength?: number + website?: string | null + } + Update: { + age?: number | null + bio?: Json | null + born_in_location?: string | null + city?: string + city_latitude?: number | null + city_longitude?: number | null + comments_enabled?: boolean + company?: string | null + country?: string | null + created_time?: string + drinks_per_month?: number | null + education_level?: string | null + ethnicity?: string[] | null + gender?: string + geodb_city_id?: string | null + has_kids?: number | null + height_in_inches?: number | null + id?: never + is_smoker?: boolean | null + is_vegetarian_or_vegan?: boolean | null + last_modification_time?: string + last_online_time?: string + looking_for_matches?: boolean + messaging_status?: string + occupation?: string | null + occupation_title?: string | null + photo_urls?: string[] | null + pinned_url?: string | null + political_beliefs?: string[] | null + pref_age_max?: number | null + pref_age_min?: number | null + pref_gender?: string[] + pref_relation_styles?: string[] + referred_by_username?: string | null + region_code?: string | null + religious_belief_strength?: number | null + religious_beliefs?: string | null + twitter?: string | null + university?: string | null + user_id?: string + visibility?: Database['public']['Enums']['lover_visibility'] + wants_kids_strength?: number + website?: string | null + } + Relationships: [] + } reports: { Row: { content_id: string @@ -700,6 +838,10 @@ export type Database = { Args: { p_question_id: number } Returns: Record[] } + get_love_question_answers_and_profiles: { + Args: { p_question_id: number } + Returns: Record[] + } gtrgm_compress: { Args: { '': unknown } Returns: unknown diff --git a/docs/development.md b/docs/development.md index 57817240..3ad1bc23 100644 --- a/docs/development.md +++ b/docs/development.md @@ -14,10 +14,11 @@ See those other useful documents as well: To add a profile variable (personality type, etc.), make modifications here: * ... -You will likely need to update the `lovers` table of the database. Set up an SQL migration file that updates the table, as in [migrations](../backend/supabase/migrations), and run it in the same vein as [migrate.sh](../scripts/migrate.sh). +You will likely need to update the `profiles` table of the database. Set up an SQL migration file that updates the table, as in [migrations](../backend/supabase/migrations), and run it in the same vein as [migrate.sh](../scripts/migrate.sh). Sync the database types from supabase to the local files (which assist Typescript in typing): ```bash + yarn regen-types ``` diff --git a/docs/knowledge.md b/docs/knowledge.md index 62e9f472..04ff32db 100644 --- a/docs/knowledge.md +++ b/docs/knowledge.md @@ -345,7 +345,7 @@ We have two ways to access our postgres database. ```ts import { db } from 'web/lib/supabase/db' -db.from('lovers').select('*').eq('user_id', userId) +db.from('profiles').select('*').eq('user_id', userId) ``` and @@ -354,7 +354,7 @@ and import { createSupabaseDirectClient } from 'shared/supabase/init' const pg = createSupabaseDirectClient() -pg.oneOrNone>('select * from lovers where user_id = $1', [userId]) +pg.oneOrNone>('select * from profiles where user_id = $1', [userId]) ``` The supabase client just uses the supabase client library, which is a wrapper around postgREST. It allows us to query and update the database directly from the frontend. @@ -397,12 +397,12 @@ const pg = createSupabaseDirectClient() // you are encouraged to use tryCatch for these const { data, error } = await tryCatch( - insert(pg, 'lovers', { user_id: auth.uid, ...body }) + insert(pg, 'profiles', { user_id: auth.uid, ...body }) ) if (error) throw APIError(500, 'Error creating lover: ' + error.message) -await update(pg, 'lovers', 'user_id', { user_id: auth.uid, age: 99 }) +await update(pg, 'profiles', 'user_id', { user_id: auth.uid, age: 99 }) await updateData(pg, 'private_users', { id: userId, notifications: { ... } }) ``` diff --git a/web/components/filters/use-filters.ts b/web/components/filters/use-filters.ts index 19f17485..5edd9907 100644 --- a/web/components/filters/use-filters.ts +++ b/web/components/filters/use-filters.ts @@ -117,7 +117,7 @@ export const useFilters = (you: Lover | undefined) => { } } -// const alternateWomenAndMen = (lovers: Lover[]) => { -// const [women, nonWomen] = partition(lovers, (l) => l.gender === 'female') +// const alternateWomenAndMen = (profiles: Lover[]) => { +// const [women, nonWomen] = partition(profiles, (l) => l.gender === 'female') // return filterDefined(zip(women, nonWomen).flat()) // } \ No newline at end of file diff --git a/web/components/optional-lover-form.tsx b/web/components/optional-lover-form.tsx index bfcf139e..8d06a45c 100644 --- a/web/components/optional-lover-form.tsx +++ b/web/components/optional-lover-form.tsx @@ -33,7 +33,7 @@ import toast from "react-hot-toast"; export const OptionalLoveUserForm = (props: { lover: LoverRow - setLover: >(key: K, value: LoverRow[K]) => void + setLover: >(key: K, value: LoverRow[K]) => void user: User buttonLabel?: string fromSignup?: boolean diff --git a/web/components/profile-grid.tsx b/web/components/profile-grid.tsx index 55d27c4b..1f269371 100644 --- a/web/components/profile-grid.tsx +++ b/web/components/profile-grid.tsx @@ -14,7 +14,7 @@ import {CompatibleBadge} from "web/components/widgets/compatible-badge"; import {useUser} from "web/hooks/use-user"; export const ProfileGrid = (props: { - lovers: Lover[] + profiles: Lover[] loadMore: () => Promise isLoadingMore: boolean isReloading: boolean @@ -23,7 +23,7 @@ export const ProfileGrid = (props: { refreshStars: () => Promise }) => { const { - lovers, + profiles, loadMore, isLoadingMore, isReloading, @@ -34,7 +34,7 @@ export const ProfileGrid = (props: { const user = useUser() - const other_lovers = lovers.filter((lover) => lover.user_id !== user?.id); + const other_profiles = profiles.filter((lover) => lover.user_id !== user?.id); return (
@@ -44,7 +44,7 @@ export const ProfileGrid = (props: { isReloading && 'animate-pulse opacity-80' )} > - {other_lovers + {other_profiles .map((lover) => ( )} - {!isLoadingMore && !isReloading && other_lovers.length === 0 && ( + {!isLoadingMore && !isReloading && other_profiles.length === 0 && (

No profiles found.

Feel free to bookmark your search and we'll notify you when new users match it!

diff --git a/web/components/profiles/profiles-home.tsx b/web/components/profiles/profiles-home.tsx index 6d7f4e9b..709c133c 100644 --- a/web/components/profiles/profiles-home.tsx +++ b/web/components/profiles/profiles-home.tsx @@ -2,7 +2,7 @@ import {Lover} from 'common/love/lover' import {removeNullOrUndefinedProps} from 'common/util/object' import {Search} from 'web/components/filters/search' import {useLover} from 'web/hooks/use-lover' -import {useCompatibleLovers} from 'web/hooks/use-lovers' +import {useCompatibleLovers} from 'web/hooks/use-profiles' import {getStars} from 'web/lib/supabase/stars' import Router from 'next/router' import {useCallback, useEffect, useRef, useState} from 'react' @@ -32,7 +32,7 @@ export function ProfilesHome() { locationFilterProps, } = useFilters(you ?? undefined); - const [lovers, setLovers] = usePersistentInMemoryState(undefined, 'profile-lovers'); + const [profiles, setLovers] = usePersistentInMemoryState(undefined, 'profile-profiles'); const {bookmarkedSearches, refreshBookmarkedSearches} = useBookmarkedSearches(user?.id) const [isLoadingMore, setIsLoadingMore] = useState(false); const [isReloading, setIsReloading] = useState(false); @@ -59,8 +59,8 @@ export function ProfilesHome() { compatibleWithUserId: user?.id, ...filters }) as any) - .then(({lovers}) => { - if (current === id.current) setLovers(lovers); + .then(({profiles}) => { + if (current === id.current) setLovers(profiles); }) .finally(() => { if (current === id.current) setIsReloading(false); @@ -69,29 +69,29 @@ export function ProfilesHome() { const {data: starredUserIds, refresh: refreshStars} = useGetter('star', user?.id, getStars); const compatibleLovers = useCompatibleLovers(user?.id); - const displayLovers = lovers && orderLovers(lovers, starredUserIds); + const displayLovers = profiles && orderLovers(profiles, starredUserIds); const loadMore = useCallback(async () => { - if (!lovers || isLoadingMore) return false; + if (!profiles || isLoadingMore) return false; try { setIsLoadingMore(true); - const lastLover = lovers[lovers.length - 1]; + const lastLover = profiles[profiles.length - 1]; const result = await api('get-profiles', removeNullOrUndefinedProps({ limit: 20, compatibleWithUserId: user?.id, after: lastLover?.id.toString(), ...filters }) as any); - if (result.lovers.length === 0) return false; - setLovers((prev) => (prev ? [...prev, ...result.lovers] : result.lovers)); + if (result.profiles.length === 0) return false; + setLovers((prev) => (prev ? [...prev, ...result.profiles] : result.profiles)); return true; } catch (err) { - console.error('Failed to load more lovers', err); + console.error('Failed to load more profiles', err); return false; } finally { setIsLoadingMore(false); } - }, [lovers, filters, isLoadingMore, setLovers]); + }, [profiles, filters, isLoadingMore, setLovers]); return ( <> @@ -113,7 +113,7 @@ export function ProfilesHome() { ) : ( unknown setEditDisplayName?: (name: string) => unknown lover: LoverRow - setLover: >(key: K, value: LoverRow[K] | undefined) => void + setLover: >(key: K, value: LoverRow[K] | undefined) => void isSubmitting: boolean onSubmit?: () => void loverCreatedAlready?: boolean diff --git a/web/components/widgets/ship-button.tsx b/web/components/widgets/ship-button.tsx index 5aa789ee..7c85f811 100644 --- a/web/components/widgets/ship-button.tsx +++ b/web/components/widgets/ship-button.tsx @@ -20,12 +20,12 @@ export const ShipButton = (props: { const like = async () => { setIsLoading(true) - await api('ship-lovers', { + await api('ship-profiles', { targetUserId1: targetId1, targetUserId2: targetId2, remove: shipped, }) - track('ship lovers', { + track('ship profiles', { targetId1, targetId2, remove: shipped, diff --git a/web/hooks/use-lover.ts b/web/hooks/use-lover.ts index bf5700b9..0e326677 100644 --- a/web/hooks/use-lover.ts +++ b/web/hooks/use-lover.ts @@ -10,7 +10,7 @@ import {usePersistentLocalState} from 'web/hooks/use-persistent-local-state' export const useLover = () => { const user = useUser() const [lover, setLover] = usePersistentLocalState< - Row<'lovers'> | undefined | null + Row<'profiles'> | undefined | null >(undefined, `lover-${user?.id}`) const refreshLover = () => { diff --git a/web/hooks/use-online.ts b/web/hooks/use-online.ts index 4d4c71f7..3ba1db09 100644 --- a/web/hooks/use-online.ts +++ b/web/hooks/use-online.ts @@ -10,7 +10,7 @@ export const useOnline = () => { if (!lover || !isAuthed) return run( db - .from('lovers') + .from('profiles') .update({ last_online_time: new Date().toISOString() }) .eq('id', lover.id) ) diff --git a/web/hooks/use-lovers.ts b/web/hooks/use-profiles.ts similarity index 88% rename from web/hooks/use-lovers.ts rename to web/hooks/use-profiles.ts index 71d59d4b..66e02df4 100644 --- a/web/hooks/use-lovers.ts +++ b/web/hooks/use-profiles.ts @@ -11,14 +11,14 @@ export const useCompatibleLovers = ( options?: { sortWithModifiers?: boolean } ) => { const [data, setData] = usePersistentInMemoryState< - APIResponse<'compatible-lovers'> | undefined | null - >(undefined, `compatible-lovers-${userId}`) + APIResponse<'compatible-profiles'> | undefined | null + >(undefined, `compatible-profiles-${userId}`) const lover = useLoverByUserId(userId ?? undefined) useEffect(() => { if (userId) { - api('compatible-lovers', { userId }) + api('compatible-profiles', { userId }) .then(setData) .catch((e) => { if (e.code === 404) { diff --git a/web/lib/supabase/answers.ts b/web/lib/supabase/answers.ts index 02bdb457..87c51850 100644 --- a/web/lib/supabase/answers.ts +++ b/web/lib/supabase/answers.ts @@ -15,7 +15,7 @@ export const deleteAnswer = async ( } export const getOtherAnswers = async (question_id: number) => { - const { data } = await db.rpc('get_love_question_answers_and_lovers' as any, { + const { data } = await db.rpc('get_love_question_answers_and_profiles' as any, { p_question_id: question_id, }) return data diff --git a/web/pages/profile.tsx b/web/pages/profile.tsx index f83a1a0e..04f28c6b 100644 --- a/web/pages/profile.tsx +++ b/web/pages/profile.tsx @@ -32,7 +32,7 @@ function ProfilePageInner(props: { user: User; lover: Lover }) { user, }) - const setLoverState = >(key: K, value: LoverRow[K] | undefined) => { + const setLoverState = >(key: K, value: LoverRow[K] | undefined) => { setLover((prevState) => ({...prevState, [key]: value})) }