Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions docs/api/creator-query-normalization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Creator Query Normalization

This document describes how raw query parameters for creator lookup endpoints are
normalized before use. See [`src/utils/public-query-parse.utils.ts`](../../src/utils/public-query-parse.utils.ts)
for the generic Zod-based validation layer that wraps these parameters.

## Overview

Creator lookup endpoints accept three optional identifier fields. Each field is
read directly from `req.query` by
[`parseCreatorPublicQuery`](../../src/utils/creator-public-query.util.ts) and
forwarded to the service layer as-is. No coercion, trimming, or case-folding is
applied at the utility level — normalization is the caller's responsibility.
Comment on lines +3 to +13
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This states the creator lookup endpoints read req.query via parseCreatorPublicQuery, but parseCreatorPublicQuery is not referenced anywhere in src/ (it’s currently only defined in src/utils/creator-public-query.util.ts). Either update the docs to describe the actual current call path/schemas that normalize these params, or clarify that this utility (and these query keys) are not wired into any endpoint yet.

Suggested change
This document describes how raw query parameters for creator lookup endpoints are
normalized before use. See [`src/utils/public-query-parse.utils.ts`](../../src/utils/public-query-parse.utils.ts)
for the generic Zod-based validation layer that wraps these parameters.
## Overview
Creator lookup endpoints accept three optional identifier fields. Each field is
read directly from `req.query` by
[`parseCreatorPublicQuery`](../../src/utils/creator-public-query.util.ts) and
forwarded to the service layer as-is. No coercion, trimming, or case-folding is
applied at the utility level — normalization is the caller's responsibility.
This document describes the creator-related public query keys currently defined
in the codebase and how they are intended to be normalized before use. See
[`src/utils/public-query-parse.utils.ts`](../../src/utils/public-query-parse.utils.ts)
for the generic Zod-based validation layer that wraps public query parameters.
## Overview
The creator-specific helper
[`parseCreatorPublicQuery`](../../src/utils/creator-public-query.util.ts)
defines three optional identifier fields for creator lookups. However, this
utility is not currently referenced by endpoint code in `src/`, so the fields
below should be read as documented query definitions rather than a guaranteed
live endpoint call path. Where these fields are used by callers, no coercion,
trimming, or case-folding is applied at the utility level — normalization
remains the caller's responsibility.

Copilot uses AI. Check for mistakes.

| Query key | Constant | Typical format |
| :--------------- | :------------------------------------------ | :---------------------- |
| `creatorId` | `CREATOR_PUBLIC_QUERY_KEYS.CREATOR_ID` | UUID v4 string |
| `creatorAddress` | `CREATOR_PUBLIC_QUERY_KEYS.CREATOR_ADDRESS` | Stellar public key (G…) |
| `username` | `CREATOR_PUBLIC_QUERY_KEYS.USERNAME` | Lowercase handle |
Comment on lines +15 to +19
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Markdown tables here start with || instead of |, which renders an extra empty first column in GitHub Markdown. Update the table rows/header separators to use a single leading | so the table columns align as intended.

Copilot uses AI. Check for mistakes.

## Field-by-field rules

### `creatorId`

Identifies a creator by their internal database UUID.

**Rules applied by callers before querying:**

- Must be a valid UUID v4 (`xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx`).
- Leading/trailing whitespace should be stripped.
- Case is insignificant for UUID comparison but the stored value is lowercase —
normalize to lowercase before querying.

**Before / after examples:**

| Raw query string | Normalized value used in query |
| :------------------------ | :----------------------------------- |
| `creatorId=ABC-123` | rejected — not a valid UUID |
| `creatorId= 3f6a1b2c-… ` | `3f6a1b2c-…` (whitespace stripped) |
| `creatorId=3F6A1B2C-…` | `3f6a1b2c-…` (lowercased) |
| `creatorId=3f6a1b2c-…` | `3f6a1b2c-…` (unchanged, valid UUID) |
Comment on lines +36 to +41
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same table formatting issue as above: these before/after examples use || at the start of each row, which adds an unintended empty column in rendered Markdown. Use a single leading | for each row.

Copilot uses AI. Check for mistakes.

---

### `creatorAddress`

Identifies a creator by their linked Stellar public key.

**Rules applied by callers before querying:**

- Must start with `G` and be 56 characters (Stellar ed25519 public key format).
- Leading/trailing whitespace should be stripped.
- Stellar addresses are case-sensitive — do **not** normalize case.

**Before / after examples:**

| Raw query string | Normalized value used in query |
| :-------------------------- | :---------------------------------------------- |
| `creatorAddress= GABC…XYZ ` | `GABC…XYZ` (whitespace stripped) |
| `creatorAddress=gabc…xyz` | rejected — lowercase Stellar address is invalid |
| `creatorAddress=GABC…XYZ` | `GABC…XYZ` (unchanged, valid address) |
| `creatorAddress=SHORTKEY` | rejected — not 56 characters |

Comment on lines +57 to +63
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same table formatting issue as above: this table uses || at the start of rows/headers, which renders an extra blank first column. Switch to a single leading | for proper GitHub Markdown tables.

Copilot uses AI. Check for mistakes.
---

### `username`

Identifies a creator by their public handle.

**Rules applied by callers before querying:**

- Leading/trailing whitespace should be stripped.
- Stored usernames are lowercase — normalize to lowercase before querying so
that `Alice`, `alice`, and `ALICE` all resolve to the same creator.
- Empty string after trimming is treated as absent (no filter applied).

**Before / after examples:**

| Raw query string | Normalized value used in query |
| :--------------------- | :----------------------------- |
| `username=Alice` | `alice` |
| `username= Alice ` | `alice` (trimmed + lowercased) |
| `username=MUSIC_MAKER` | `music_maker` |
| `username=` | _(parameter ignored)_ |

Comment on lines +79 to +85
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same table formatting issue as above: this table’s rows start with ||, which creates an extra empty column when rendered. Use a single leading | for each row to match standard GitHub Markdown table syntax.

Copilot uses AI. Check for mistakes.
## Validation integration

These normalization rules are enforced at the route/controller layer using the
`parsePublicQuery` helper from
[`src/utils/public-query-parse.utils.ts`](../../src/utils/public-query-parse.utils.ts).
A Zod schema wraps each field, applies `.trim()` and `.toLowerCase()` transforms
where appropriate, and returns a typed `{ ok: true, data }` result or a
structured `{ ok: false, details }` error array suitable for a `400` response.

```ts
// Example schema used with parsePublicQuery
const creatorLookupSchema = z.object({
creatorId: z.string().uuid().optional(),
creatorAddress: z.string().length(56).startsWith('G').optional(),
username: z.string().trim().toLowerCase().optional(),
Comment on lines +98 to +100
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example schema doesn’t match the normalization rules described above: creatorId isn’t trimmed/lowercased before UUID validation, creatorAddress isn’t trimmed, and username will return '' (not undefined) for username= after .trim(). Consider updating the example to show the same trim/case-folding/empty-string-to-absent behavior the doc specifies.

Suggested change
creatorId: z.string().uuid().optional(),
creatorAddress: z.string().length(56).startsWith('G').optional(),
username: z.string().trim().toLowerCase().optional(),
creatorId: z.string()
.trim()
.toLowerCase()
.pipe(z.string().uuid())
.optional(),
creatorAddress: z.string()
.trim()
.pipe(z.string().length(56).startsWith('G'))
.optional(),
username: z.string()
.trim()
.toLowerCase()
.transform((value) => (value === '' ? undefined : value))
.optional(),

Copilot uses AI. Check for mistakes.
});
```

## Related files

- [`src/utils/creator-public-query.util.ts`](../../src/utils/creator-public-query.util.ts) — raw query extraction
- [`src/utils/public-query-parse.utils.ts`](../../src/utils/public-query-parse.utils.ts) — Zod-based parse + validation
- [`src/constants/creator-public-query.constants.ts`](../../src/constants/creator-public-query.constants.ts) — query key constants
- [`src/modules/creator/`](../../src/modules/creator/) — creator module routes and services
Loading