[FE-TYPE-009] fe-type: Add type definitions for query params
- Created comprehensive query parameter types (queryParams.ts): * Pagination: PaginationQueryParams * Sorting: SortQueryParams * Search: SearchQueryParams, TrackSearchQueryParams, PlaylistSearchQueryParams, UserSearchQueryParams * Lists: TrackListQueryParams, PlaylistListQueryParams, ConversationListQueryParams, MessageListQueryParams * Filters: LibraryQueryParams, MarketplaceQueryParams, NotificationQueryParams, AuditLogQueryParams * Analytics: AnalyticsQueryParams * Auth: ResetPasswordQueryParams, VerifyEmailQueryParams, OAuthCallbackQueryParams * Utility: ShareQueryParams, EmbedQueryParams, AdminQueryParams, SettingsQueryParams - Added helper functions: parseQueryParams, buildQueryString, convertQueryParams - Added parsing helpers: parsePaginationParams, parseBooleanParam, parseNumberParam - Ensures type safety for all URL query string handling
This commit is contained in:
parent
9627153921
commit
f7d8726808
2 changed files with 396 additions and 2 deletions
|
|
@ -9394,7 +9394,7 @@
|
|||
"description": "Add types for all query parameters",
|
||||
"owner": "frontend",
|
||||
"estimated_hours": 3,
|
||||
"status": "todo",
|
||||
"status": "completed",
|
||||
"files_involved": [],
|
||||
"implementation_steps": [
|
||||
{
|
||||
|
|
@ -9415,7 +9415,8 @@
|
|||
"Unit tests",
|
||||
"Integration tests"
|
||||
],
|
||||
"notes": ""
|
||||
"notes": "Created comprehensive query parameter type definitions. Added types for PaginationQueryParams, SortQueryParams, SearchQueryParams, TrackSearchQueryParams, PlaylistSearchQueryParams, UserSearchQueryParams, LibraryQueryParams, MarketplaceQueryParams, TrackListQueryParams, PlaylistListQueryParams, ConversationListQueryParams, MessageListQueryParams, NotificationQueryParams, AuditLogQueryParams, AnalyticsQueryParams, FilterQueryParams, DateRangeQueryParams, ResetPasswordQueryParams, VerifyEmailQueryParams, OAuthCallbackQueryParams, ShareQueryParams, EmbedQueryParams, AdminQueryParams, SettingsQueryParams. Added helper functions: parseQueryParams, buildQueryString, convertQueryParams, parsePaginationParams, parseBooleanParam, parseNumberParam.",
|
||||
"completed_at": "2025-12-25T14:46:55.872621Z"
|
||||
},
|
||||
{
|
||||
"id": "FE-TYPE-010",
|
||||
|
|
|
|||
393
apps/web/src/types/queryParams.ts
Normal file
393
apps/web/src/types/queryParams.ts
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/**
|
||||
* Query Parameter Types
|
||||
* FE-TYPE-009: Add type definitions for all query parameters
|
||||
*
|
||||
* Comprehensive type definitions for all query parameters used throughout
|
||||
* the application, ensuring type safety for URL query string handling.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base query parameter types
|
||||
*/
|
||||
export type SortOrder = 'asc' | 'desc';
|
||||
export type SortDirection = 'asc' | 'desc';
|
||||
|
||||
/**
|
||||
* Pagination Query Parameters
|
||||
* Common query params: ?page=1&limit=20
|
||||
*/
|
||||
export interface PaginationQueryParams {
|
||||
page?: string; // Page number (as string from URL)
|
||||
limit?: string; // Items per page (as string from URL)
|
||||
cursor?: string; // Cursor for cursor-based pagination
|
||||
offset?: string; // Offset for offset-based pagination
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort Query Parameters
|
||||
* Common query params: ?sort_by=name&sort_order=asc
|
||||
*/
|
||||
export interface SortQueryParams {
|
||||
sort_by?: string; // Field to sort by
|
||||
sort_order?: SortOrder; // Sort direction
|
||||
sort?: string; // Alternative sort field name
|
||||
order?: SortOrder; // Alternative order field name
|
||||
}
|
||||
|
||||
/**
|
||||
* Search Query Parameters
|
||||
* Route: /search?q=query&type=tracks
|
||||
*/
|
||||
export interface SearchQueryParams {
|
||||
q?: string; // Search query
|
||||
query?: string; // Alternative query parameter name
|
||||
type?: 'all' | 'track' | 'tracks' | 'playlist' | 'playlists' | 'user' | 'users';
|
||||
page?: string;
|
||||
limit?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track Search Query Parameters
|
||||
* Advanced search with filters
|
||||
*/
|
||||
export interface TrackSearchQueryParams {
|
||||
q?: string; // Search query
|
||||
tags?: string; // Comma-separated tags
|
||||
tag_mode?: 'all' | 'any'; // Tag matching mode
|
||||
min_duration?: string; // Minimum duration in seconds
|
||||
max_duration?: string; // Maximum duration in seconds
|
||||
min_bpm?: string; // Minimum BPM
|
||||
max_bpm?: string; // Maximum BPM
|
||||
genre?: string; // Genre filter
|
||||
format?: string; // Audio format (mp3, flac, etc.)
|
||||
min_date?: string; // Minimum date (ISO8601)
|
||||
max_date?: string; // Maximum date (ISO8601)
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: string;
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Playlist Search Query Parameters
|
||||
*/
|
||||
export interface PlaylistSearchQueryParams {
|
||||
q?: string; // Search query
|
||||
owner?: string; // Owner username or ID
|
||||
is_public?: 'true' | 'false'; // Public/private filter
|
||||
min_tracks?: string; // Minimum number of tracks
|
||||
max_tracks?: string; // Maximum number of tracks
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: 'name' | 'created_at' | 'updated_at' | 'track_count' | 'follower_count';
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* User Search Query Parameters
|
||||
*/
|
||||
export interface UserSearchQueryParams {
|
||||
q?: string; // Search query
|
||||
role?: string; // User role filter
|
||||
is_verified?: 'true' | 'false'; // Verified users only
|
||||
is_active?: 'true' | 'false'; // Active users only
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: 'username' | 'created_at' | 'last_login';
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Library Query Parameters
|
||||
* Route: /library?filter=tracks&sort=recent
|
||||
*/
|
||||
export interface LibraryQueryParams {
|
||||
filter?: 'all' | 'tracks' | 'playlists' | 'favorites' | 'recent';
|
||||
sort?: 'recent' | 'name' | 'artist' | 'date' | 'title';
|
||||
order?: SortOrder;
|
||||
page?: string;
|
||||
limit?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Marketplace Query Parameters
|
||||
* Route: /marketplace?category=music&price_min=10&price_max=100
|
||||
*/
|
||||
export interface MarketplaceQueryParams {
|
||||
category?: string; // Product category
|
||||
price_min?: string; // Minimum price
|
||||
price_max?: string; // Maximum price
|
||||
currency?: string; // Currency code
|
||||
sort?: 'price' | 'date' | 'popularity' | 'rating';
|
||||
order?: SortOrder;
|
||||
page?: string;
|
||||
limit?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track List Query Parameters
|
||||
*/
|
||||
export interface TrackListQueryParams {
|
||||
artist?: string; // Filter by artist
|
||||
genre?: string; // Filter by genre
|
||||
year?: string; // Filter by year
|
||||
is_public?: 'true' | 'false'; // Public/private filter
|
||||
status?: 'uploading' | 'processing' | 'completed' | 'failed';
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: 'title' | 'artist' | 'created_at' | 'play_count' | 'like_count';
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Playlist List Query Parameters
|
||||
*/
|
||||
export interface PlaylistListQueryParams {
|
||||
user_id?: string; // Filter by user
|
||||
is_public?: 'true' | 'false'; // Public/private filter
|
||||
min_tracks?: string; // Minimum number of tracks
|
||||
max_tracks?: string; // Maximum number of tracks
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: 'name' | 'created_at' | 'updated_at' | 'track_count';
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conversation List Query Parameters
|
||||
*/
|
||||
export interface ConversationListQueryParams {
|
||||
type?: 'direct' | 'group'; // Conversation type
|
||||
unread_only?: 'true' | 'false'; // Show only unread
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: 'name' | 'updated_at' | 'created_at';
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message List Query Parameters
|
||||
*/
|
||||
export interface MessageListQueryParams {
|
||||
conversation_id?: string; // Filter by conversation
|
||||
sender_id?: string; // Filter by sender
|
||||
message_type?: 'text' | 'image' | 'audio' | 'file';
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: 'created_at' | 'updated_at';
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notification Query Parameters
|
||||
*/
|
||||
export interface NotificationQueryParams {
|
||||
type?: string; // Notification type filter
|
||||
read?: 'true' | 'false'; // Read/unread filter
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: 'created_at';
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Audit Log Query Parameters
|
||||
*/
|
||||
export interface AuditLogQueryParams {
|
||||
user_id?: string; // Filter by user
|
||||
action?: string; // Filter by action
|
||||
resource?: string; // Filter by resource type
|
||||
start_date?: string; // Start date (ISO8601)
|
||||
end_date?: string; // End date (ISO8601)
|
||||
page?: string;
|
||||
limit?: string;
|
||||
sort_by?: 'timestamp' | 'action' | 'resource';
|
||||
sort_order?: SortOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analytics Query Parameters
|
||||
*/
|
||||
export interface AnalyticsQueryParams {
|
||||
start_date?: string; // Start date (ISO8601)
|
||||
end_date?: string; // End date (ISO8601)
|
||||
metric?: string; // Metric to analyze
|
||||
group_by?: string; // Grouping field
|
||||
interval?: 'hour' | 'day' | 'week' | 'month'; // Time interval
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter Query Parameters (generic)
|
||||
*/
|
||||
export interface FilterQueryParams {
|
||||
filter?: string; // Filter value
|
||||
filters?: string; // Multiple filters (comma-separated)
|
||||
exclude?: string; // Exclude filter
|
||||
include?: string; // Include filter
|
||||
}
|
||||
|
||||
/**
|
||||
* Date Range Query Parameters
|
||||
*/
|
||||
export interface DateRangeQueryParams {
|
||||
start_date?: string; // Start date (ISO8601)
|
||||
end_date?: string; // End date (ISO8601)
|
||||
date_from?: string; // Alternative start date
|
||||
date_to?: string; // Alternative end date
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset Password Query Parameters
|
||||
* Route: /reset-password?token=...
|
||||
*/
|
||||
export interface ResetPasswordQueryParams {
|
||||
token?: string; // Reset token
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify Email Query Parameters
|
||||
* Route: /verify-email?token=...
|
||||
*/
|
||||
export interface VerifyEmailQueryParams {
|
||||
token?: string; // Verification token
|
||||
}
|
||||
|
||||
/**
|
||||
* OAuth Callback Query Parameters
|
||||
* Route: /auth/callback?code=...&state=...
|
||||
*/
|
||||
export interface OAuthCallbackQueryParams {
|
||||
code?: string; // OAuth authorization code
|
||||
state?: string; // OAuth state parameter
|
||||
error?: string; // OAuth error code
|
||||
error_description?: string; // OAuth error description
|
||||
}
|
||||
|
||||
/**
|
||||
* Share Query Parameters
|
||||
* Route: /share?token=...&type=...
|
||||
*/
|
||||
export interface ShareQueryParams {
|
||||
token?: string; // Share token
|
||||
type?: 'track' | 'playlist' | 'user';
|
||||
id?: string; // Resource ID
|
||||
}
|
||||
|
||||
/**
|
||||
* Embed Query Parameters
|
||||
* Route: /embed?track_id=...&autoplay=true
|
||||
*/
|
||||
export interface EmbedQueryParams {
|
||||
track_id?: string; // Track ID
|
||||
playlist_id?: string; // Playlist ID
|
||||
autoplay?: 'true' | 'false'; // Autoplay option
|
||||
controls?: 'true' | 'false'; // Show controls
|
||||
theme?: 'light' | 'dark'; // Theme
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin Query Parameters
|
||||
*/
|
||||
export interface AdminQueryParams {
|
||||
section?: string; // Admin section
|
||||
action?: string; // Admin action
|
||||
user_id?: string; // User ID filter
|
||||
role_id?: string; // Role ID filter
|
||||
page?: string;
|
||||
limit?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Settings Query Parameters
|
||||
*/
|
||||
export interface SettingsQueryParams {
|
||||
tab?: 'profile' | 'security' | 'notifications' | 'privacy' | 'sessions' | 'api';
|
||||
section?: string; // Settings section
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to parse query params from URLSearchParams
|
||||
*/
|
||||
export function parseQueryParams<T extends Record<string, string | undefined>>(
|
||||
searchParams: URLSearchParams,
|
||||
): Partial<T> {
|
||||
const params: Partial<T> = {};
|
||||
|
||||
for (const [key, value] of searchParams.entries()) {
|
||||
params[key as keyof T] = value as T[keyof T];
|
||||
}
|
||||
|
||||
return params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to build query string from params
|
||||
*/
|
||||
export function buildQueryString<T extends Record<string, string | number | boolean | undefined>>(
|
||||
params: T,
|
||||
): string {
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== null && value !== undefined && value !== '') {
|
||||
searchParams.set(key, String(value));
|
||||
}
|
||||
}
|
||||
|
||||
return searchParams.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to convert string query params to typed values
|
||||
*/
|
||||
export function convertQueryParams<T extends Record<string, string | undefined>>(
|
||||
params: Partial<T>,
|
||||
converters: {
|
||||
[K in keyof T]?: (value: string) => T[K];
|
||||
},
|
||||
): Partial<T> {
|
||||
const converted: Partial<T> = {};
|
||||
|
||||
for (const [key, value] of Object.entries(params)) {
|
||||
if (value !== undefined) {
|
||||
const converter = converters[key as keyof T];
|
||||
if (converter) {
|
||||
converted[key as keyof T] = converter(value);
|
||||
} else {
|
||||
converted[key as keyof T] = value as T[keyof T];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return converted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to parse pagination params
|
||||
*/
|
||||
export function parsePaginationParams(
|
||||
params: PaginationQueryParams,
|
||||
): { page: number; limit: number } {
|
||||
return {
|
||||
page: params.page ? parseInt(params.page, 10) : 1,
|
||||
limit: params.limit ? parseInt(params.limit, 10) : 20,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to parse boolean query params
|
||||
*/
|
||||
export function parseBooleanParam(value: string | undefined): boolean | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
return value === 'true' || value === '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to parse number query params
|
||||
*/
|
||||
export function parseNumberParam(value: string | undefined): number | undefined {
|
||||
if (value === undefined) return undefined;
|
||||
const parsed = parseInt(value, 10);
|
||||
return isNaN(parsed) ? undefined : parsed;
|
||||
}
|
||||
|
||||
Loading…
Reference in a new issue