harden media
This commit is contained in:
79
adapters/media/resolvers/DefaultMediaResolverAdapter.ts
Normal file
79
adapters/media/resolvers/DefaultMediaResolverAdapter.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* DefaultMediaResolverAdapter
|
||||
*
|
||||
* Resolves system-default media references to public asset URLs.
|
||||
* Part of the adapters layer, implementing the MediaResolverPort interface.
|
||||
*/
|
||||
|
||||
import { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
|
||||
import { MediaReference } from '@core/domain/media/MediaReference';
|
||||
|
||||
/**
|
||||
* Configuration for the DefaultMediaResolverAdapter
|
||||
*/
|
||||
export interface DefaultMediaResolverConfig {
|
||||
/**
|
||||
* Base path for default assets (defaults to '/media/default')
|
||||
*/
|
||||
basePath?: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* DefaultMediaResolverAdapter
|
||||
*
|
||||
* Resolves system-default media references to public asset URLs.
|
||||
*
|
||||
* URL format: /media/default/{variant}
|
||||
* Examples:
|
||||
* - /media/default/male-default-avatar
|
||||
* - /media/default/female-default-avatar
|
||||
* - /media/default/neutral-default-avatar
|
||||
* - /media/default/team-logo.png
|
||||
* - /media/default/league-logo.png
|
||||
*/
|
||||
export class DefaultMediaResolverAdapter implements MediaResolverPort {
|
||||
private readonly basePath: string;
|
||||
|
||||
constructor(config: DefaultMediaResolverConfig = {}) {
|
||||
this.basePath = config.basePath || '/media/default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a system-default media reference to a path-only URL
|
||||
* Returns paths like /media/default/{variant} (no baseUrl)
|
||||
*/
|
||||
async resolve(ref: MediaReference): Promise<string | null> {
|
||||
// Only handle system-default references
|
||||
if (ref.type !== 'system-default') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Determine the filename based on variant and avatarVariant
|
||||
let filename: string;
|
||||
|
||||
if (ref.variant === 'avatar' && ref.avatarVariant) {
|
||||
// Driver avatars must use website public assets:
|
||||
// apps/website/public/images/avatars/{male|female|neutral}-default-avatar.(jpg|jpeg)
|
||||
// We intentionally keep the URL extension-less; MediaController maps it to the real file.
|
||||
filename = `${ref.avatarVariant}-default-avatar`;
|
||||
} else if (ref.variant === 'avatar') {
|
||||
// Avatar without specific variant (fallback to neutral)
|
||||
filename = `neutral-default-avatar`;
|
||||
} else {
|
||||
// Other variants (team, league, etc.)
|
||||
filename = `${ref.variant}.png`;
|
||||
}
|
||||
|
||||
// Return path-only URL
|
||||
return `${this.basePath}/${filename}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function for creating DefaultMediaResolverAdapter instances
|
||||
*/
|
||||
export function createDefaultMediaResolver(
|
||||
config: DefaultMediaResolverConfig = {}
|
||||
): DefaultMediaResolverAdapter {
|
||||
return new DefaultMediaResolverAdapter(config);
|
||||
}
|
||||
92
adapters/media/resolvers/GeneratedMediaResolverAdapter.ts
Normal file
92
adapters/media/resolvers/GeneratedMediaResolverAdapter.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
/**
|
||||
* GeneratedMediaResolverAdapter
|
||||
*
|
||||
* Resolves generated media references to image serving URLs.
|
||||
* Part of the adapters layer, implementing the MediaResolverPort interface.
|
||||
*/
|
||||
|
||||
import { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
|
||||
import { MediaReference } from '@core/domain/media/MediaReference';
|
||||
|
||||
/**
|
||||
* Configuration for the GeneratedMediaResolverAdapter
|
||||
*/
|
||||
export interface GeneratedMediaResolverConfig {
|
||||
/**
|
||||
* Base path for generated assets (defaults to '/media/generated')
|
||||
* @deprecated No longer used - returns path-only URLs
|
||||
*/
|
||||
basePath?: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* GeneratedMediaResolverAdapter
|
||||
*
|
||||
* Resolves generated media references to image serving URLs.
|
||||
*
|
||||
* URL format: /media/generated/{type}/{id}
|
||||
* Examples:
|
||||
* - /media/teams/{id}/logo
|
||||
* - /media/leagues/{id}/logo
|
||||
* - /media/avatar/{id}
|
||||
*
|
||||
* The type and id are extracted from the generationRequestId.
|
||||
* Format: "{type}-{id}" (e.g., "team-123", "league-456")
|
||||
*/
|
||||
export class GeneratedMediaResolverAdapter implements MediaResolverPort {
|
||||
constructor(_config: GeneratedMediaResolverConfig = {}) {
|
||||
// basePath is not used since we return path-only URLs
|
||||
// config.basePath is ignored for backward compatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a generated media reference to a path-only URL
|
||||
* Returns paths like /media/teams/{id}/logo (no baseUrl)
|
||||
*/
|
||||
async resolve(ref: MediaReference): Promise<string | null> {
|
||||
// Only handle generated references
|
||||
if (ref.type !== 'generated') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Parse the generationRequestId to extract type and id
|
||||
// Format: "{type}-{id}" or "{type}-{subtype}-{id}"
|
||||
const requestId = ref.generationRequestId;
|
||||
|
||||
if (!requestId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Find the first hyphen to split type and id
|
||||
// Format: "{type}-{id}" where id can contain hyphens
|
||||
const firstHyphenIndex = requestId.indexOf('-');
|
||||
if (firstHyphenIndex === -1) {
|
||||
// Invalid format
|
||||
return null;
|
||||
}
|
||||
|
||||
const type = requestId.substring(0, firstHyphenIndex);
|
||||
const id = requestId.substring(firstHyphenIndex + 1);
|
||||
|
||||
// Return path-only URLs matching the API routes
|
||||
if (type === 'team') {
|
||||
return `/media/teams/${id}/logo`;
|
||||
} else if (type === 'league') {
|
||||
return `/media/leagues/${id}/logo`;
|
||||
} else if (type === 'driver') {
|
||||
return `/media/avatar/${id}`;
|
||||
}
|
||||
|
||||
// Fallback for other types
|
||||
return `/media/generated/${type}/${id}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function for creating GeneratedMediaResolverAdapter instances
|
||||
*/
|
||||
export function createGeneratedMediaResolver(
|
||||
config: GeneratedMediaResolverConfig = {}
|
||||
): GeneratedMediaResolverAdapter {
|
||||
return new GeneratedMediaResolverAdapter(config);
|
||||
}
|
||||
71
adapters/media/resolvers/UploadedMediaResolverAdapter.ts
Normal file
71
adapters/media/resolvers/UploadedMediaResolverAdapter.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* UploadedMediaResolverAdapter
|
||||
*
|
||||
* Resolves uploaded media references to image serving URLs.
|
||||
* Part of the adapters layer, implementing the MediaResolverPort interface.
|
||||
*/
|
||||
|
||||
import { MediaResolverPort } from '@core/ports/media/MediaResolverPort';
|
||||
import { MediaReference } from '@core/domain/media/MediaReference';
|
||||
|
||||
/**
|
||||
* Configuration for the UploadedMediaResolverAdapter
|
||||
*/
|
||||
export interface UploadedMediaResolverConfig {
|
||||
/**
|
||||
* Base path for uploaded assets (defaults to '/media/uploaded')
|
||||
*/
|
||||
basePath?: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* UploadedMediaResolverAdapter
|
||||
*
|
||||
* Resolves uploaded media references to image serving URLs.
|
||||
*
|
||||
* URL format: /media/uploaded/{mediaId}
|
||||
* Examples:
|
||||
* - /media/uploaded/media-123
|
||||
* - /media/uploaded/media-456
|
||||
*
|
||||
* Note: This is a stub implementation. In production, this would:
|
||||
* - Check if the media exists in storage
|
||||
* - Handle different file types (images, videos, documents)
|
||||
* - Handle access control and permissions
|
||||
* - Generate signed URLs for private media
|
||||
*/
|
||||
export class UploadedMediaResolverAdapter implements MediaResolverPort {
|
||||
private readonly basePath: string;
|
||||
|
||||
constructor(config: UploadedMediaResolverConfig = {}) {
|
||||
this.basePath = config.basePath || '/media/uploaded';
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an uploaded media reference to a path-only URL
|
||||
* Returns paths like /media/uploaded/{mediaId} (no baseUrl)
|
||||
*/
|
||||
async resolve(ref: MediaReference): Promise<string | null> {
|
||||
// Only handle uploaded references
|
||||
if (ref.type !== 'uploaded') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate mediaId exists
|
||||
if (!ref.mediaId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Return path-only URL
|
||||
return `${this.basePath}/${ref.mediaId}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory function for creating UploadedMediaResolverAdapter instances
|
||||
*/
|
||||
export function createUploadedMediaResolver(
|
||||
config: UploadedMediaResolverConfig = {}
|
||||
): UploadedMediaResolverAdapter {
|
||||
return new UploadedMediaResolverAdapter(config);
|
||||
}
|
||||
Reference in New Issue
Block a user