website refactor

This commit is contained in:
2026-01-14 10:51:05 +01:00
parent 4522d41aef
commit 0d89ad027e
291 changed files with 6887 additions and 3685 deletions

View File

@@ -0,0 +1,63 @@
/**
* MediaAdapter
*
* Handles HTTP operations for media assets.
* This is where external API calls belong.
*/
import { Result } from '@/lib/contracts/Result';
import { DomainError } from '@/lib/contracts/services/Service';
import { getWebsiteApiBaseUrl } from '@/lib/config/apiBaseUrl';
import { MediaBinaryDTO } from '@/lib/types/MediaBinaryDTO';
/**
* MediaAdapter
*
* Handles binary media fetching from the API.
* All HTTP calls are isolated here.
*/
export class MediaAdapter {
private baseUrl: string;
constructor() {
this.baseUrl = getWebsiteApiBaseUrl();
}
/**
* Fetch binary media from API
*
* @param mediaPath - API path to media resource
* @returns Result with MediaBinaryDTO on success, DomainError on failure
*/
async fetchMedia(mediaPath: string): Promise<Result<MediaBinaryDTO, DomainError>> {
try {
const response = await fetch(`${this.baseUrl}${mediaPath}`, {
method: 'GET',
});
if (!response.ok) {
if (response.status === 404) {
return Result.err({
type: 'notFound',
message: `Media not found: ${mediaPath}`
});
}
return Result.err({
type: 'serverError',
message: `HTTP ${response.status}: ${response.statusText}`
});
}
const buffer = await response.arrayBuffer();
const contentType = response.headers.get('content-type') || 'image/svg+xml';
return Result.ok({ buffer, contentType });
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return Result.err({
type: 'networkError',
message: `Failed to fetch media: ${errorMessage}`
});
}
}
}

View File

@@ -1,102 +0,0 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { proxyMediaRequest, getMediaContentType, getMediaCacheControl } from './MediaProxyAdapter';
// Mock fetch globally
const mockFetch = vi.fn();
global.fetch = mockFetch;
describe('MediaProxyAdapter', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('proxyMediaRequest', () => {
it('should successfully proxy media and return ArrayBuffer', async () => {
const mockBuffer = new ArrayBuffer(8);
const mockResponse = {
ok: true,
arrayBuffer: vi.fn().mockResolvedValue(mockBuffer),
};
mockFetch.mockResolvedValue(mockResponse);
const result = await proxyMediaRequest('/media/avatar/123');
expect(result.isOk()).toBe(true);
expect(result.unwrap()).toBe(mockBuffer);
expect(mockFetch).toHaveBeenCalledWith(
'http://localhost:3000/media/avatar/123',
{ method: 'GET' }
);
});
it('should handle 404 errors', async () => {
const mockResponse = {
ok: false,
status: 404,
statusText: 'Not Found',
};
mockFetch.mockResolvedValue(mockResponse);
const result = await proxyMediaRequest('/media/avatar/999');
expect(result.isErr()).toBe(true);
const error = result.getError();
expect(error.type).toBe('notFound');
expect(error.message).toContain('Media not found');
});
it('should handle other HTTP errors', async () => {
const mockResponse = {
ok: false,
status: 500,
statusText: 'Internal Server Error',
};
mockFetch.mockResolvedValue(mockResponse);
const result = await proxyMediaRequest('/media/avatar/123');
expect(result.isErr()).toBe(true);
const error = result.getError();
expect(error.type).toBe('serverError');
expect(error.message).toContain('HTTP 500');
});
it('should handle network errors', async () => {
mockFetch.mockRejectedValue(new Error('Network error'));
const result = await proxyMediaRequest('/media/avatar/123');
expect(result.isErr()).toBe(true);
const error = result.getError();
expect(error.type).toBe('networkError');
expect(error.message).toContain('Failed to fetch media');
});
it('should use custom API base URL from environment', () => {
process.env.API_BASE_URL = 'https://api.example.com';
// Just verify the function exists and can be called
expect(typeof proxyMediaRequest).toBe('function');
// Reset
delete process.env.API_BASE_URL;
});
});
describe('getMediaContentType', () => {
it('should return image/png for all media paths', () => {
expect(getMediaContentType('/media/avatar/123')).toBe('image/png');
expect(getMediaContentType('/media/teams/456/logo')).toBe('image/png');
expect(getMediaContentType('/media/leagues/789/cover')).toBe('image/png');
});
});
describe('getMediaCacheControl', () => {
it('should return public cache control with max-age', () => {
expect(getMediaCacheControl()).toBe('public, max-age=3600');
});
});
});

View File

@@ -1,67 +0,0 @@
/**
* MediaProxyAdapter
*
* Handles direct HTTP proxy operations for media assets.
* This is a special case where direct fetch is needed for binary responses.
*/
import { Result } from '@/lib/contracts/Result';
export type MediaProxyError =
| { type: 'notFound'; message: string }
| { type: 'serverError'; message: string }
| { type: 'networkError'; message: string };
/**
* Proxy media request to backend API
*
* @param mediaPath - The API path to fetch media from (e.g., "/media/avatar/123")
* @returns Result with ArrayBuffer on success, or error on failure
*/
export async function proxyMediaRequest(
mediaPath: string
): Promise<Result<ArrayBuffer, MediaProxyError>> {
try {
const baseUrl = process.env.API_BASE_URL || 'http://localhost:3000';
const response = await fetch(`${baseUrl}${mediaPath}`, {
method: 'GET',
});
if (!response.ok) {
if (response.status === 404) {
return Result.err({
type: 'notFound',
message: `Media not found: ${mediaPath}`
});
}
return Result.err({
type: 'serverError',
message: `HTTP ${response.status}: ${response.statusText}`
});
}
const buffer = await response.arrayBuffer();
return Result.ok(buffer);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return Result.err({
type: 'networkError',
message: `Failed to fetch media: ${errorMessage}`
});
}
}
/**
* Get content type for media path
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export function getMediaContentType(mediaPath: string): string {
return 'image/png';
}
/**
* Get cache control header value
*/
export function getMediaCacheControl(): string {
return 'public, max-age=3600';
}