Rename lovers -> profiles

This commit is contained in:
MartinBraquet
2025-09-17 15:11:53 +02:00
parent a4cc3e10c2
commit f353e590e1
35 changed files with 301 additions and 163 deletions

View File

@@ -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<k> } = {
'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<k> } = {
'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,

View File

@@ -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()

View File

@@ -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) {

View File

@@ -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],

View File

@@ -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: []}
}
}

View File

@@ -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) {

View File

@@ -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

View File

@@ -15,7 +15,7 @@ export const updateLover: APIHandler<'update-lover'> = async (
const pg = createSupabaseDirectClient()
const { data: existingLover } = await tryCatch(
pg.oneOrNone<Row<'lovers'>>('select * from lovers where user_id = $1', [
pg.oneOrNone<Row<'profiles'>>('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) {

View File

@@ -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'],

View File

@@ -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')

View File

@@ -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}")')`)
)

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;

View File

@@ -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);

View File

@@ -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);

View File

@@ -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': {

View File

@@ -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([

View File

@@ -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]
}

View File

@@ -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<string, unknown>[]
}
get_love_question_answers_and_profiles: {
Args: { p_question_id: number }
Returns: Record<string, unknown>[]
}
gtrgm_compress: {
Args: { '': unknown }
Returns: unknown

View File

@@ -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
```

View File

@@ -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<Row<'lovers'>>('select * from lovers where user_id = $1', [userId])
pg.oneOrNone<Row<'profiles'>>('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: { ... } })
```

View File

@@ -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())
// }

View File

@@ -33,7 +33,7 @@ import toast from "react-hot-toast";
export const OptionalLoveUserForm = (props: {
lover: LoverRow
setLover: <K extends Column<'lovers'>>(key: K, value: LoverRow[K]) => void
setLover: <K extends Column<'profiles'>>(key: K, value: LoverRow[K]) => void
user: User
buttonLabel?: string
fromSignup?: boolean

View File

@@ -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<boolean>
isLoadingMore: boolean
isReloading: boolean
@@ -23,7 +23,7 @@ export const ProfileGrid = (props: {
refreshStars: () => Promise<void>
}) => {
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 (
<div className="relative">
@@ -44,7 +44,7 @@ export const ProfileGrid = (props: {
isReloading && 'animate-pulse opacity-80'
)}
>
{other_lovers
{other_profiles
.map((lover) => (
<ProfilePreview
key={lover.id}
@@ -64,7 +64,7 @@ export const ProfileGrid = (props: {
</div>
)}
{!isLoadingMore && !isReloading && other_lovers.length === 0 && (
{!isLoadingMore && !isReloading && other_profiles.length === 0 && (
<div className="py-8 text-center">
<p>No profiles found.</p>
<p>Feel free to bookmark your search and we'll notify you when new users match it!</p>

View File

@@ -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<Lover[] | undefined>(undefined, 'profile-lovers');
const [profiles, setLovers] = usePersistentInMemoryState<Lover[] | undefined>(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() {
<LoadingIndicator/>
) : (
<ProfileGrid
lovers={displayLovers}
profiles={displayLovers}
loadMore={loadMore}
isLoadingMore={isLoadingMore}
isReloading={isReloading}

View File

@@ -41,7 +41,7 @@ export const RequiredLoveUserForm = (props: {
setEditUsername?: (name: string) => unknown
setEditDisplayName?: (name: string) => unknown
lover: LoverRow
setLover: <K extends Column<'lovers'>>(key: K, value: LoverRow[K] | undefined) => void
setLover: <K extends Column<'profiles'>>(key: K, value: LoverRow[K] | undefined) => void
isSubmitting: boolean
onSubmit?: () => void
loverCreatedAlready?: boolean

View File

@@ -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,

View File

@@ -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 = () => {

View File

@@ -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)
)

View File

@@ -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) {

View File

@@ -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

View File

@@ -32,7 +32,7 @@ function ProfilePageInner(props: { user: User; lover: Lover }) {
user,
})
const setLoverState = <K extends Column<'lovers'>>(key: K, value: LoverRow[K] | undefined) => {
const setLoverState = <K extends Column<'profiles'>>(key: K, value: LoverRow[K] | undefined) => {
setLover((prevState) => ({...prevState, [key]: value}))
}