Profile API
A profile is the dating-side view of a user: name, bio, demographics, preferences. Authentication identity (email, password, etc.) lives in Supabase Auth and is not part of the profile.
Status
| Endpoint | Status |
|---|---|
POST /profiles | ✅ Shipped |
GET /profiles/me | 🚧 Planned |
PATCH /profiles/me | 🚧 Planned |
| Profile photos / media | 🚧 Planned (Supabase Storage) |
| Profile languages, education | 🚧 Planned |
Source:
src/feats/profiles/.
Create profile
Creates the dating profile for the currently authenticated Supabase
user. The user's id (UUID) is taken from the verified JWT, so clients
do not send it.
Request
POST /profiles
Authorization: Bearer <supabase-access-token>
Content-Type: application/json
{
"firstName": "Nurbek",
"bio": "I am very cool",
"birthdate": "2004-12-05",
"height": 193,
"hometown": "Almaty",
"datingPurpose": "friendship",
"showDatingPurpose": true,
"religion": "atheism",
"familyPlan": "no-maybe-in-future",
"financialState": "wealthy",
"bodyType": "fit",
"alcoholAttitude": "negative",
"smokingAttitude": "negative"
}
Fields
| Field | Type | Required | Description |
|---|---|---|---|
firstName | string | ✅ | Display name |
bio | string | null | ✅ | Short bio |
birthdate | ISO date | ✅ | Coerced via z.coerce.date() |
height | number | ✅ | cm, range 55..272 |
hometown | string | ✅ | Free-text city for now |
datingPurpose | enum | ✅ | See enums |
showDatingPurpose | boolean | ✅ | Whether to show the purpose publicly |
religion | enum | ✅ | See enums |
familyPlan | enum | ✅ | See enums |
financialState | enum | ✅ | See enums |
bodyType | enum | ✅ | See enums |
alcoholAttitude | enum | ✅ | positive / neutral / negative |
smokingAttitude | enum | ✅ | positive / neutral / negative |
The Zod schema is
profiles.schemas.ts;
the corresponding Drizzle table is
profile.schema.ts.
Server-derived fields
These are computed by the API, not sent by the client:
| Field | Source |
|---|---|
userId | Supabase user from the JWT |
zodiac | Derived from birthdate (getZodiacSign(date)) |
Response
200 OK with the created profile. (The shipped handler currently
returns the inserted row; richer shape coming as the module grows.)
Errors
Validation errors are returned by the global error handler in
src/core/server.ts:
{
"error": "Validation failed",
"fields": [
{ "field": "height", "message": "Number must be greater than or equal to 55" }
]
}
| Status | Cause |
|---|---|
400 | Validation failed (see body) |
401 | Missing / invalid bearer token |
500 | Internal error |
Enumerations
These mirror the constants in
profiles.constants.ts.
Some are also seeded into reference tables for joins.
datingPurpose
dating, relationship, friendship, chatting
bodyType
slim, normal, fit, sportive, muscular, stocky, chubby
familyPlan
no-dont-want, no-not-soon, no-maybe-in-future, no-but-want-to,
already-have
financialState
unemployed, studying, irregular-income, low-income,
middle-income, wealthy
religion
catholicism, orthodoxy, islam, protestantism, buddhism,
hinduism, atheism, agnosticism, judaism
attitude (alcoholAttitude, smokingAttitude)
positive, neutral, negative — backed by the Postgres enum
attitude.
zodiac (server-derived)
aquarius, pisces, aries, taurus, gemini, cancer, leo,
virgo, libra, scorpio, sagittarius, capricorn — backed by the
Postgres enum zodiac.
educationLevel (planned)
basic_general, average_general, secondary_special, bachelor,
specialty, master, postgraduate — backed by the Postgres enum
education_level. Used by the planned profile_educations table.
Example with curl
ACCESS=$(bun run ./devtools/authenticate.ts)
curl -X POST http://localhost:8080/profiles \
-H "Authorization: Bearer $ACCESS" \
-H "Content-Type: application/json" \
-d '{
"firstName": "Nurbek",
"bio": "I am very cool",
"birthdate": "2004-12-05",
"height": 193,
"hometown": "Almaty",
"datingPurpose": "friendship",
"showDatingPurpose": true,
"religion": "atheism",
"familyPlan": "no-maybe-in-future",
"financialState": "wealthy",
"bodyType": "fit",
"alcoholAttitude": "negative",
"smokingAttitude": "negative"
}'
A worked example is also kept in
api/e2e/profile.http.
Roadmap
GET /profiles/me— fetch own profile.PATCH /profiles/me— partial update (same field rules).POST /profiles/me/photos— Supabase Storage-backed avatars.profile_languages,profile_educationsjoin tables.- City-as-FK (Geonames) instead of free-text
hometown, with PostGIS-backed location for radius queries.