Skip to content
Merged
Show file tree
Hide file tree
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
3 changes: 1 addition & 2 deletions prisma/schema/user.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

creatorProfile CreatorProfile?
stellarWallet StellarWallet?
creatorProfile CreatorProfile?
creatorProfile CreatorProfile?
}
28 changes: 28 additions & 0 deletions src/constants/pagination.constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// src/constants/pagination.constants.ts
// Shared pagination defaults for list endpoints.
// Import from here to keep defaults consistent and easy to change centrally.

/**
* Default number of items returned per page.
*/
export const DEFAULT_PAGE_SIZE = 20;

/**
* Default page index (1-based).
*/
export const DEFAULT_PAGE = 1;

/**
* Default offset for offset-based pagination.
*/
export const DEFAULT_OFFSET = 0;

/**
* Maximum allowed page size to prevent oversized queries.
*/
export const MAX_PAGE_SIZE = 100;

/**
* Minimum allowed page size.
*/
export const MIN_PAGE_SIZE = 1;
68 changes: 68 additions & 0 deletions src/modules/creators/creators.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// src/modules/creators/creators.filter.ts
// Parser for creator list filter input. Reusable across list handlers.

/**
* Supported filter keys for creator list requests.
*/
export const SUPPORTED_CREATOR_FILTERS = ['verified', 'search'] as const;

export type CreatorFilterKey = (typeof SUPPORTED_CREATOR_FILTERS)[number];

/**
* Parsed creator filter input ready for use in list queries.
*/
export interface CreatorFilterInput {
verified?: boolean;
search?: string;
}

/**
* Parse and validate raw query filter input for creator list requests.
*
* - Accepts only supported filter keys; rejects unknown ones with an error
* - Coerces `verified` string to boolean
* - Trims `search` string
* - Repeated calls with the same input return the same result
*
* @param raw - Raw query object (e.g. req.query)
* @returns Parsed filter input
* @throws Error if unsupported filter keys are present
*
* @example
* parseCreatorFilters({ verified: 'true', search: 'jazz' })
* // => { verified: true, search: 'jazz' }
*
* @example
* parseCreatorFilters({ unknown: 'value' })
* // throws Error: Unsupported filter key(s): unknown
*/
export function parseCreatorFilters(
raw: Record<string, unknown>
): CreatorFilterInput {
const filterKeys = Object.keys(raw).filter(key =>
SUPPORTED_CREATOR_FILTERS.includes(key as CreatorFilterKey)
);

const unsupported = Object.keys(raw).filter(
key => !SUPPORTED_CREATOR_FILTERS.includes(key as CreatorFilterKey)
);

if (unsupported.length > 0) {
throw new Error(`Unsupported filter key(s): ${unsupported.join(', ')}`);
}

const result: CreatorFilterInput = {};

if (filterKeys.includes('verified') && raw.verified !== undefined) {
result.verified = raw.verified === 'true' || raw.verified === true;
}

if (filterKeys.includes('search') && typeof raw.search === 'string') {
const trimmed = raw.search.trim();
if (trimmed.length > 0) {
result.search = trimmed;
}
}

return result;
}
31 changes: 31 additions & 0 deletions src/modules/creators/creators.handle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// src/modules/creators/creators.handle.ts
// Helper for normalizing creator handles before use in API responses or lookups.

/**
* Normalize a creator handle for consistent storage and lookup.
*
* - Trims leading/trailing whitespace
* - Converts to lowercase
* - Collapses internal whitespace to a single underscore
* - Strips characters that are not alphanumeric, hyphens, or underscores
*
* Repeated calls with the same input always return the same result (idempotent).
*
* @param handle - Raw handle input
* @returns Normalized handle string
*
* @example
* normalizeCreatorHandle(' JazzKing ') // 'jazzking'
* normalizeCreatorHandle('Jazz King') // 'jazz_king'
* normalizeCreatorHandle('Jazz--King') // 'jazz--king' → 'jazz-king'
* normalizeCreatorHandle('Jazz__King') // 'jazz__king' → 'jazz_king'
*/
export function normalizeCreatorHandle(handle: string): string {
return handle
.trim()
.toLowerCase()
.replace(/\s+/g, '_') // internal spaces → underscore
.replace(/[^a-z0-9_-]/g, '') // strip unsupported characters
.replace(/-{2,}/g, '-') // collapse consecutive hyphens
.replace(/_{2,}/g, '_'); // collapse consecutive underscores
}
43 changes: 43 additions & 0 deletions src/modules/creators/creators.stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// src/modules/creators/creators.stats.ts
// Helper for formatting public creator stats in API responses.

import { CreatorMetrics } from '../../types/profile.types';

/**
* Public-facing creator stats shape.
*
* Keeps the response focused on what clients need for public endpoints.
* Avoids leaking internal or sensitive metric fields.
*/
export interface PublicCreatorStats {
holderCount: number;
totalSupply: number;
totalVolume: number;
lastActivityAt?: Date;
}

/**
* Format a CreatorMetrics object into a public stats response.
*
* Centralizes the public stats shape so all creator endpoints
* return a consistent structure.
*
* @param metrics - Internal creator metrics
* @returns Public stats object safe for API responses
*
* @example
* serializePublicCreatorStats({ holderCount: 10, totalSupply: 100, totalVolume: 500 })
* // => { holderCount: 10, totalSupply: 100, totalVolume: 500 }
*/
export function serializePublicCreatorStats(
metrics: CreatorMetrics
): PublicCreatorStats {
return {
holderCount: metrics.holderCount,
totalSupply: metrics.totalSupply,
totalVolume: metrics.totalVolume,
...(metrics.lastActivityAt !== undefined
? { lastActivityAt: metrics.lastActivityAt }
: {}),
};
}
Loading