> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/Ishaq74/concordia/llms.txt
> Use this file to discover all available pages before exploring further.

# Services Marketplace

> Service listings marketplace with availability scheduling, bookings, multi-language support, and reviews

# Services Marketplace

Concordia's Services Marketplace enables providers to list professional services with flexible scheduling, booking management, multi-language translations, and customer reviews.

## Overview

The marketplace is built from:

* `services_listings.schema.ts` - Core service listings
* `services_categories.schema.ts` - Service categorization
* `services_translations.schema.ts` - Multi-language content
* `services_availability.schema.ts` - Availability schedules
* `services_bookings.schema.ts` - Reservation system
* `services_reviews.schema.ts` - Customer reviews

## Service Listings

From `src/database/schemas/services_listings.schema.ts`:

### Core Fields

<ParamField path="id" type="text" required>
  Service unique identifier
</ParamField>

<ParamField path="slug" type="text" required>
  Unique URL-friendly identifier
</ParamField>

<ParamField path="categoryId" type="text">
  Reference to service category
</ParamField>

<ParamField path="providerId" type="text" required>
  User ID of the service provider
</ParamField>

<ParamField path="organizationId" type="text">
  Optional organization offering this service
</ParamField>

### Service Status

<ParamField path="status" type="text" default="pending_review">
  Listing status: `pending_review`, `active`, `suspended`, `archived`
</ParamField>

<ParamField path="isActive" type="boolean" default={true}>
  Whether the service is currently accepting bookings
</ParamField>

<ParamField path="isFeatured" type="boolean" default={false}>
  Featured services get prominent placement
</ParamField>

<ParamField path="displayInHome" type="boolean" default={false}>
  Display on homepage
</ParamField>

### Pricing

<ParamField path="basePrice" type="text">
  Base price as string (for precise decimal handling)

  Example: `"50.00"`
</ParamField>

<ParamField path="priceType" type="text">
  Pricing model: `hourly`, `fixed`, `free`, `negotiable`
</ParamField>

<ParamField path="currency" type="text" default="EUR">
  ISO 4217 currency code
</ParamField>

### Service Details

<ParamField path="durationMinutes" type="integer">
  Standard service duration in minutes

  Example: `60` for 1-hour service
</ParamField>

<ParamField path="isMobile" type="boolean" default={false}>
  Whether provider travels to customer location
</ParamField>

<ParamField path="maxParticipants" type="integer">
  Maximum number of participants (for workshops/classes)

  Example: `10`
</ParamField>

### Booking Policies

<ParamField path="bookingAdvanceHours" type="integer">
  Minimum hours in advance required for booking

  Example: `24` (1 day notice)
</ParamField>

<ParamField path="cancellationHours" type="integer">
  Hours before service start when free cancellation is allowed

  Example: `48` (2 days)
</ParamField>

### Content Settings

<ParamField path="allowReviews" type="boolean" default={true}>
  Enable customer reviews for this service
</ParamField>

<ParamField path="inLanguage" type="text" default="fr">
  Primary language of the service
</ParamField>

## Service Categories

From `src/database/schemas/services_categories.schema.ts`:

<ParamField path="id" type="text" required>
  Category identifier
</ParamField>

<ParamField path="slug" type="text" required>
  Unique URL slug
</ParamField>

<ParamField path="name" type="jsonb" required>
  Category name in multiple languages

  ```json theme={null}
  {
    "fr": "Plomberie",
    "en": "Plumbing"
  }
  ```
</ParamField>

<ParamField path="description" type="jsonb">
  Category description in multiple languages
</ParamField>

<ParamField path="icon" type="text">
  Icon identifier (e.g., MDI icon name)

  Example: `"mdi:wrench"`
</ParamField>

<ParamField path="featuredImageId" type="text">
  Reference to featured image
</ParamField>

<ParamField path="parentId" type="text">
  Parent category for hierarchical structure
</ParamField>

<ParamField path="sortOrder" type="integer" default={0}>
  Display order within parent
</ParamField>

<ParamField path="displayInHome" type="boolean" default={false}>
  Show on homepage
</ParamField>

<ParamField path="displayInMenu" type="boolean" default={true}>
  Show in navigation menu
</ParamField>

<ParamField path="isActive" type="boolean" default={true}>
  Category is active and visible
</ParamField>

<ParamField path="isFeatured" type="boolean" default={false}>
  Featured categories get special prominence
</ParamField>

<Note>
  Categories are hierarchical via `parentId` and ordered by `sortOrder` for flexible taxonomy.
</Note>

## Multi-Language Translations

From `src/database/schemas/services_translations.schema.ts`:

<ParamField path="id" type="text" required>
  Translation entry ID
</ParamField>

<ParamField path="serviceId" type="text" required>
  Reference to parent service listing
</ParamField>

<ParamField path="inLanguage" type="text" required>
  ISO 639-1 language code
</ParamField>

<ParamField path="title" type="jsonb" required>
  Service title in this language

  Example: `{"fr": "Réparation de plomberie d'urgence"}`
</ParamField>

<ParamField path="description" type="jsonb" required>
  Full service description
</ParamField>

<ParamField path="shortDescription" type="jsonb">
  Brief summary for listings
</ParamField>

<ParamField path="seoTitle" type="jsonb">
  SEO-optimized page title
</ParamField>

<ParamField path="seoDescription" type="jsonb">
  Meta description for search engines
</ParamField>

<ParamField path="seoKeywords" type="jsonb">
  SEO keywords array
</ParamField>

<ParamField path="canonicalUrl" type="jsonb">
  Canonical URL for this translation
</ParamField>

## Availability Scheduling

From `src/database/schemas/services_availability.schema.ts`:

<ParamField path="id" type="text" required>
  Availability entry ID
</ParamField>

<ParamField path="serviceId" type="text" required>
  Reference to service listing
</ParamField>

<ParamField path="dayOfWeek" type="integer" required>
  Day of week: 0 (Sunday) to 6 (Saturday)
</ParamField>

<ParamField path="startTime" type="text" required>
  Start time in HH:MM format

  Example: `"09:00"`
</ParamField>

<ParamField path="endTime" type="text" required>
  End time in HH:MM format

  Example: `"17:00"`
</ParamField>

<ParamField path="isAvailable" type="boolean" default={true}>
  Whether this time slot is available for booking
</ParamField>

### Unique Constraint

The schema enforces uniqueness on `(serviceId, dayOfWeek, startTime)` to prevent duplicate time slots:

```typescript theme={null}
uniqueIndex("idx_services_availability_unique").on(
  table.serviceId,
  table.dayOfWeek,
  table.startTime,
)
```

<Info>
  This allows providers to define recurring weekly schedules. Each service can have multiple availability windows per day.
</Info>

## Bookings & Reservations

From `src/database/schemas/services_bookings.schema.ts`:

### Booking Fields

<ParamField path="id" type="text" required>
  Booking unique identifier
</ParamField>

<ParamField path="serviceId" type="text" required>
  Reference to booked service
</ParamField>

<ParamField path="customerId" type="text" required>
  User ID of the customer
</ParamField>

<ParamField path="providerId" type="text" required>
  User ID of the service provider
</ParamField>

### Booking Schedule

<ParamField path="bookingDate" type="text" required>
  Date of service in YYYY-MM-DD format

  Example: `"2026-03-15"`
</ParamField>

<ParamField path="bookingTime" type="text" required>
  Time of service in HH:MM format

  Example: `"14:00"`
</ParamField>

<ParamField path="durationMinutes" type="integer" required>
  Duration of this booking in minutes

  Example: `90`
</ParamField>

### Payment

<ParamField path="totalPrice" type="text">
  Final price as string for precision

  Example: `"75.00"`
</ParamField>

<ParamField path="currency" type="text" default="EUR">
  ISO 4217 currency code
</ParamField>

### Booking Status

<ParamField path="status" type="text" default="pending">
  Booking status: `pending`, `confirmed`, `completed`, `cancelled`, `no_show`
</ParamField>

### Communication

<ParamField path="customerMessage" type="text">
  Message from customer to provider
</ParamField>

<ParamField path="providerResponse" type="text">
  Provider's response to booking
</ParamField>

### Lifecycle Timestamps

<ParamField path="cancelledAt" type="timestamp">
  When booking was cancelled (if applicable)
</ParamField>

<ParamField path="completedAt" type="timestamp">
  When service was completed
</ParamField>

<ParamField path="createdAt" type="timestamp" required>
  When booking was created
</ParamField>

<ParamField path="updatedAt" type="timestamp" required>
  Last modification time
</ParamField>

## Reviews & Ratings

From `src/database/schemas/services_reviews.schema.ts`:

<ParamField path="id" type="text" required>
  Review identifier
</ParamField>

<ParamField path="serviceId" type="text" required>
  Service being reviewed
</ParamField>

<ParamField path="parentId" type="text">
  Parent review ID for threaded replies (provider responses)
</ParamField>

### Author Information

<ParamField path="authorName" type="text" required>
  Reviewer's display name
</ParamField>

<ParamField path="authorEmail" type="text" required>
  Reviewer's email (for notifications)
</ParamField>

<ParamField path="authorId" type="text">
  Optional authenticated user ID
</ParamField>

### Review Content

<ParamField path="content" type="jsonb" required>
  Review text as structured JSON
</ParamField>

<ParamField path="rating" type="integer" required>
  Star rating (typically 1-5)
</ParamField>

<ParamField path="inLanguage" type="text" required>
  Language code of the review
</ParamField>

### Moderation

<ParamField path="status" type="text" default="pending">
  Review status: `pending`, `approved`, `rejected`
</ParamField>

<Warning>
  Reviews require moderation before being visible to prevent spam and inappropriate content.
</Warning>

### Threaded Replies

The schema supports threaded discussions via `parentId`:

```typescript theme={null}
export const servicesReviewsRelations = relations(servicesReviews, ({ one, many }) => ({
  service: one(servicesListings, {
    fields: [servicesReviews.serviceId],
    references: [servicesListings.id],
  }),
  parent: one(servicesReviews, {
    fields: [servicesReviews.parentId],
    references: [servicesReviews.id],
    relationName: "review_replies",
  }),
  replies: many(servicesReviews, {
    relationName: "review_replies",
  }),
}));
```

<Info>
  Providers can respond to reviews by creating a reply with `parentId` set to the original review.
</Info>

## Media Management

From `services_listings.schema.ts`, services can have multiple media items:

```typescript theme={null}
export const servicesMediaLinks = pgTable("services_media_links", {
  serviceId: text("service_id").notNull(),
  mediaId: text("media_id").notNull(),
  type: text("type").notNull(), // "cover", "gallery"
  position: text("position"),
});
```

<ParamField path="type" type="text" required>
  Media usage type: `cover` for main image, `gallery` for additional images
</ParamField>

<ParamField path="position" type="text">
  Display order for gallery images
</ParamField>

## Database Indexes

Performance optimizations from schema files:

### Listings Indexes

```sql theme={null}
CREATE UNIQUE INDEX idx_services_listings_slug ON services_listings(slug);
CREATE INDEX idx_services_listings_status ON services_listings(status);
CREATE INDEX idx_services_listings_category ON services_listings(category_id);
CREATE INDEX idx_services_listings_provider ON services_listings(provider_id);
CREATE INDEX idx_services_listings_featured ON services_listings(is_featured);
CREATE INDEX idx_services_listings_active ON services_listings(is_active);
```

### Bookings Indexes

```sql theme={null}
CREATE INDEX idx_services_bookings_service ON services_bookings(service_id);
CREATE INDEX idx_services_bookings_customer ON services_bookings(customer_id);
CREATE INDEX idx_services_bookings_provider ON services_bookings(provider_id);
CREATE INDEX idx_services_bookings_status ON services_bookings(status);
CREATE INDEX idx_services_bookings_date ON services_bookings(booking_date);
```

### Reviews Indexes

```sql theme={null}
CREATE INDEX idx_services_reviews_service ON services_reviews(service_id);
CREATE INDEX idx_services_reviews_status ON services_reviews(status);
CREATE INDEX idx_services_reviews_rating ON services_reviews(rating);
CREATE INDEX idx_services_reviews_parent ON services_reviews(parent_id);
```

## Integration with Places

From `concordia-specs.md` (lines 955-980), services can be linked to physical places:

```typescript theme={null}
place_id: uuid FK → place.id  // Professional location
```

This enables:

* Professional services tied to a business location
* Geographic search for local services
* Reviews aggregation across place and services

## Marketplace Flow

1. **Provider creates service** → Status: `pending_review`
2. **Admin moderates** → Status: `active` or `rejected`
3. **Provider sets availability** → Define weekly schedule
4. **Customer books service** → Creates booking with `pending` status
5. **Provider confirms** → Booking status: `confirmed`
6. **Service completed** → Booking status: `completed`, `completedAt` set
7. **Customer reviews** → Review created with `pending` status
8. **Moderator approves** → Review status: `approved`

## Best Practices

### Pricing Strategy

* Use `priceType` to set customer expectations
* Store prices as strings for decimal precision
* Always specify `currency` for international users

### Availability Management

* Define clear weekly schedules
* Use `isAvailable: false` for temporary blocks
* Consider `bookingAdvanceHours` for preparation time

### Booking Policies

* Set reasonable `cancellationHours` for flexibility
* Use `customerMessage` for special requirements
* Update status promptly for good customer experience

### Review Moderation

* Review all pending reviews within 24 hours
* Allow providers to respond via threaded replies
* Use `approved` status only for legitimate reviews
