Skip to main content

Like Groups API

StatusImplementation
✅ Shippedsrc/feats/groups/

A like group is how users apply to attend an event. The group is formed by one user plus optional friends. It starts as waiting while invited friends answer. The creator sends it with /send; only then does it become pending and visible to organizers. Once enough event authors approve it, a private chat is created for the participants.

See also: Approval algorithm, Like-groups model.

All endpoints require Authorization: Bearer <supabase-access-token>.

Endpoints​

MethodPathDescription
GET/groupsList my groups (as creator or member)
GET/groups/:idGet a group
POST/groups/:id/membersAdd a friend to the group
DELETE/groups/:id/members/:userIdRemove a member
GET/groups/:id/approvalsList approvals for the group
POST/groups/:id/approveApprove the group (event author only)
POST/groups/:id/rejectReject the group (event author only)
POST/groups/:id/invite/acceptAccept an invited member position
POST/groups/:id/invite/refuseRefuse an invited member position
POST/groups/:id/sendSend a waiting group to event organizers
DELETE/groups/:idDelete a waiting invitation

Group creation happens via POST /events/:id/like.

Get group​

GET /groups/:id
Authorization: Bearer <token>
{
"id": "uuid",
"eventId": "uuid",
"creatorId": "uuid",
"status": "waiting",
"memberIds": ["uuid", "uuid"],
"createdAt": "2026-04-01T00:00:00Z"
}

Visibility: members of the group, the event creator, and event authors.

Manage members​

POST /groups/:id/members
{ "userId": "friend-uuid" }

Rules:

  • Caller must be the group creator.
  • The user being added must be a friend of the group creator.
  • The user must not already be in another pending group for the same event.
  • Group is in pending (cannot mutate after approved / rejected).
LimitValue
Members per group10 (including creator)
DELETE /groups/:id/members/:userId

Removing members before approval recalculates the approval threshold; existing approvals still count. See Approval algorithm → edge cases.

Approvals​

GET /groups/:id/approvals
{
"data": [
{
"id": "uuid",
"groupId": "uuid",
"authorId": "uuid",
"decision": "consent",
"createdAt": "2026-04-01T00:00:00Z"
}
]
}

The decision field is either consent (approve) or refuse (reject).

POST /groups/:id/approve
POST /groups/:id/reject

Authorization: caller must be a current author of the group's event. Each author can only vote once per group.

Approve — consensus algorithm​

The threshold formula:

effectiveT = min(group_members, eligible_organizers)

Where eligible_organizers = accepted event authors who haven't refused yet.

Status transitions:

  • approved: consents >= effectiveT
  • rejected: eligible - refuses < effectiveT (threshold can no longer be reached)
  • pending: otherwise

When approved:

  1. Group status → approved.
  2. A chat of type approved_group is created with all members + all authors as participants.
  3. Response includes the new chatId.
{
"id": "group-uuid",
"status": "approved",
"approved": true,
"chatId": "new-chat-uuid"
}

Reject​

When a single author refuses and the threshold becomes unreachable, status → rejected. No chat is created.

Errors​

StatusReason
400Group not in pending state
403Caller is not group creator / event author / member
404Group not found
409Member already in another pending group for this event

Constants​

ConstantValue
MAX_MEMBERS10 (including creator)
EXPIRATION_HOURS24
DEFAULT_PAGE_SIZE20
MAX_PAGE_SIZE100

Implementation notes​

Source: src/feats/groups/.

  • The consensus algorithm uses min(group_members, eligible_organizers); see Approval algorithm for the rationale.
  • memberKey is a canonical sorted string of member IDs, ensuring unique pending group compositions per event via a partial unique index.
  • Group lifecycle and chat creation are wrapped in a single Drizzle transaction so partial state cannot escape.
  • Approval decisions are recorded in an append-only like_group_audit table.