PreferencesUser
Game Verse

Backend API (Convex)

Function list by table, KISS, args and results

Overview

  • KISS architecture, realtime Convex.
  • Each group (module) corresponds to 1 file in packages/backend/convex/*.
  • Input/output: write concisely; FE uses api.<module>.<function> from Convex codegen.

members.ts

  • create: Create new member
    • Args: { username, passwordHash, name, phone?, order?, active? }
    • Returns: member
    • Note: check unique username.
  • updateProfile: Update name/phone
    • Args: { id, name?, phone? }
    • Returns: member
  • changePassword: Change password
    • Args: { id, passwordHash }
    • Returns: { success }
  • toggleActive: Hide/show member
    • Args: { id, active }
    • Returns: { success }
  • listBrief: Brief list
    • Args: { activeOnly? }
    • Returns: [{ _id, name, username, phone, active, order }]
  • getFull: Member details + relations
    • Args: { id, recentLimit? }
    • Returns: { member, assignedMarkets, recentSurveys }
  • markets: Assigned markets
    • Args: { memberId, activeOnly? }
    • Returns: markets[] (brief)
  • surveysInRange: Member's surveys by date
    • Args: { memberId, fromDay, toDay }
    • Returns: surveys[]

markets.ts

  • create: Create market
    • Args: { name, addressJson, order?, active? }
    • Returns: market
  • update: Update market
    • Args: { id, name?, addressJson? }
    • Returns: market
  • toggleActive: Hide/show market
    • Args: { id, active }
    • Returns: { success }
  • reorder: Update order in bulk
    • Args: { items: [{ id, order }] }
    • Returns: { success }
  • listBrief: Brief list
    • Args: { activeOnly? }
    • Returns: markets[] (brief)
  • getFull: Market details + relations
    • Args: { id, recentLimit? }
    • Returns: { market, assignedMembers, recentSurveys }

assignments.ts (assignments)

  • assign: Assign member to market (idempotent)
    • Args: { marketId, memberId }
    • Returns: assignment
  • unassign: Unassign
    • Args: { marketId, memberId }
    • Returns: { success }
  • listByMarket: Member list by market
    • Args: { marketId }
    • Returns: members[]
  • listByMember: Market list by member
    • Args: { memberId }
    • Returns: markets[]

units.ts (units)

  • listBrief: Unit list
    • Args: { activeOnly? }
    • Returns: units[] (sorted by order)
  • create: Create unit
    • Args: { name, abbr?, order? }
    • Returns: unit (unique name)
  • update: Edit unit
    • Args: { id, name?, abbr? }
    • Returns: unit
  • toggleActive: Hide/show unit
    • Args: { id, active }
    • Returns: { success }
  • safeDelete: Safe delete (only if no products reference it)
  • Args: { id }
  • Returns: { success } | Error if products still use it

products.ts (products)

  • listBrief: Product list
    • Args: { activeOnly? }
    • Returns: products[] (by order)
  • listWithUnits: Product list with units
    • Args: { activeOnly? }
    • Returns: [{ ...product, unit }]
  • create: Create product
    • Args: { name, unitId, note?, order? }
    • Returns: product (unique name)
  • update: Edit product
    • Args: { id, name?, unitId?, note? }
    • Returns: product
  • toggleActive: Hide/show product
    • Args: { id, active }
    • Returns: { success }
  • safeDelete: Safe delete (if no surveyItems reference it)
    • Args: { id }
    • Returns: { success } | Error if still referenced
  • reorder: Update order in bulk
    • Args: { items: [{ id, order }] }
    • Returns: { success }

surveys.ts (survey sessions)

  • createForMarket: Create survey for market + generate items
    • Args: { marketId, memberId, surveyDay?, note?, copyFromPrevious? }
    • Returns: survey
    • Note: generate item from active products by order; can copy prices from most recent survey of same market.
  • getFull: Get survey + items (expand product/unit)
    • Args: { id }
    • Returns: { survey, market, member, items[] }
  • listMineByRange: My survey list by date
    • Args: { memberId, fromDay, toDay }
    • Returns: surveys[] (newest first)
  • deleteCascade: Delete survey and all items
    • Args: { id }
    • Returns: { success }
  • lastInMarket: Most recent survey of market
    • Args: { marketId }
    • Returns: survey | null
  • countFilled: Count rows with entered prices
    • Args: { id }
    • Returns: { filled, total }

surveyItems.ts (survey lines)

  • autosave: Save price/note per line (update lastUpdatedAt)
    • Args: { id, price?, note? }
    • Returns: { success }
  • listWithProductUnit: Item list + product/unit
    • Args: { surveyId }
    • Returns: items[] (expanded)
  • clearPrice: Clear price of one line
    • Args: { id }
    • Returns: { success }
  • bulkClear: Clear prices of multiple lines or all
    • Args: { surveyId, productIds? }
    • Returns: { success }

reports.ts (snapshot reports)

  • summaryByMarketRange: Live aggregation by date range (not saved)
    • Args: { fromDay, toDay }
    • Returns: { summaryRows, includedSurveyIds }
  • generateRange: Create immutable snapshot report
  • Args: { fromDay, toDay, createdByAdminId }
  • Returns: report
  • listBrief: Recent report list
    • Args: { limit? }
    • Returns: [{ _id, fromDay, toDay, generatedAt }]
  • getFull: Report details
    • Args: { id }
    • Returns: report

FE Usage Suggestions

  • /khaosat:
  • Get assigned markets: members.markets -> "Create survey" button.
  • Create survey: surveys.createForMarket -> open surveys.getFull to display form.
  • Autosave: surveyItems.autosave (debounce ~300ms).
  • History: surveys.listMineByRange.
  • /admin:
    • Catalog: units/products/markets CRUD + assignments.
    • Aggregation: reports.summaryByMarketRange -> click "Export" = reports.generateRange.