website refactor

This commit is contained in:
2026-01-14 02:02:24 +01:00
parent 8d7c709e0c
commit 4522d41aef
291 changed files with 12763 additions and 9309 deletions

View File

@@ -1,37 +1,33 @@
import { AdminApiClient } from '@/lib/api/admin/AdminApiClient';
import { AdminService } from '@/lib/services/admin/AdminService';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { Mutation } from '@/lib/contracts/mutations/Mutation';
import { Result } from '@/lib/contracts/Result';
import { MutationError, mapToMutationError } from '@/lib/contracts/mutations/MutationError';
/**
* DeleteUserMutation
*
*
* Framework-agnostic mutation for deleting users.
* Called from Server Actions.
*
*
* Input: { userId: string }
* Output: void
*
* Output: Result<void, MutationError>
*
* Pattern: Server Action → Mutation → Service → API Client
*/
export class DeleteUserMutation implements Mutation<{ userId: string }, void> {
private service: AdminService;
constructor() {
// Manual DI for serverless
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
});
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const apiClient = new AdminApiClient(baseUrl, errorReporter, logger);
this.service = new AdminService(apiClient);
export class DeleteUserMutation {
async execute(input: { userId: string }): Promise<Result<void, MutationError>> {
try {
// Manual construction: Service creates its own dependencies
const service = new AdminService();
const result = await service.deleteUser(input.userId);
if (result.isErr()) {
return Result.err(mapToMutationError(result.getError()));
}
return Result.ok(undefined);
} catch (err) {
return Result.err('deleteFailed');
}
}
async execute(input: { userId: string }): Promise<void> {
await this.service.deleteUser(input.userId);
}
}
}

View File

@@ -1,37 +1,33 @@
import { AdminApiClient } from '@/lib/api/admin/AdminApiClient';
import { AdminService } from '@/lib/services/admin/AdminService';
import { EnhancedErrorReporter } from '@/lib/infrastructure/EnhancedErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import { Mutation } from '@/lib/contracts/mutations/Mutation';
import { Result } from '@/lib/contracts/Result';
import { MutationError, mapToMutationError } from '@/lib/contracts/mutations/MutationError';
/**
* UpdateUserStatusMutation
*
*
* Framework-agnostic mutation for updating user status.
* Called from Server Actions.
*
*
* Input: { userId: string; status: string }
* Output: void
*
* Output: Result<void, MutationError>
*
* Pattern: Server Action → Mutation → Service → API Client
*/
export class UpdateUserStatusMutation implements Mutation<{ userId: string; status: string }, void> {
private service: AdminService;
constructor() {
// Manual DI for serverless
const logger = new ConsoleLogger();
const errorReporter = new EnhancedErrorReporter(logger, {
showUserNotifications: true,
logToConsole: true,
reportToExternal: process.env.NODE_ENV === 'production',
});
const baseUrl = process.env.NEXT_PUBLIC_API_BASE_URL || 'http://localhost:3001';
const apiClient = new AdminApiClient(baseUrl, errorReporter, logger);
this.service = new AdminService(apiClient);
export class UpdateUserStatusMutation {
async execute(input: { userId: string; status: string }): Promise<Result<void, MutationError>> {
try {
// Manual construction: Service creates its own dependencies
const service = new AdminService();
const result = await service.updateUserStatus(input.userId, input.status);
if (result.isErr()) {
return Result.err(mapToMutationError(result.getError()));
}
return Result.ok(undefined);
} catch (err) {
return Result.err('updateFailed');
}
}
async execute(input: { userId: string; status: string }): Promise<void> {
await this.service.updateUserStatus(input.userId, input.status);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* Forgot Password Mutation
*
* Framework-agnostic mutation for forgot password operations.
* Called from Server Actions.
*
* Pattern: Server Action → Mutation → Service → API Client
*/
import { Result } from '@/lib/contracts/Result';
import { AuthService } from '@/lib/services/auth/AuthService';
import { ForgotPasswordDTO } from '@/lib/types/generated/ForgotPasswordDTO';
import { ForgotPasswordResult } from '@/lib/mutations/auth/types/ForgotPasswordResult';
export class ForgotPasswordMutation {
async execute(params: ForgotPasswordDTO): Promise<Result<ForgotPasswordResult, string>> {
try {
const authService = new AuthService();
const result = await authService.forgotPassword(params);
return Result.ok(result);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to send reset link';
return Result.err(errorMessage);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* Login Mutation
*
* Framework-agnostic mutation for login operations.
* Called from Server Actions.
*
* Pattern: Server Action → Mutation → Service → API Client
*/
import { Result } from '@/lib/contracts/Result';
import { AuthService } from '@/lib/services/auth/AuthService';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
import { LoginParamsDTO } from '@/lib/types/generated/LoginParamsDTO';
export class LoginMutation {
async execute(params: LoginParamsDTO): Promise<Result<SessionViewModel, string>> {
try {
const authService = new AuthService();
const session = await authService.login(params);
return Result.ok(session);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Login failed';
return Result.err(errorMessage);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* Reset Password Mutation
*
* Framework-agnostic mutation for reset password operations.
* Called from Server Actions.
*
* Pattern: Server Action → Mutation → Service → API Client
*/
import { Result } from '@/lib/contracts/Result';
import { AuthService } from '@/lib/services/auth/AuthService';
import { ResetPasswordDTO } from '@/lib/types/generated/ResetPasswordDTO';
import { ResetPasswordResult } from '@/lib/mutations/auth/types/ResetPasswordResult';
export class ResetPasswordMutation {
async execute(params: ResetPasswordDTO): Promise<Result<ResetPasswordResult, string>> {
try {
const authService = new AuthService();
const result = await authService.resetPassword(params);
return Result.ok(result);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to reset password';
return Result.err(errorMessage);
}
}
}

View File

@@ -0,0 +1,26 @@
/**
* Signup Mutation
*
* Framework-agnostic mutation for signup operations.
* Called from Server Actions.
*
* Pattern: Server Action → Mutation → Service → API Client
*/
import { Result } from '@/lib/contracts/Result';
import { AuthService } from '@/lib/services/auth/AuthService';
import { SessionViewModel } from '@/lib/view-models/SessionViewModel';
import { SignupParamsDTO } from '@/lib/types/generated/SignupParamsDTO';
export class SignupMutation {
async execute(params: SignupParamsDTO): Promise<Result<SessionViewModel, string>> {
try {
const authService = new AuthService();
const session = await authService.signup(params);
return Result.ok(session);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Signup failed';
return Result.err(errorMessage);
}
}
}

View File

@@ -0,0 +1,11 @@
/**
* Forgot Password Mutation Result
*
* Result type for forgot password operations.
* This represents the successful outcome of a forgot password mutation.
*/
export interface ForgotPasswordResult {
message: string;
magicLink?: string;
}

View File

@@ -0,0 +1,10 @@
/**
* Reset Password Mutation Result
*
* Result type for reset password operations.
* This represents the successful outcome of a reset password mutation.
*/
export interface ResetPasswordResult {
message: string;
}

View File

@@ -0,0 +1,32 @@
import { Result } from '@/lib/contracts/Result';
import type { Mutation } from '@/lib/contracts/mutations/Mutation';
import type { DomainError } from '@/lib/contracts/services/Service';
import { DriverProfileUpdateService } from '@/lib/services/drivers/DriverProfileUpdateService';
export interface UpdateDriverProfileCommand {
bio?: string;
country?: string;
}
type UpdateDriverProfileMutationError = 'DRIVER_PROFILE_UPDATE_FAILED';
const mapToMutationError = (_error: DomainError): UpdateDriverProfileMutationError => {
return 'DRIVER_PROFILE_UPDATE_FAILED';
};
export class UpdateDriverProfileMutation
implements Mutation<UpdateDriverProfileCommand, void, UpdateDriverProfileMutationError>
{
async execute(
command: UpdateDriverProfileCommand,
): Promise<Result<void, UpdateDriverProfileMutationError>> {
const service = new DriverProfileUpdateService();
const result = await service.updateProfile({ bio: command.bio, country: command.country });
if (result.isErr()) {
return Result.err(mapToMutationError(result.getError()));
}
return Result.ok(undefined);
}
}

View File

@@ -0,0 +1,36 @@
import { Result } from '@/lib/contracts/Result';
import { LeagueService } from '@/lib/services/leagues/LeagueService';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import type { CreateLeagueInputDTO } from '@/lib/types/generated/CreateLeagueInputDTO';
/**
* CreateLeagueMutation
*
* Framework-agnostic mutation for league creation.
* Can be called from Server Actions or other contexts.
*/
export class CreateLeagueMutation {
private service: LeagueService;
constructor() {
// Manual wiring for serverless
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
this.service = new LeagueService(apiClient);
}
async createLeague(input: CreateLeagueInputDTO): Promise<Result<void, string>> {
try {
await this.service.createLeague(input);
return Result.ok(undefined);
} catch (error) {
console.error('createLeague failed:', error);
return Result.err('Failed to create league');
}
}
}

View File

@@ -0,0 +1,57 @@
import { Result } from '@/lib/contracts/Result';
import { ProtestService } from '@/lib/services/protests/ProtestService';
import { ProtestsApiClient } from '@/lib/api/protests/ProtestsApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import type { ApplyPenaltyCommandDTO } from '@/lib/types/generated/ApplyPenaltyCommandDTO';
import type { RequestProtestDefenseCommandDTO } from '@/lib/types/generated/RequestProtestDefenseCommandDTO';
/**
* ProtestReviewMutation
*
* Framework-agnostic mutation for protest review operations.
* Can be called from Server Actions or other contexts.
*/
export class ProtestReviewMutation {
private service: ProtestService;
constructor() {
// Manual wiring for serverless
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
const apiClient = new ProtestsApiClient(baseUrl, errorReporter, logger);
this.service = new ProtestService(apiClient);
}
async applyPenalty(input: ApplyPenaltyCommandDTO): Promise<Result<void, string>> {
try {
await this.service.applyPenalty(input);
return Result.ok(undefined);
} catch (error) {
console.error('applyPenalty failed:', error);
return Result.err('Failed to apply penalty');
}
}
async requestDefense(input: RequestProtestDefenseCommandDTO): Promise<Result<void, string>> {
try {
await this.service.requestDefense(input);
return Result.ok(undefined);
} catch (error) {
console.error('requestDefense failed:', error);
return Result.err('Failed to request defense');
}
}
async reviewProtest(input: { protestId: string; stewardId: string; decision: string; decisionNotes: string }): Promise<Result<void, string>> {
try {
await this.service.reviewProtest(input as any);
return Result.ok(undefined);
} catch (error) {
console.error('reviewProtest failed:', error);
return Result.err('Failed to review protest');
}
}
}

View File

@@ -0,0 +1,66 @@
import { Result } from '@/lib/contracts/Result';
import { LeagueService } from '@/lib/services/leagues/LeagueService';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import type { MembershipRole } from '@/lib/types/MembershipRole';
/**
* RosterAdminMutation
*
* Framework-agnostic mutation for roster administration operations.
* Can be called from Server Actions or other contexts.
*/
export class RosterAdminMutation {
private service: LeagueService;
constructor() {
// Manual wiring for serverless
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
this.service = new LeagueService(apiClient);
}
async approveJoinRequest(leagueId: string, joinRequestId: string): Promise<Result<void, string>> {
try {
await this.service.approveJoinRequest(leagueId, joinRequestId);
return Result.ok(undefined);
} catch (error) {
console.error('approveJoinRequest failed:', error);
return Result.err('Failed to approve join request');
}
}
async rejectJoinRequest(leagueId: string, joinRequestId: string): Promise<Result<void, string>> {
try {
await this.service.rejectJoinRequest(leagueId, joinRequestId);
return Result.ok(undefined);
} catch (error) {
console.error('rejectJoinRequest failed:', error);
return Result.err('Failed to reject join request');
}
}
async updateMemberRole(leagueId: string, driverId: string, role: MembershipRole): Promise<Result<void, string>> {
try {
await this.service.updateMemberRole(leagueId, driverId, role);
return Result.ok(undefined);
} catch (error) {
console.error('updateMemberRole failed:', error);
return Result.err('Failed to update member role');
}
}
async removeMember(leagueId: string, driverId: string): Promise<Result<void, string>> {
try {
await this.service.removeMember(leagueId, driverId);
return Result.ok(undefined);
} catch (error) {
console.error('removeMember failed:', error);
return Result.err('Failed to remove member');
}
}
}

View File

@@ -0,0 +1,77 @@
import { Result } from '@/lib/contracts/Result';
import { LeagueService } from '@/lib/services/leagues/LeagueService';
import { LeaguesApiClient } from '@/lib/api/leagues/LeaguesApiClient';
import { ConsoleErrorReporter } from '@/lib/infrastructure/logging/ConsoleErrorReporter';
import { ConsoleLogger } from '@/lib/infrastructure/logging/ConsoleLogger';
import type { CreateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/CreateLeagueScheduleRaceInputDTO';
import type { UpdateLeagueScheduleRaceInputDTO } from '@/lib/types/generated/UpdateLeagueScheduleRaceInputDTO';
/**
* ScheduleAdminMutation
*
* Framework-agnostic mutation for schedule administration operations.
* Can be called from Server Actions or other contexts.
*/
export class ScheduleAdminMutation {
private service: LeagueService;
constructor() {
// Manual wiring for serverless
const baseUrl = process.env.NEXT_PUBLIC_API_URL || '';
const errorReporter = new ConsoleErrorReporter();
const logger = new ConsoleLogger();
const apiClient = new LeaguesApiClient(baseUrl, errorReporter, logger);
this.service = new LeagueService(apiClient);
}
async publishSchedule(leagueId: string, seasonId: string): Promise<Result<void, string>> {
try {
await this.service.publishAdminSchedule(leagueId, seasonId);
return Result.ok(undefined);
} catch (error) {
console.error('publishSchedule failed:', error);
return Result.err('Failed to publish schedule');
}
}
async unpublishSchedule(leagueId: string, seasonId: string): Promise<Result<void, string>> {
try {
await this.service.unpublishAdminSchedule(leagueId, seasonId);
return Result.ok(undefined);
} catch (error) {
console.error('unpublishSchedule failed:', error);
return Result.err('Failed to unpublish schedule');
}
}
async createRace(leagueId: string, seasonId: string, input: { track: string; car: string; scheduledAtIso: string }): Promise<Result<void, string>> {
try {
await this.service.createAdminScheduleRace(leagueId, seasonId, input);
return Result.ok(undefined);
} catch (error) {
console.error('createRace failed:', error);
return Result.err('Failed to create race');
}
}
async updateRace(leagueId: string, seasonId: string, raceId: string, input: Partial<{ track: string; car: string; scheduledAtIso: string }>): Promise<Result<void, string>> {
try {
await this.service.updateAdminScheduleRace(leagueId, seasonId, raceId, input);
return Result.ok(undefined);
} catch (error) {
console.error('updateRace failed:', error);
return Result.err('Failed to update race');
}
}
async deleteRace(leagueId: string, seasonId: string, raceId: string): Promise<Result<void, string>> {
try {
await this.service.deleteAdminScheduleRace(leagueId, seasonId, raceId);
return Result.ok(undefined);
} catch (error) {
console.error('deleteRace failed:', error);
return Result.err('Failed to delete race');
}
}
}

View File

@@ -0,0 +1,34 @@
/**
* Complete Onboarding Mutation
*
* Framework-agnostic mutation for completing onboarding.
* Called from Server Actions.
*
* Pattern: Server Action → Mutation → Service → API Client
*/
import { Result } from '@/lib/contracts/Result';
import { OnboardingService } from '@/lib/services/onboarding/OnboardingService';
import { CompleteOnboardingInputDTO } from '@/lib/types/generated/CompleteOnboardingInputDTO';
import { CompleteOnboardingViewDataBuilder } from '@/lib/builders/view-data/CompleteOnboardingViewDataBuilder';
import { CompleteOnboardingViewData } from '@/lib/builders/view-data/CompleteOnboardingViewData';
export class CompleteOnboardingMutation {
async execute(params: CompleteOnboardingInputDTO): Promise<Result<CompleteOnboardingViewData, string>> {
try {
const onboardingService = new OnboardingService();
const result = await onboardingService.completeOnboarding(params);
if (result.isErr()) {
const error = result.getError();
return Result.err(error.message || 'Failed to complete onboarding');
}
const output = CompleteOnboardingViewDataBuilder.build(result.unwrap());
return Result.ok(output);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to complete onboarding';
return Result.err(errorMessage);
}
}
}

View File

@@ -0,0 +1,20 @@
import { Mutation } from '@/lib/contracts/mutations/Mutation';
import { Result } from '@/lib/contracts/Result';
import { OnboardingService } from '@/lib/services/onboarding/OnboardingService';
import { RequestAvatarGenerationInputDTO } from '@/lib/types/generated/RequestAvatarGenerationInputDTO';
import { GenerateAvatarsViewDataBuilder } from '@/lib/builders/view-data/GenerateAvatarsViewDataBuilder';
import { GenerateAvatarsViewData } from '@/lib/builders/view-data/GenerateAvatarsViewData';
export class GenerateAvatarsMutation implements Mutation<RequestAvatarGenerationInputDTO, GenerateAvatarsViewData, string> {
async execute(input: RequestAvatarGenerationInputDTO): Promise<Result<GenerateAvatarsViewData, string>> {
const service = new OnboardingService();
const result = await service.generateAvatars(input);
if (result.isErr()) {
return Result.err(result.getError().message);
}
const output = GenerateAvatarsViewDataBuilder.build(result.unwrap());
return Result.ok(output);
}
}

View File

@@ -0,0 +1,30 @@
import { SponsorshipRequestsService } from '@/lib/services/sponsors/SponsorshipRequestsService';
import type { AcceptSponsorshipRequestCommand } from '@/lib/services/sponsors/SponsorshipRequestsService';
import type { Mutation } from '@/lib/contracts/mutations/Mutation';
import { Result } from '@/lib/contracts/Result';
import type { Result as ResultType } from '@/lib/contracts/Result';
export type AcceptSponsorshipRequestMutationError =
| 'ACCEPT_SPONSORSHIP_REQUEST_FAILED';
export class AcceptSponsorshipRequestMutation
implements Mutation<AcceptSponsorshipRequestCommand, void, AcceptSponsorshipRequestMutationError>
{
private readonly service: SponsorshipRequestsService;
constructor() {
this.service = new SponsorshipRequestsService();
}
async execute(
command: AcceptSponsorshipRequestCommand,
): Promise<ResultType<void, AcceptSponsorshipRequestMutationError>> {
const result = await this.service.acceptRequest(command);
if (result.isErr()) {
return Result.err('ACCEPT_SPONSORSHIP_REQUEST_FAILED');
}
return Result.ok(undefined);
}
}

View File

@@ -0,0 +1,30 @@
import { SponsorshipRequestsService } from '@/lib/services/sponsors/SponsorshipRequestsService';
import type { RejectSponsorshipRequestCommand } from '@/lib/services/sponsors/SponsorshipRequestsService';
import type { Mutation } from '@/lib/contracts/mutations/Mutation';
import { Result } from '@/lib/contracts/Result';
import type { Result as ResultType } from '@/lib/contracts/Result';
export type RejectSponsorshipRequestMutationError =
| 'REJECT_SPONSORSHIP_REQUEST_FAILED';
export class RejectSponsorshipRequestMutation
implements Mutation<RejectSponsorshipRequestCommand, void, RejectSponsorshipRequestMutationError>
{
private readonly service: SponsorshipRequestsService;
constructor() {
this.service = new SponsorshipRequestsService();
}
async execute(
command: RejectSponsorshipRequestCommand,
): Promise<ResultType<void, RejectSponsorshipRequestMutationError>> {
const result = await this.service.rejectRequest(command);
if (result.isErr()) {
return Result.err('REJECT_SPONSORSHIP_REQUEST_FAILED');
}
return Result.ok(undefined);
}
}