feat: add public freelancer profile API - #169
Merged
SudiptaPaul-31 merged 1 commit intoJul 27, 2026
Merged
Conversation
Expose read-only, unauthenticated endpoints under /api/public/freelancers/[id]
so external consumers and clients can pull a freelancer's public record:
GET /api/public/freelancers/[id] profile
GET /api/public/freelancers/[id]/contracts completed contracts
GET /api/public/freelancers/[id]/reviews client reviews
GET /api/public/freelancers/[id]/reputation aggregated reputation score
Query logic, types, sanitisation and parsing live in
lib/publicFreelancerProfile.ts, following the existing freelancerDiscovery
pattern.
Public data only: handlers copy fields explicitly instead of spreading rows, so
users.email, contracts.terms, client identity, escrow wiring and
reviews.reviewer_id cannot leak even if columns are added later. Client-only
accounts return the same 404 as unknown ids, so the endpoints cannot be used to
enumerate users.
Consistent schema: every response is { data, meta } and every error is
{ error, code }; money as decimal strings, timestamps ISO-UTC, null for
"no data".
Efficient queries: one round-trip per endpoint (two for sub-resources, which
confirm existence first so an unknown id 404s instead of returning an empty
page); totals and the review summary come from COUNT(*) OVER() / AVG(...) OVER()
in the same statement; reputation reads the pre-aggregated
freelancer_reputation snapshot; limit capped at 100; partial and composite
indexes added in scripts/011-public-profile-indexes.sql.
availability is derived from in-flight work rather than stored, since the schema
has no availability column.
The reputation score is a new composite blend of completed contracts, client
reviews and delivery metrics, with per-component weights redistributed when a
signal is missing. The existing /api/freelancers/[userId]/reputation endpoint is
unchanged.
Adds docs/public-freelancer-profile-api.md and 59 tests.
|
@3m1n3nc3 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds the public freelancer profile API: four read-only, unauthenticated endpoints under a new
/api/public/freelancers/{id}namespace.GET /api/public/freelancers/{id}GET /api/public/freelancers/{id}/contractsGET /api/public/freelancers/{id}/reviews?verified=filterGET /api/public/freelancers/{id}/reputationQuery logic, types, sanitisation, parsing and response helpers live in
lib/publicFreelancerProfile.ts, following the existinglib/freelancerDiscovery.tspattern. Full integration guide indocs/public-freelancer-profile-api.md, linked from the README.Why
Closes #154. External consumers and clients need a stable, documented surface for freelancer data that exposes public information only, rather than reaching into endpoints built for authenticated dashboard use.
Acceptance criteria
Authorization enforced. Handlers copy fields explicitly instead of spreading rows, so
users.email,contracts.terms, client identity, escrow wiring andreviews.reviewer_idcannot leak even if columns are added to those tables later. A test injects every sensitive column into the mocked rows and asserts none reaches the response. Client-only accounts return the same404as unknown ids, so these endpoints cannot be used to enumerate users. Rate limited to 120 req/min per IP per endpoint.Efficient queries. One database round-trip per endpoint — two for sub-resources, which confirm the freelancer exists first so an unknown id returns
404rather than an empty page. Profile aggregates ride along as scalar sub-selects; list totals and the review summary come fromCOUNT(*) OVER()/AVG(...) OVER()in the same statement (window functions run beforeLIMIT, so they cover the whole filtered set, not the page). Reputation reads the pre-aggregatedfreelancer_reputationsnapshot rather than recomputing.limitcapped at 100. Partial and composite indexes added inscripts/011-public-profile-indexes.sql; the completed-contract and active-job indexes are partial so they stay small as history grows. Responses are edge-cacheable for 60s with a 300s stale-while-revalidate window.Consistent schema. Every response is
{ data, meta }; every error is{ error, code }— branch oncode, not onerror. Money is returned as decimal strings (total_amountisDECIMAL(18,6), so JSON numbers would lose precision), timestamps are ISO-8601 UTC, and "no data" isnullwhile aggregate counts are0.API documentation provided.
docs/public-freelancer-profile-api.mdcovers the envelope, every field, all error codes, curl examples with real payloads, the exact reputation formula, the withheld-field list with rationale, and the performance notes above.Technical considerations worth reviewing
Two calls I'd like a second opinion on:
1.
availabilityis derived, not stored. The schema has no availability column, so rather than adding one nothing can set, the profile reportsbusywhen the freelancer has a job inassigned/in_progress/in_review, otherwiseavailable. Documented as derived; if explicit freelancer-set availability lands later, the field keeps its shape and only changes source.2. The reputation score is new, not the existing one. The issue asks for a score based on completed contracts, reviews and platform metrics, but the existing
reputationScoreignores reviews entirely. This endpoint returns a composite blend — completion.30, on-time.25, dispute-free.20, client rating.25— where components with no data are dropped and their weight is redistributed proportionally, so a freelancer with no reviews yet is scored on delivery history rather than penalised for the gap.components[]is returned so consumers can show the breakdown instead of treating the number as a black box.GET /api/freelancers/{userId}/reputationis untouched; the docs explain how the two relate.On
walletAddress: deliberately included. It is the freelancer's on-chain identity, already public on the ledger, is what makes escrow payments independently verifiable, and is already returned by the existingGET /api/freelancerslisting. Happy to drop it if maintainers would rather keep the public surface narrower.Database
psql "$DATABASE_URL" -f scripts/011-public-profile-indexes.sql