# BrandingService

**Kind:** Service

**Source:** [`atloria-monorepo/apps/api/src/branding/branding.service.ts`](https://github.com/sherkety/atloria/blob/main/atloria-monorepo/apps/api/src/branding/branding.service.ts#L92)

Project branding — identity + colors + support email. One row per project.
Everything is nullable; the frontend falls back to project name + Atloria
palette when a field is blank.

`BrandingService` manages per-project branding settings, including identity fields, color palette values, and support email configuration. It exposes internal read/update operations and a public-safe read path, while allowing all branding fields to remain nullable so clients can fall back to the project name and default Atloria palette.

## Methods

| Method | Signature | Returns | Description |
|---|---|---|---|
| `get` | `get(projectId: string, user: JwtPayload)` | `unknown` | Get the current branding for a project. |
| `update` | `update(projectId: string, dto: UpdateBrandingDto, user: JwtPayload)` | `unknown` | Upsert the project's branding. |
| `getPublic` | `getPublic(projectId: string)` | `unknown` | Public read — used by the `/public/p/:slugId` endpoint to return branding alongside project info. |

## Dependencies

- `PrismaService`
- `AuditService` _(optional)_
- `PlanService` _(optional)_

## Where it refuses work

- `BrandingService` stops the work with `NotFoundException` when `!project` — “Project not found”.
- `BrandingService` stops the work with `ForbiddenException` when `project.organizationId !== user.organizationId` — “You do not have access to this project”.
- `BrandingService` stops the work with `BadRequestException` when `dto.primaryColor && !HEX_COLOR_REGEX.test(dto.primaryColor)`.
- `BrandingService` stops the work with `BadRequestException` when `dto.accentColor && !HEX_COLOR_REGEX.test(dto.accentColor)`.
- `BrandingService` stops the work with `BadRequestException` when `dto.supportEmail && !EMAIL_REGEX.test(dto.supportEmail)`.
- `BrandingService` stops the work with `BadRequestException` when `dto.theme && !THEME_PRESETS.includes(dto.theme as (typeof THEME_PRESETS)[number])`.

## When something fails

- `BrandingService` handles failure in 3 places: it turns it into a return value in 2, and lets it reach the caller in 1.

## Diagram

```mermaid
sequenceDiagram
  participant Client
  participant BrandingController
  participant BrandingService
  participant BrandingRepository
  participant Project

  Client->>BrandingController: GET /projects/:projectId/branding
  BrandingController->>BrandingService: get(projectId)
  BrandingService->>BrandingRepository: find branding for project
  BrandingRepository-->>BrandingService: Branding row or null
  BrandingService-->>BrandingController: Branding settings
  BrandingController-->>Client: Branding response

  Client->>BrandingController: PATCH /projects/:projectId/branding
  BrandingController->>BrandingService: update(projectId, dto)
  BrandingService->>BrandingRepository: create or update branding row
  BrandingRepository-->>BrandingService: Updated branding
  BrandingService-->>BrandingController: Updated settings
  BrandingController-->>Client: Updated branding

  Client->>BrandingController: GET public branding
  BrandingController->>BrandingService: getPublic(projectId)
  BrandingService->>Project: resolve public project context
  BrandingService-->>BrandingController: Public branding fields
  BrandingController-->>Client: Public branding response
```

## Usage

```ts
import { Injectable } from '@nestjs/common';
import { BrandingService } from './branding.service';

@Injectable()
export class ProjectSettingsService {
  constructor(private readonly brandingService: BrandingService) {}

  async updateProjectBranding(projectId: string) {
    return this.brandingService.update(projectId, {
      name: 'Acme Portal',
      primaryColor: '#2563EB',
      secondaryColor: '#0F172A',
      supportEmail: 'support@acme.example',
    });
  }

  async getPublicProjectBranding(projectId: string) {
    const branding = await this.brandingService.getPublic(projectId);

    return {
      name: branding?.name ?? 'Project',
      primaryColor: branding?.primaryColor ?? '#6366F1',
      supportEmail: branding?.supportEmail ?? null,
    };
  }
}
```

## AI Coding Instructions

- Keep branding scoped to a single project; there should be at most one branding record per project.
- Preserve nullable branding fields during reads and updates—do not replace missing values with defaults in persistence logic.
- Apply frontend or response-layer fallbacks for blank values using the project name and Atloria palette.
- Use `getPublic()` for unauthenticated or externally visible project experiences so only public-safe branding data is exposed.
- When extending branding fields, update the DTOs, persistence schema, and public response mapping consistently.

## Relationships

- DEPENDS_ON → `PrismaService`
- DEPENDS_ON → `AuditService`
- DEPENDS_ON → `PlanService`

## Referenced By

- `BrandingController` (DEPENDS_ON)
- `BrandingModule` (MODULE_PROVIDES)
- `BrandingModule` (MODULE_EXPORTS)
- `DocVersionExportService` (DEPENDS_ON)
- `PublicProjectController` (DEPENDS_ON)
