Source document · API/API_SRS.md
SB Relaxing Club Go API Software Requirements Specification
Version: 1.0
Status: Implementation baseline
Updated: 25 September 2026
Parent: Product SRS
1. Purpose
This document defines the backend/API that powers the SB Relaxing Club Kotlin Multiplatform mobile applications and Next.js/shadcn administration webapp. The Go API is the sole authority for persisted state, identity, authorization, workflow transitions, payment status, notifications, reporting, and audit records. No client may connect directly to the database or make a business-critical decision solely in client code.
The mobile video reference is local project material and is not part of the API repository contract. API behavior must support the product flows defined in the Planning and Mobile SRS documents without coupling server design to pixel-level presentation.
2. Normative language and principles
“MUST” is required for launch. “SHOULD” is recommended unless a recorded decision defers it. “MAY” is optional. All times are stored in UTC and returned in ISO-8601; clients format in the user’s locale. Money is represented as integer minor units plus an ISO currency code, never floating point. IDs are opaque stable identifiers. All state-changing endpoints authenticate, authorize, validate, and record relevant audit data.
3. Architecture requirements
3.1 Service shape
Implement a versioned Go HTTP API, preferably JSON over HTTPS (/api/v1). Use PostgreSQL as the transactional system of record and S3-compatible private object storage for uploaded files. Use signed, short-lived upload/download URLs or an equivalent access-controlled delivery mechanism. CDN delivery may be used for public media after moderation/visibility checks. Use a transactional outbox for events that must reliably trigger notifications, reporting projections, or asynchronous processing.
The API MUST provide:
- Modular domain boundaries: identity, catalog, providers, listings, orders, payments, sales/reporting, chat, notifications, community/media, meetings/livestreams, lifestyle, and administration.
- Explicit request/response schemas and stable error codes.
- Database migrations, health/readiness endpoints, structured logs, metrics, and trace/correlation IDs.
- Configurable secrets, no secrets in source control, and environment-specific configuration.
- Idempotency support for order creation and other retry-prone mutations.
- Pagination, filtering, and bounded request sizes for list endpoints.
- WebSocket or equivalent authenticated real-time channels for chat and WebRTC signaling where needed.
3.2 API compatibility
All routes are versioned. Additive optional fields are preferred for compatible evolution. Breaking changes require a new version or an explicit migration window. OpenAPI 3 specification MUST describe routes, schemas, authentication, error responses, and examples. Generate or validate client contracts from this specification where practical.
3.3 Deployment and module architecture
The initial backend MUST be a modular monolith: one Go application and one primary PostgreSQL database, separated internally into feature modules. Auth, orders, payments, chat, and other names in this document refer to modules inside that application, not independently deployed microservices. This keeps cross-domain transactions, deployment, observability, and local development manageable while the product is young.
Each feature module owns its domain types, use cases, persistence interface, transport handlers, and internal rules. Other modules use its exported application interface or published domain events; they MUST NOT import its database adapter or update its tables directly. For example, Payments records and confirms payments, while Orders decides whether a confirmed payment permits an order transition. Order completion owns creation of the completed-order snapshot and sale within one transaction.
Infrastructure adapters live behind narrow interfaces. PostgreSQL, Redis/queue, S3 storage, Stripe, email, APNs, and FCM can therefore be replaced or faked without changing domain rules. Introduce a new interface only where there are multiple real adapters, a third-party dependency, nondeterministic behavior, or a transaction seam that tests need to control.
4. Identity, sessions, and authorization
4.1 Authentication
Support email/password registration, email OTP verification, email/password sign-in followed by a separate emailed OTP challenge, logout/revocation, password reset, Google identity sign-in, refresh-token rotation, and account deletion. Passwords are hashed with a modern memory-hard password hashing algorithm and per-account salt. OTPs and reset tokens are short-lived, single-use, rate-limited, and stored only as non-reversible hashes. Session and refresh-token secrets are never returned in logs. A session must not be issued by the password step; it is issued only after the sign-in OTP is consumed.
Google sign-in accepts a provider-issued identity token; the API verifies signature, audience, issuer, expiry, and nonce/state protections as applicable, then resolves or creates the user according to a documented account-linking policy. Never trust a client-supplied Google email without token verification.
Guest booking uses a 256-bit random capability that expires after 90 days. The API stores only its SHA-256 hash and returns the clear capability only on guest order creation. GET /api/v1/guest/orders/{id} requires X-Guest-Access-Token and grants read access only to that order. Guest access cannot create payments, chat, community, provider, or admin access. Guest creation requires contact email and phone; provider order reads expose those contact details to the assigned provider. Reusing an email and idempotency key rotates the capability, so a retried client must persist the latest response.
4.2 Authorization
Enforce role and record ownership on every request. Roles: guest, member, provider, accountant, admin. Provider status derives from an approved provider profile, not a client-editable role flag. Accountants are scoped to explicitly assigned service IDs. Admin-only operations must be separately protected and audited. Use deny-by-default authorization and prevent insecure direct-object-reference access by checking the target record’s relationship to the caller.
Minimum ownership rules:
- User: self read/update/delete; admin manage; public profile fields only when intentionally exposed.
- Provider profile/listing: public read of active public fields; owner manages own; admin manages all.
- Order: customer, assigned provider, admin access; accountant read only when scoped and permitted.
- Chat: participants access; admin access only under documented support/moderation policy and audited.
- Sale: provider sees own sales; accountant sees assigned service sales; admin sees all.
- Private files: authorized order/chat/profile parties only. Public media must have explicit visibility.
Provider registration and listing creation are authenticated mutations. The server derives provider ownership from the bearer identity and verifies listing and menu-item ownership in repository operations; client-supplied owner IDs are never authoritative.
4.3 Session endpoints
Provide endpoints for register, OTP verify/resend, login, Google exchange, refresh, logout, forgot/reset password, current user/profile update, username recovery, and account deletion. Apply IP/account/device throttles and generic responses where needed to prevent account enumeration.
5. Common API conventions
5.1 Headers
Accept JSON. Use bearer access token for authenticated routes. Accept Idempotency-Key on order creation, payment intent/confirmation submissions, and other mutations where retry could duplicate effects. Return X-Request-ID or equivalent correlation ID. Upload/download authorization must be scoped and expiring.
5.2 Success and errors
Every JSON endpoint MUST use the standard response envelope below. HTTP status codes still express transport-level outcome. Clients use success, error.code, and HTTP status for control flow; they MUST NOT parse message text. Do not leak stack traces, SQL details, provider secrets, or private record existence.
Successful single-resource response:
{
"success": true,
"data": {
"id": "ord_01J8ZK8F7M3Y2QW6A9C4B1N0PX",
"status": "pending"
},
"meta": null,
"error": null,
"request_id": "req_01J8ZK9P5V6R8D3T2H4N7M0CQA"
}Successful collection response:
{
"success": true,
"data": [
{ "id": "svc_01J8ZK...", "name": "Printing" }
],
"meta": {
"pagination": {
"next_cursor": "eyJpZCI6InN2Y18uLi4ifQ",
"has_more": true,
"limit": 20
}
},
"error": null,
"request_id": "req_01J8ZK9P5V6R8D3T2H4N7M0CQA"
}Error response:
{
"success": false,
"data": null,
"meta": null,
"error": {
"code": "ORDER_INVALID_TRANSITION",
"message": "The order cannot move from completed to in_progress.",
"fields": [
{
"field": "status",
"code": "invalid_transition",
"message": "A completed order is final."
}
],
"details": null
},
"request_id": "req_01J8ZK9P5V6R8D3T2H4N7M0CQA"
}Envelope rules:
successis always present.- Exactly one of
dataanderroris non-null. metais used for pagination, deprecation notices, and other response metadata; it is not a place for domain data.request_idis always present and matches the response correlation header.error.codeis stable, uppercase, and namespaced by domain, such asAUTH_INVALID_CREDENTIALS,ORDER_NOT_FOUND,PAYMENT_ALREADY_CONFIRMED, andVALIDATION_FAILED.error.messageis safe to display as a fallback. Mobile/web clients should normally localize fromerror.codeand field codes.error.fieldsis present for field validation and otherwise omitted or empty.error.detailsmay contain documented, non-sensitive machine-readable context. It MUST never expose internals.201 Createdis used for created resources,202 Acceptedfor queued work, and204 No Contentonly where there is intentionally no JSON body. Binary downloads, redirects to signed storage, WebSocket frames, health probes, and third-party webhook acknowledgements are exempt from the JSON envelope.- Authentication failures use
401; authenticated but disallowed requests use403; inaccessible records may deliberately return404to avoid revealing existence; version/state conflicts use409; semantic validation uses422; rate limiting uses429and a retry hint.
5.3 Lists
All user-facing feeds and potentially changing lists use cursor pagination. Administrative tables may use page-number pagination only when exact page navigation and total count are required. Apply filters on the server and use stable ordering with an ID tie-breaker. Default and maximum limits are bounded. Collection data is returned in data; pagination information is returned in meta.pagination.
5.4 Time, money, and concurrency
Dates are RFC3339 UTC instants unless a domain value is a local date/time with an explicit IANA timezone. Price is integer minor units and ISO currency. Use optimistic versioning or conditional updates on contested workflow states. A state transition must fail clearly if the record changed concurrently.
6. Domain data requirements
At minimum persist the following logical aggregates (physical schema may normalize them):
| Aggregate | Essential data |
|---|---|
| User | id, email, username, password/provider identity, display name, avatar, locale, role/status, timestamps, deletion state |
| ProviderProfile | owner(s), approval/availability, display name, phone/contact, bio, service area, categories, portfolio, public contact fields |
| ServiceCategory / Service | names/translations, description, icon/image, active state, display ordering, category relation, price range if applicable |
| BusinessListing | owner/provider, title/category, description, cover, status, feature dates, payment QR assets, subscription dates/status |
| MenuItem | listing, name/description, price/currency, active state, ordering |
| Order | customer/guest reference, provider/listing/service, item snapshots, schedule/timezone, address, phone, notes, attachment references, total, currency, order/payment states, timestamps |
| CompletedOrder | immutable completion/reporting snapshot linked to source order, completion actor/time |
| Sale | source order or manual entry, provider/service, amount/currency, classification, actor, occurred-at, audit metadata |
| Conversation / Message | participants, provider/customer relationship, message payload/type, attachments, read/delivery state, created-at |
| PushDevice | user, platform, token, app version, locale, enabled/preferences, updated/last-seen |
| Moment / Video / Comment | author, media references, text, visibility/moderation state, engagement counters/relationships, timestamps |
| DatingProfile / LifestylePreference | owner, opted-in sections, profile data, visibility/moderation state |
| MeetingRoom / Participant / Signal | host, room state, passcode verifier, participant membership, scoped signaling payloads and expiry |
| Livestream | streamer, title, state, provider media-session reference, audience counters, recording reference if retained |
| LifestyleContent / EntertainmentLink / MusicTrack | type/category, content metadata, media or URL, status, ordering, publication dates |
| AccountantAssignment | accountant user, service scope, effective dates, assigning admin |
| AppAnnouncement | version/platform targeting, title/body/localizations, publication window, store URL |
| AuditEvent | actor, action, object type/id, timestamp, request ID, safe before/after summary |
Use soft deletion only where recovery, legal retention, order history, or auditability requires it. Account deletion must remove or anonymize personal data according to the approved retention policy while preserving legally/operationally required financial records without unnecessary personal fields.
6.1 Canonical model conventions
- Public IDs use opaque UUIDv7 or ULID values, optionally with readable prefixes (
usr_,ord_,pay_). Database sequence IDs MUST NOT be exposed. - Mutable aggregates include
versionfor optimistic concurrency andcreated_at/updated_attimestamps. - Optional JSON fields are omitted where the contract allows absence; nullable fields represent meaningful domain absence. Requests MUST distinguish omitted patch fields from explicit null.
- Money uses
{ "amount": 1890, "currency": "CNY" }, where amount is in minor units. - Phone numbers are normalized to E.164 when possible. Email and username uniqueness use normalized comparison while preserving display form where needed.
- API DTOs are separate from persistence rows and internal domain types. Password hashes, storage object keys, payment-provider secrets, internal moderation notes, and soft-delete metadata are never serialized accidentally.
6.2 Core model definitions
The following are contract-level model shapes. The OpenAPI schemas MUST define all required/optional fields, formats, enums, limits, and examples.
type Money struct {
Amount int64 `json:"amount"` // minor units
Currency string `json:"currency"` // ISO 4217
}
type User struct {
ID string `json:"id"`
Username string `json:"username"`
Email string `json:"email"` // self/admin views only
DisplayName string `json:"display_name"`
AvatarURL *string `json:"avatar_url,omitempty"`
Locale string `json:"locale"`
Role UserRole `json:"role"`
Status UserStatus `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type ProviderProfile struct {
ID string `json:"id"`
OwnerUserID string `json:"owner_user_id"`
SecondaryUserID *string `json:"secondary_user_id,omitempty"`
DisplayName string `json:"display_name"`
Bio string `json:"bio"`
Phone *string `json:"phone,omitempty"` // only in authorized views
ProfileImageURL *string `json:"profile_image_url,omitempty"`
ServiceArea string `json:"service_area"`
CategoryIDs []string `json:"category_ids"`
Portfolio []MediaAsset `json:"portfolio"`
ApprovalStatus ApprovalStatus `json:"approval_status"`
Availability Availability `json:"availability"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type BusinessListing struct {
ID string `json:"id"`
ProviderID string `json:"provider_id"`
CategoryID string `json:"category_id"`
Name string `json:"name"`
Description string `json:"description"`
CoverImageURL *string `json:"cover_image_url,omitempty"`
MenuItems []MenuItem `json:"menu_items,omitempty"`
Status ListingStatus `json:"status"`
FeaturedUntil *time.Time `json:"featured_until,omitempty"`
SubscriptionStatus SubscriptionStatus `json:"subscription_status"`
SubscriptionDueAt *time.Time `json:"subscription_due_at,omitempty"`
PaymentMethods []PaymentMethod `json:"payment_methods"`
Version int64 `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type MenuItem struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Price Money `json:"price"`
IsActive bool `json:"is_active"`
SortOrder int `json:"sort_order"`
}
type Order struct {
ID string `json:"id"`
OrderNumber string `json:"order_number"`
CustomerUserID *string `json:"customer_user_id,omitempty"`
GuestReference *string `json:"guest_reference,omitempty"` // privileged views only
ProviderID string `json:"provider_id"`
BusinessID *string `json:"business_id,omitempty"`
ServiceID *string `json:"service_id,omitempty"`
Items []OrderItem `json:"items"` // immutable name/price snapshots
Contact OrderContact `json:"contact"`
Schedule OrderSchedule `json:"schedule"`
Notes *string `json:"notes,omitempty"`
Attachments []MediaAsset `json:"attachments"`
Total Money `json:"total"`
Status OrderStatus `json:"status"`
PaymentStatus PaymentStatus `json:"payment_status"`
Cancellation *Cancellation `json:"cancellation,omitempty"`
Version int64 `json:"version"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CompletedAt *time.Time `json:"completed_at,omitempty"`
}
type OrderItem struct {
MenuItemID *string `json:"menu_item_id,omitempty"`
Name string `json:"name"`
UnitPrice Money `json:"unit_price"`
Quantity int `json:"quantity"`
LineTotal Money `json:"line_total"`
}
type Payment struct {
ID string `json:"id"`
Purpose PaymentPurpose `json:"purpose"` // order or listing_subscription
PurposeID string `json:"purpose_id"`
PayerUserID *string `json:"payer_user_id,omitempty"`
Method PaymentMethod `json:"method"`
Amount Money `json:"amount"`
Status PaymentStatus `json:"status"`
ProviderReference *string `json:"provider_reference,omitempty"`
EvidenceAssetURL *string `json:"evidence_asset_url,omitempty"`
SubmittedAt *time.Time `json:"submitted_at,omitempty"`
ConfirmedAt *time.Time `json:"confirmed_at,omitempty"`
ConfirmedBy *string `json:"confirmed_by,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type Sale struct {
ID string `json:"id"`
ProviderID string `json:"provider_id"`
OrderID *string `json:"order_id,omitempty"`
ServiceID *string `json:"service_id,omitempty"`
BusinessID *string `json:"business_id,omitempty"`
Source SaleSource `json:"source"` // completed_order or manual
Amount Money `json:"amount"`
OccurredAt time.Time `json:"occurred_at"`
RecordedBy string `json:"recorded_by"`
AdjustmentOf *string `json:"adjustment_of,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
type Conversation struct {
ID string `json:"id"`
CustomerUserID string `json:"customer_user_id"`
ProviderID string `json:"provider_id"`
LastMessage *Message `json:"last_message,omitempty"`
UnreadCount int `json:"unread_count"`
LastActivityAt time.Time `json:"last_activity_at"`
}
type Message struct {
ID string `json:"id"`
ConversationID string `json:"conversation_id"`
SenderUserID string `json:"sender_user_id"`
ClientMessageID string `json:"client_message_id"`
Type MessageType `json:"type"`
Text *string `json:"text,omitempty"`
Attachments []MediaAsset `json:"attachments,omitempty"`
CreatedAt time.Time `json:"created_at"`
ReadAt *time.Time `json:"read_at,omitempty"`
}
type MediaAsset struct {
ID string `json:"id"`
Kind MediaKind `json:"kind"`
URL string `json:"url"`
ThumbnailURL *string `json:"thumbnail_url,omitempty"`
MimeType string `json:"mime_type"`
SizeBytes int64 `json:"size_bytes"`
Width *int `json:"width,omitempty"`
Height *int `json:"height,omitempty"`
DurationMS *int64 `json:"duration_ms,omitempty"`
}The snippets show contract intent, not a mandate to place every type in one Go package. Public, owner, admin, and accountant views SHOULD use purpose-specific DTOs so private fields cannot leak through generic serialization.
6.3 Required enums
At minimum define and validate: UserRole, UserStatus, ApprovalStatus, Availability, ListingStatus, SubscriptionStatus, OrderStatus, PaymentStatus, PaymentMethod, PaymentPurpose, SaleSource, MessageType, MediaKind, ModerationStatus, MeetingStatus, and LivestreamStatus. Enum values are lowercase snake case in JSON. Unknown persisted values must fail visibly during development and be handled safely during rolling deployment.
6.4 Relationship rules
- A user may own zero or one provider profile. A provider profile may optionally link one secondary user under the approved pair policy.
- A provider owns many business listings; a listing owns many menu items.
- An order belongs to exactly one provider and refers to either a platform service, a business listing, or both where the catalog design requires it.
- An order contains immutable item snapshots; later menu edits do not change existing orders.
- An order may have multiple payment attempts, but at most one successful confirmed/captured amount for a given payable balance unless partial payments/refunds are explicitly introduced.
- A completed order produces exactly one primary sale. Corrections create adjustment records rather than overwriting history.
- One customer/provider pair has at most one active direct conversation unless a future feature explicitly introduces order-specific conversations.
- Uploaded assets are first-class records. Domain records reference asset IDs; raw object-storage keys stay internal.
7. Functional API requirements
7.1 Catalog and discovery
Provide public/guest-safe read endpoints for categories, services, active provider profiles, active business listings, featured listings, and search. Search MUST support query, category, active status, and pagination. Results MUST exclude suspended, expired, or unpublished listings. Do not expose private contact data unless policy allows it. Home payloads may be composed server-side for efficient rendering but must not become a separate source of truth.
7.2 Providers and business publishing
Support provider registration/update, portfolio asset management, availability, listing create/update/deactivate, menu item CRUD, payment QR asset configuration, featured placement, and subscription status. Admin approval may be required by policy; until approved, profile/listings are not publicly bookable.
Publishing tiers and featured add-on amounts must be configured as server-side price rules, with effective dates and currency. A client must not submit an arbitrary fee. Store fee snapshots on each publication transaction. Manual QR payment produces submitted/awaiting confirmation state until an authorized admin confirms it. A payment submission must never automatically activate listings unless the payment state is confirmed under configured policy.
Monthly renewal processing MUST record due dates, send reminders three days before and on the due date, apply the configured grace/suspension policy, notify the provider, and restore listings after confirmed payment. All automated changes are auditable and idempotent.
The initial manual subscription payment flow creates a listing_subscription payment using server-configured amount, currency, and duration. The provider submits payment evidence; only an administrator can confirm it. Confirmation and subscription extension commit together. A listing becomes public only when its provider is approved and its subscription is active and unexpired. Production configuration must include LISTING_SUBSCRIPTION_AMOUNT, LISTING_SUBSCRIPTION_CURRENCY, and LISTING_SUBSCRIPTION_DAYS.
7.3 Orders and payment states
Order creation validates: service/listing is active and bookable; provider is eligible; items belong to the listing and are active; quantities and prices are valid; required category attachments are present; date/time is valid; guest booking is allowed; calculated totals match server-side prices. Persist an immutable item/price snapshot. Ignore client-calculated totals as authoritative. Create a payment attempt for an authenticated customer’s order from the persisted order amount/currency, and scope payment submission to that customer and confirmation to the listing owner or an administrator. Commit payment and order payment-state changes together.
Order states: pending, confirmed, in_progress, completed, cancelled. Payment states: unpaid, payment_submitted, payment_confirmed, payment_rejected, optionally refunded. Define allowed transitions in one backend state machine. Invalid transitions return a conflict error and do not partially mutate records.
Required behavior:
- Create order atomically and return its summary.
- Notify provider after commit through a transactional outbox.
- Allow authorized provider/admin to update state subject to transition rules.
The
POST /api/v1/orders/{id}/transitionendpoint requires the expectedversion; customers may only cancel their own pending or confirmed orders, while provider owners and admins may apply the configured workflow changes. - Allow customer cancellation only under configured policy and before terminal completion.
- Record customer “I have paid” as a submission/claim, not verified payment.
- Provider/admin confirms or rejects manual payment and records actor/time/evidence note.
- On completion, atomically create/ensure one CompletedOrder and one corresponding Sale, set completion fields, and enqueue customer notification.
- Ensure repeated completion requests cannot duplicate records or notifications.
- Customer hide/delete from own view is a per-customer soft-hide flag and does not remove provider/admin/accountant records.
- Preserve guest order access through a scoped capability and safely support conversion/linking to an account if the product chooses that behavior.
Stripe, if enabled, uses server-created payment intents and verified provider webhooks. Never trust client callbacks as proof of capture. Verify webhook signatures and make webhook processing idempotent. Maintain payment/order state separation.
7.4 Provider dashboard and sales
Provide provider-scoped dashboard aggregates, incoming/active/completed orders, chat summaries, and sales totals. Manual sale creation requires amount, currency, occurred-at, service/listing association when known, customer label/contact only as permitted, and description. Identify it as off-platform. Updates/deletions are restricted and audited. Define whether manual records may be edited and how adjustments are represented; do not silently rewrite historical totals.
7.5 Chat and real-time delivery
Create/find one-to-one customer-provider conversations with participant authorization. Support text, image, and video messages; attachment references must be uploaded through authorized upload flow. Provide conversation list sorted by activity, last-message preview, unread count, message history pagination, read markers, and message send. Enforce that only participants can read/write. Near-real-time delivery uses authenticated WebSocket or equivalent; REST history remains available after reconnect. On message persistence, enqueue push only when recipient is not actively viewing or according to preference rules. Deduplicate retried sends with client message ID/idempotency key.
7.6 Upload and media access
Provide upload initialization/completion or constrained multipart upload flows for images, video, documents, audio, and QR images. Validate file size, extension/MIME by content inspection, ownership, and permitted context. Set per-media limits through server configuration. Reject executable/unsafe content. Record object key, checksum, dimensions/duration where applicable, owner, purpose, visibility, moderation state, and lifecycle. Private media requires signed short-lived access or API proxy. Support deletion/retention and orphan cleanup. Upload completion may enqueue thumbnail/transcoding/scanning jobs.
7.7 Moments, videos, and moderation
Support post creation/edit/delete, feed pagination, likes/unlikes, comment creation and retrieval, counts, optional location, China Place subtype, and visibility. Short video supports upload, metadata, feed, like, comments/replies, comment likes, share event, and deduplicated view event. Counters must be server-controlled or reconciled from relationship/event records; clients cannot set counts directly.
Moderation endpoints support report, block/unblock, admin queue, review, hide/remove/restore, user restriction/suspension, and reason/audit. Filtering must prevent blocked or removed content from appearing to the affected viewer. Apply rate limits and anti-spam controls. Admin access to private content is audited.
7.8 Dating and lifestyle registration
Persist per-user opt-in selections. Enforce opt-in and age eligibility rules before dating profile visibility. Users manage their own profile and visibility. Provide paginated browse with filters allowed by policy; never expose private contact fields until the chosen consent/match policy permits. Report/block controls apply. Specific minimum-age, match/contact, and sensitive-data retention policy requires product-owner definition before release.
7.9 Meetings and livestreams
Meeting endpoints create/list/join/leave/end rooms, track active participants, and authorize host controls. When LiveKit is configured, join returns a short-lived JWT scoped to the meeting room and the authenticated participant; the token permits publishing/subscribing only to that room. Android requests camera/microphone permissions and connects with the LiveKit Android SDK. Media flows through LiveKit, while the API persists membership and never exposes the server secret. Production configuration requires LIVEKIT_URL (wss://), LIVEKIT_API_KEY, and LIVEKIT_API_SECRET.
Livestream lifecycle supports creation, scheduled/live/ended states as selected, viewer join/leave, engagement, and media provider session integration. The API MUST not claim to relay/transcode live video unless a separately provisioned media service does so. Define eligibility (provider-only vs broader eligible members) before enabling create-live endpoints.
7.10 Entertainment and curated content
Admins manage categories, external links, music tracks, comics, games, books/stories, and content metadata. Public reads return only published/active content. Validate external URLs and schemes; disallow unsafe executable schemes. Preserve sort order, localization, and publication windows.
7.11 Notifications and announcements
Register/unregister APNs and FCM tokens and preferences. A user can have multiple devices. Handle token refresh and invalid-token cleanup. Send order, chat, payment, subscription, moderation, and app-announcement notifications as applicable. Notification sending is asynchronous and retryable; API success for the source mutation is not rolled back by a temporary provider outage. Deep-link payloads contain opaque IDs and route hints, never private content. Re-check authorization when a destination loads.
App announcements support title/body/localizations, min/current version targeting, platform targeting, publication window, and optional store URL. Provide a read endpoint and acknowledgement if product analytics need it.
7.12 Admin and accountant APIs
Admin routes support user/role and active/suspended status changes, searchable/paginated users/accountants/services/providers/listings/orders/payments, provider approval, listing status changes, versioned order transitions, payment confirmation/rejection, community report moderation/export, announcements, an operational summary, and audit search. Require administrator authorization on mutations and audit all material changes. Admins replace accountant service assignments explicitly; accountant summaries include only assigned services, and accountants cannot read the global audit log or operational collections.
The operational overview reports active member/provider/listing counts, pending provider approvals, 30-day order and completed-order counts, gross completed-order value, submitted payments, and open/under-review community reports. Collection reads accept q, status, limit, and offset; collection payloads return items, total, limit, and offset. User status is limited to active or suspended; listing status changes are limited to active, draft, or deactivated. Provider approval, status changes, role updates, accountant assignment changes, report moderation, payment confirmation/rejection, and administrator order transitions are written to the audit log and use transactional domain workflows where state spans aggregates.
Accountant routes expose only assigned-service report rows and aggregates. Scope is checked server-side on every query, including exports. Report filters include date range, service, provider where allowed, currency, and order/sale state. Timezone for day/week/month aggregation is an explicit query or configured reporting timezone. Exports are permission-checked and expire if downloaded via signed URL.
8. Endpoint inventory (minimum)
The OpenAPI contract must include at least the following groups; exact route naming may follow repository conventions while preserving semantics:
| Group | Minimum operations |
|---|---|
| Auth | register, verify/resend OTP, login, Google exchange, refresh, logout, forgot/reset password, me, username recovery, delete account |
| Catalog | list categories/services, get detail, featured, search |
| Provider | register/update profile, public profile/detail, availability, portfolio |
| Listings | list/detail/search, create/update/deactivate, menu CRUD, publish/feature/renew payment submission |
| Orders | create, list mine, detail, cancel, hide, provider list, status transition, payment submission/confirm/reject |
| Dashboard | provider summary, sales list, manual sale create/adjust |
| Uploads | initialize, complete, get authorized asset URL, delete where allowed |
| Chat | list/create conversation, list/send messages, mark read, realtime connect |
| Community | Moments CRUD/feed/like/comment/report; video feed/create/like/comment/share/view |
| Lifestyle | opt-in registration, dating profile/browse, curated content reads |
| Meetings | create/list/join/leave/end, signaling channel |
| Livestream | create/start/list/join/leave/end and interaction endpoints where enabled |
| Notifications | device register/remove, preferences, announcements |
| Admin | users, roles, catalog, providers/listings, orders, sales, content, moderation, announcements, audit |
| Accountant | scoped dashboard, sales/completed orders, report/export |
9. Asynchronous processing and event guarantees
Use an outbox or equivalent durable mechanism for events such as order.created, order.status_changed, payment.submitted, payment.confirmed, order.completed, sale.created, chat.message_created, listing.renewal_due, listing.suspended, content.reported, and announcement.published. Events include event ID, aggregate ID, occurrence time, schema version, and trace ID. Consumers are idempotent. Failed delivery retries with bounded backoff and dead-letter visibility. Sensitive data is minimized in event payloads.
10. Security, privacy, and abuse controls
- TLS everywhere; encrypt secrets and sensitive stored fields where warranted.
- Secure password hashing, token rotation/revocation, OTP/reset expiry, brute-force protection, and rate limits.
- Validate all input and uploaded content; protect against injection, mass assignment, SSRF, oversized payloads, and unsafe URL schemes.
- Restrict CORS to approved web origins; CSRF protection if cookie sessions are used.
- Keep PII out of logs; redact tokens and private attachment URLs.
- Track consent/preferences, account deletion requests, and retention actions.
- Rate-limit signup, search, messaging, posting, liking, view events, report actions, and payment submissions.
- Add audit events for role changes, payment confirmations, order completion/cancellation overrides, accountant-scope edits, moderation, and financial adjustments.
- Apply backups, restore procedures, migration rollback planning, and key rotation.
11. Performance and reliability
- Paginate every unbounded collection and use indexes aligned to ownership, status, and ordering filters.
- Target p95 under 500 ms for common cached/read API calls and under 800 ms for typical writes, excluding upload transfer and third-party providers; establish measured SLOs before production.
- Use CDN/object storage for media bytes rather than routing large files through application memory.
- Bound WebSocket connections/messages, enforce heartbeats, reconnect safely, and provide REST recovery.
- Make order/payment transitions transactional and retriable without duplication.
- Provide liveness and readiness checks; readiness validates required database/dependency connectivity without exposing credentials.
- Preserve functionality when push provider is down by queuing delivery.
12. Observability and operations
Structured logs include timestamp, level, service, request/trace ID, route, status, latency, actor ID where safe, and error code. Metrics include request rates/errors/latency, database pool, queue depth/age, notification outcomes, upload failures, WebSocket counts, and workflow transition failures. Traces cover database and external provider calls. Admin actions are searchable through audit records. Alerts cover sustained errors, queue backlog, failed backups, storage issues, and notification failure rates.
13. Environments and configuration
Provide local/development, staging, and production environments with separate databases, storage buckets, credentials, OAuth keys, APNs/FCM keys, Stripe configuration, and callback URLs. Production data must not be copied to developer environments without approved anonymization. Feature flags/configuration control Stripe, livestream eligibility, subscription grace period, upload limits, and moderation settings.
14. Required repository structure
Use the following feature-oriented structure. Small variations are acceptable only when they preserve module ownership and dependency direction.
API/
├── cmd/
│ ├── api/
│ │ └── main.go # HTTP/WebSocket process composition
│ └── worker/
│ └── main.go # outbox jobs, notifications, media jobs
├── internal/
│ ├── app/ # product feature modules
│ │ ├── auth/
│ │ │ ├── domain.go # session, OTP, credential concepts
│ │ │ ├── service.go # exported application interface/use cases
│ │ │ ├── repository.go # persistence interfaces owned by auth
│ │ │ ├── policy.go
│ │ │ ├── transport_http.go
│ │ │ ├── postgres.go
│ │ │ └── events.go
│ │ ├── users/
│ │ ├── catalog/
│ │ ├── providers/
│ │ ├── listings/
│ │ ├── orders/
│ │ │ ├── domain.go # Order aggregate and status rules
│ │ │ ├── service.go # create/cancel/transition/query use cases
│ │ │ ├── repository.go
│ │ │ ├── pricing.go
│ │ │ ├── transport_http.go
│ │ │ ├── postgres.go
│ │ │ └── events.go
│ │ ├── payments/
│ │ │ ├── domain.go # Payment aggregate and payment states
│ │ │ ├── service.go # submit/confirm/reject/Stripe intent
│ │ │ ├── repository.go
│ │ │ ├── gateway.go # payment-provider seam
│ │ │ ├── transport_http.go
│ │ │ ├── transport_webhook.go
│ │ │ ├── postgres.go
│ │ │ └── stripe.go # Stripe adapter, if enabled
│ │ ├── sales/
│ │ ├── chat/
│ │ ├── media/
│ │ ├── community/
│ │ ├── lifestyle/
│ │ ├── meetings/
│ │ ├── livestreams/
│ │ ├── notifications/
│ │ ├── moderation/
│ │ ├── reporting/
│ │ ├── admin/
│ │ ├── audit/
│ │ └── outbox/
│ ├── platform/ # shared technical implementation only
│ │ ├── config/
│ │ ├── database/
│ │ ├── httpserver/
│ │ │ ├── middleware/
│ │ │ ├── response/ # one standard API envelope
│ │ │ └── validation/
│ │ ├── observability/
│ │ ├── queue/
│ │ ├── storage/
│ │ ├── clock/
│ │ └── id/
├── migrations/
├── openapi/
│ ├── openapi.yaml
│ └── examples/
├── docs/
│ └── adr/ # only durable, non-obvious decisions
├── reference/ # local-only implementation material; not committed
├── scripts/
├── testdata/
├── CONTEXT.md # canonical domain vocabulary
├── API_SRS.md
├── go.mod
├── go.sum
├── Makefile
└── README.mdRules for this structure:
cmdcomposes modules and adapters; it contains no business rules.internal/appis the single home for product feature modules and their use cases.internal/platformcontains reusable technical machinery, not marketplace rules and not a generic dumping ground.- Each feature module owns its domain rules and database access. Its
postgres.gosatisfies interfaces defined by that same module. - HTTP handlers decode/authenticate/validate transport input, call one use case, and encode the standard response. They do not contain workflow rules or SQL.
- Domain models do not import HTTP, PostgreSQL, Stripe, FCM, or S3 packages.
- Avoid global
models,services,repositories, andhandlersdirectories. They scatter one feature across the repository and make ownership unclear. - Cross-module database joins for read-only reporting belong in the reporting module. Cross-module writes are coordinated through an application use case and transaction, not direct table edits.
- Generated OpenAPI code, if used, stays in a clearly named generated package and does not become the domain model.
15. Module interfaces and collaboration
15.1 Auth module
Auth owns credentials, OTPs, sessions, refresh-token families, revocation, and external identity verification. Users owns profile and account state. Auth may resolve a user through a narrow Users interface; it must not update user tables directly.
type AuthService interface {
Register(ctx context.Context, cmd RegisterCommand) (RegistrationResult, error)
VerifyEmail(ctx context.Context, cmd VerifyEmailCommand) (Session, error)
Login(ctx context.Context, cmd LoginCommand) (Session, error)
LoginWithGoogle(ctx context.Context, cmd GoogleLoginCommand) (Session, error)
Refresh(ctx context.Context, refreshToken string) (Session, error)
Logout(ctx context.Context, refreshToken string) error
RequestPasswordReset(ctx context.Context, email string) error
ResetPassword(ctx context.Context, cmd ResetPasswordCommand) error
}The interface hides hashing, token signing, OTP persistence, email delivery, Google verification, rotation, and rate-limit bookkeeping. HTTP handlers only map request DTOs to commands and results to the standard envelope.
15.2 Orders module
Orders owns order creation, server-side price snapshots, scheduling/contact details, cancellation, status transitions, customer hiding, and atomic completion. The module receives narrow collaborators for catalog/listing snapshots, payment eligibility, transactions, and event publication.
type OrderService interface {
Create(ctx context.Context, actor Actor, cmd CreateOrderCommand) (Order, error)
Get(ctx context.Context, actor Actor, orderID string) (OrderView, error)
List(ctx context.Context, actor Actor, query ListOrdersQuery) (OrderPage, error)
Cancel(ctx context.Context, actor Actor, cmd CancelOrderCommand) (Order, error)
Transition(ctx context.Context, actor Actor, cmd TransitionOrderCommand) (Order, error)
HideForCustomer(ctx context.Context, actor Actor, orderID string) error
}Transition(..., completed) performs one transaction that locks/checks the order version, validates transition and payment policy, marks the order completed, creates the immutable completed-order snapshot, creates exactly one sale, and writes outbox events. This operation is not assembled in an HTTP handler.
15.3 Payments module
Payments owns payment attempts, manual payment submissions, verification/rejection, Stripe intent creation, and verified webhook handling. It does not own the order lifecycle. A confirmed payment emits payment.confirmed; the Orders module applies any resulting order policy through its own interface.
type PaymentService interface {
MethodsFor(ctx context.Context, actor Actor, purpose PaymentPurposeRef) ([]PaymentMethodView, error)
SubmitManual(ctx context.Context, actor Actor, cmd SubmitManualPaymentCommand) (Payment, error)
ConfirmManual(ctx context.Context, actor Actor, cmd ConfirmManualPaymentCommand) (Payment, error)
RejectManual(ctx context.Context, actor Actor, cmd RejectManualPaymentCommand) (Payment, error)
CreateStripeIntent(ctx context.Context, actor Actor, cmd CreateStripeIntentCommand) (StripeIntentResult, error)
HandleStripeWebhook(ctx context.Context, payload []byte, signature string) error
}Manual confirmation requires a provider/admin policy check, stores confirmer and timestamp, and is idempotent. Stripe webhook handling verifies the provider signature and deduplicates event IDs. Payment amounts and payable references are loaded from authoritative server records.
15.4 Collaboration flow examples
Create order:
HTTP handler
-> Orders.Create(command, idempotency key)
-> Listings/Catalog snapshot reader
-> Orders calculates total and persists order + outbox event in one transaction
-> response envelope returns Order DTO
-> worker consumes order.created and asks Notifications to notify providerManual payment confirmation:
HTTP handler
-> Payments.ConfirmManual(payment ID, actor)
-> payment record confirmed + payment.confirmed outbox event
-> worker/application event handler asks Orders to apply payment policy
-> Orders updates payment projection/status if current state permits
-> customer/provider notification queuedComplete order:
HTTP handler
-> Orders.Transition(order ID, completed, expected version)
-> authorization + transition + payment-policy check
-> order update + CompletedOrder + Sale + outbox events in one DB transaction
-> response returns completed Order DTO
-> workers send notifications and update asynchronous projections15.5 Transaction ownership
The application use case that promises atomic behavior owns the transaction. Repository methods accept the transaction context supplied by that use case. Do not open independent nested transactions inside repositories. Events that must follow committed state are inserted into the outbox within the same database transaction and processed afterward.
15.6 Request and response DTOs
Transport DTOs belong beside the module’s transport adapter or in generated OpenAPI packages. Commands contain validated intent and actor context, not HTTP request objects. Domain aggregates are not serialized directly. Response presenters create purpose-specific DTOs such as PublicProviderResponse, OwnerProviderResponse, CustomerOrderResponse, and AdminOrderResponse to enforce field visibility.
16. Acceptance criteria
The API is acceptable when:
- OpenAPI documents all client-used operations and stable error contracts.
- Authentication, account recovery, refresh/revocation, and guest capability flows work with rate limits.
- Role/ownership tests prove cross-user, cross-provider, accountant-scope, and private-file access is denied.
- Order creation calculates server-side prices, persists snapshots, and safely handles retries.
- State transition tests prove invalid transitions fail and completion creates exactly one completed record and sale.
- Payment submission never implies confirmation; Stripe webhooks (if enabled) are verified and idempotent.
- Chat history, unread state, realtime reconnect, and push fallback work end to end.
- Upload authorization prevents unauthorized access and validates media constraints. Moment image assets remain private until the owner attaches them to a public Moment; attachment validates ownership transactionally, then grants authenticated feed readers signed, short-lived read URLs.
- Admin and accountant reports are correctly scoped and auditable.
- Notification failure does not lose domain events; queued delivery can be inspected and retried.
- Account deletion follows the agreed retention policy and records completion without retaining unnecessary PII.
- Operational health, logs, metrics, backups, and restore instructions are available before production.
17. Decisions required before implementation is locked
- Database/object-storage/queue provider and deployment target.
- Exact OAuth account linking and guest-order recovery behavior.
- Guest booking verification and guest retention period.
- Subscription grace period, renewal confirmation, and featured-duration rules.
- Stripe launch scope, currencies, webhook ownership, and refund policy.
- Provider-only versus member livestream eligibility and media infrastructure.
- Dating minimum age, contact visibility/matching policy, and moderation policy.
- Data retention/deletion obligations for financial records, messages, and media.
- Whether administrators may inspect private chat content and under what audited conditions.