Skip to main content

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

EndpointStatus
POST /profiles✅ Shipped
GET /profiles✅ Shipped
GET /profiles/:id✅ Shipped
PATCH /profiles✅ Shipped
DELETE /profiles✅ Shipped
Profile educations✅ Shipped
Profile languages✅ Shipped
Profile photos / media🚧 Planned (Supabase Storage)

Source: src/feats/profiles/.

Get own profile

Fetches the profile for the currently authenticated user, including educations and languages.

Request

GET /profiles
Authorization: Bearer <supabase-access-token>

Response

200 OK with the profile object including educations and languages arrays.

Errors

StatusCause
401Missing / invalid bearer token
404Profile not found

Get profile by ID

Fetches any profile by its UUID.

Request

GET /profiles/:id
Authorization: Bearer <supabase-access-token>

Response

200 OK with the profile object including educations and languages.

Errors

StatusCause
401Missing / invalid bearer token
404Profile not found

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

"educations": [
{ "level": "bachelor", "inProgress": true }
],
"languages": ["kk", "ru", "en"]
}

Fields

FieldTypeRequiredDescription
firstNamestringDisplay name
biostring | nullShort bio
birthdateISO dateCoerced via z.coerce.date()
heightnumbercm, range 55..272
hometownstringFree-text city for now
datingPurposeenumSee enums
showDatingPurposebooleanWhether to show the purpose publicly
religionenumSee enums
familyPlanenumSee enums
financialStateenumSee enums
bodyTypeenumSee enums
alcoholAttitudeenumpositive / neutral / negative
smokingAttitudeenumpositive / neutral / negative
educationsarrayArray of { level, inProgress } objects
languagesarrayArray of ISO 639-1 language codes (e.g. ["en", "ru"])

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:

FieldSource
userIdSupabase user from the JWT
zodiacDerived 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" }
]
}
StatusCause
400Validation failed (see body)
401Missing / invalid bearer token
500Internal error

Update profile

Partially updates the authenticated user's profile. Only provided fields are updated.

Request

PATCH /profiles
Authorization: Bearer <supabase-access-token>
Content-Type: application/json

{
"bio": "Updated bio",
"height": 190
}

All fields from CreateProfileSchema are optional in the update. If birthdate is updated, zodiac is recalculated automatically.

Response

200 OK with the updated profile.

Errors

StatusCause
400Validation failed
401Missing / invalid bearer token
404Profile not found

Delete profile

Permanently deletes the authenticated user's profile.

Request

DELETE /profiles
Authorization: Bearer <supabase-access-token>

Response

{ "deleted": true }

Errors

StatusCause
401Missing / invalid bearer token
404Profile not found

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

basic_general, average_general, secondary_special, bachelor, specialty, master, postgraduate — backed by the Postgres enum education_level. Used by the 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",
"educations": [{ "level": "bachelor", "inProgress": true }],
"languages": ["kk", "ru", "en"]
}'

A worked example is also kept in api/e2e/profile.http.

Roadmap

  • POST /profiles/photos — Supabase Storage-backed avatars.
  • City-as-FK (Geonames) instead of free-text hometown, with PostGIS-backed location for radius queries.