148 lines
4.2 KiB
TypeScript
148 lines
4.2 KiB
TypeScript
/**
|
|
* Port: MediaResolverPort
|
|
*
|
|
* Interface for resolving MediaReference objects to actual URLs.
|
|
* Part of the clean architecture ports layer.
|
|
*
|
|
* Implementations:
|
|
* - InMemoryMediaResolverAdapter (for tests/stubs)
|
|
* - HttpMediaResolverAdapter (for production HTTP resolution)
|
|
* - FileSystemMediaResolverAdapter (for local file resolution)
|
|
*/
|
|
|
|
import { MediaReference } from '@core/domain/media/MediaReference';
|
|
|
|
/**
|
|
* MediaResolverPort interface
|
|
*
|
|
* Resolves a MediaReference to a concrete path string or null if no media exists.
|
|
* Returns path-only URLs (e.g., /media/teams/123/logo) without baseUrl.
|
|
*
|
|
* @param ref - The media reference to resolve
|
|
* @returns Promise resolving to a path string or null
|
|
*
|
|
* @example
|
|
* ```typescript
|
|
* const resolver: MediaResolverPort = new MediaResolverAdapter();
|
|
* const ref = MediaReference.createSystemDefault('avatar');
|
|
* const path = await resolver.resolve(ref);
|
|
* // Returns: '/media/default/male-default-avatar.png'
|
|
* ```
|
|
*/
|
|
export interface MediaResolverPort {
|
|
/**
|
|
* Resolve a media reference to a path-only URL
|
|
*
|
|
* @param ref - The media reference to resolve
|
|
* @returns Promise resolving to path string or null (for 'none' type or resolution failures)
|
|
*
|
|
* @throws Should not throw for valid inputs - returns null instead
|
|
* @throws May throw for invalid inputs (null ref)
|
|
*/
|
|
resolve(ref: MediaReference): Promise<string | null>;
|
|
}
|
|
|
|
/**
|
|
* Type guard to check if an object implements MediaResolverPort
|
|
*/
|
|
export function isMediaResolverPort(obj: unknown): obj is MediaResolverPort {
|
|
const typedObj = obj as MediaResolverPort;
|
|
return (
|
|
obj !== null &&
|
|
typeof obj === 'object' &&
|
|
typeof typedObj.resolve === 'function'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Default resolution strategies for different reference types
|
|
* Returns path-only URLs
|
|
*/
|
|
export const ResolutionStrategies = {
|
|
/**
|
|
* Resolve system-default references
|
|
* Format: /media/default/{variant}
|
|
*/
|
|
systemDefault: (ref: MediaReference): string | null => {
|
|
if (ref.type !== 'system-default') return null;
|
|
|
|
let filename: string;
|
|
if (ref.variant === 'avatar' && ref.avatarVariant) {
|
|
filename = `${ref.avatarVariant}-default-avatar.png`;
|
|
} else if (ref.variant === 'avatar') {
|
|
filename = `neutral-default-avatar.png`;
|
|
} else {
|
|
filename = `${ref.variant}.png`;
|
|
}
|
|
|
|
return `/media/default/${filename}`;
|
|
},
|
|
|
|
/**
|
|
* Resolve generated references
|
|
* Format: /media/teams/{id}/logo, /media/leagues/{id}/logo, /media/avatar/{id}
|
|
*/
|
|
generated: (ref: MediaReference): string | null => {
|
|
if (ref.type !== 'generated') return null;
|
|
|
|
if (!ref.generationRequestId) {
|
|
return null;
|
|
}
|
|
|
|
const firstHyphenIndex = ref.generationRequestId.indexOf('-');
|
|
if (firstHyphenIndex === -1) {
|
|
return null;
|
|
}
|
|
|
|
const type = ref.generationRequestId.substring(0, firstHyphenIndex);
|
|
const id = ref.generationRequestId.substring(firstHyphenIndex + 1);
|
|
|
|
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}`;
|
|
}
|
|
|
|
return `/media/generated/${type}/${id}`;
|
|
},
|
|
|
|
/**
|
|
* Resolve uploaded references
|
|
* Format: /media/uploaded/{mediaId}
|
|
*/
|
|
uploaded: (ref: MediaReference): string | null => {
|
|
if (ref.type !== 'uploaded') return null;
|
|
if (!ref.mediaId) return null;
|
|
return `/media/uploaded/${ref.mediaId}`;
|
|
},
|
|
|
|
/**
|
|
* Resolve none references
|
|
* Returns: null
|
|
*/
|
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
none: (_ref: MediaReference): string | null => {
|
|
return null;
|
|
},
|
|
} as const;
|
|
|
|
/**
|
|
* Helper function to resolve using default strategies
|
|
*/
|
|
export function resolveWithDefaults(ref: MediaReference): string | null {
|
|
switch (ref.type) {
|
|
case 'system-default':
|
|
return ResolutionStrategies.systemDefault(ref);
|
|
case 'generated':
|
|
return ResolutionStrategies.generated(ref);
|
|
case 'uploaded':
|
|
return ResolutionStrategies.uploaded(ref);
|
|
case 'none':
|
|
return ResolutionStrategies.none(ref);
|
|
default:
|
|
// Exhaustive check - TypeScript will error if we miss a case
|
|
return null;
|
|
}
|
|
} |