Status: planned contract. The current build is a frontend-only prototype — there is no live backend. All data lives in the browser (IndexedDB, see
src/app/core/db.service.ts). This document specifies the REST API a future backend would implement so the Angular app can be pointed at it with minimal change. Field names match the TypeScript models insrc/app/core/models.ts, which in turn mirror the Cleveland-Marketplace Supabase schema.
Base URL (proposed): https://api.jtechmarketplace.org/v1
- All requests and responses are
application/json. - Timestamps are ISO-8601 UTC strings.
- IDs are opaque strings.
- Money (
price) is a number in USD;0means a free / gemach item. - Collection endpoints return
{ "data": [...], "total": <int> }. - Mutations return the affected resource.
A future backend would issue a bearer token on login. Send it on every authenticated request:
Authorization: Bearer <token>
In the prototype, "auth" is a dummy:
AuthServicejust selects a profile and stores its id inlocalStorage. No token, no password check.
{ "error": { "code": "not_found", "message": "Product not found" } }| Status | Meaning |
|---|---|
200 |
OK |
201 |
Created |
400 |
Validation error |
401 |
Missing / invalid token |
403 |
Authenticated but not allowed (not owner / not admin) |
404 |
Resource not found |
409 |
Conflict (e.g. username taken) |
Create an account.
Request:
{ "username": "rivka_g", "fullName": "Rivka Greenbaum", "email": "rivka@example.com", "password": "•••••" }Response 201: { "token": "...", "user": <Profile> }
Errors: 409 username taken, 400 username < 3 chars.
Request: { "identifier": "rivka_g", "password": "•••••" } — identifier is a username or email.
Response 200: { "token": "...", "user": <Profile> }
Returns the current Profile. Requires auth. 401 if no valid token.
Invalidates the current token. Response 204.
List / search listings. Query parameters:
| Param | Type | Description |
|---|---|---|
q |
string | Full-text search over name + description |
category |
string | Category slug (judaica, seforim, kosher-tech, …) |
condition |
string | New · Like New · Good · Fair · For Parts |
maxPrice |
number | Upper price bound |
status |
string | Defaults to excluding hidden |
sellerId |
string | Listings by one seller |
sort |
string | recent (default) · price-asc · price-desc |
page, pageSize |
number | Pagination |
Response 200: { "data": [<Product>], "total": 42 }
Returns one Product. 404 if missing.
Create a listing. Requires auth — sellerId is taken from the token.
Request:
{
"name": "Sterling Silver Menorah",
"description": "Classic oil menorah…",
"price": 240,
"imageUrls": ["https://…/1.jpg"],
"category": "judaica",
"condition": "Like New",
"location": "Lakewood"
}Response 201: the created Product (status defaults to available).
Update a listing. Requires auth; must be the owner or an admin (403 otherwise).
Body: any subset of the product fields, e.g. { "status": "sold" }.
Response 200: updated Product.
Delete a listing. Owner or admin only. Response 204.
The current user's favorited products. Requires auth.
Response 200: { "data": [<Product>] }
Body: { "productId": "p-001" }. Response 201: <Favorite>.
Remove a favorite. Response 204.
Public. Response 200: { "data": [<Comment>] }.
Requires auth. Body: { "content": "Is this still available?" }.
Response 201: <Comment>.
Author or admin only. Response 204.
The current user's conversations, newest activity first. Requires auth.
Response 200: { "data": [<Conversation & { lastMessage, unread }>] }
Get-or-create a conversation with another user.
Body: { "productId": "p-005" | null, "withUserId": "u-shloimy" }
Response 200/201: <Conversation>.
Messages in a conversation, oldest first. Participant only (403 otherwise).
Body: { "content": "Sounds good — Sunday works." }
Response 201: <Message>.
Marks the other party's messages as read. Response 204.
Public seller profile + their non-hidden listings.
Response 200: { "user": <Profile>, "listings": [<Product>] }
Update your own profile. Requires auth.
Body: subset of { fullName, bio, phone, neighborhood, avatarUrl }.
Response 200: updated Profile.
All admin endpoints require a token whose Profile.role === "admin" (403 otherwise).
All listings including hidden, with seller info. Supports the same query params as GET /products.
All user profiles. Response 200: { "data": [<Profile>], "total": <int> }.
Moderate a listing — typically { "status": "hidden" } or { "status": "available" }.
Profile { id, username, fullName, avatarUrl, bio, phone, email,
role: 'user' | 'admin', neighborhood, createdAt }
Product { id, sellerId, name, description, price, imageUrls: string[],
category, condition, status: 'available'|'pending'|'sold'|'hidden',
location, createdAt, updatedAt }
Favorite { userId, productId, createdAt }
Comment { id, productId, userId, content, createdAt }
Conversation { id, productId: string|null, participantIds: string[], createdAt }
Message { id, conversationId, senderId, content, isRead,
parentMessageId: string|null, createdAt }
CartItem { productId, qty, addedAt } // client-side only in the prototypePayments, shipping/tracking, real authentication & sessions, email notifications, and image uploads are intentionally not implemented — the UI shows them as dummy switches marked "Coming soon".