diff --git a/.dockerignore b/.dockerignore index ef13bb283..72b45ca6d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -55,6 +55,11 @@ html-dumps html-dumps-optimized backups .husky +docs +plans +tests +testing +resources # Development files .prettierrc diff --git a/apps/api/Dockerfile.dev b/apps/api/Dockerfile.dev index 9dc2f85a9..b6e7036b9 100644 --- a/apps/api/Dockerfile.dev +++ b/apps/api/Dockerfile.dev @@ -5,14 +5,16 @@ WORKDIR /app # Install bash for better shell capabilities RUN apk add --no-cache bash -# Copy root package.json and install dependencies +# Copy package manifests and install dependencies (incl. workspaces) COPY package.json package-lock.json ./ -RUN npm ci +COPY apps/api/package.json apps/api/package.json +RUN npm ci --workspaces --include-workspace-root RUN find ./node_modules -name "ts-node-dev" -print || true # Debugging line -# Copy apps/api and packages for development +# Copy sources for development (monorepo) COPY apps/api apps/api/ -COPY packages core/ +COPY core core/ +COPY adapters adapters/ COPY apps/api/tsconfig.json apps/api/ COPY tsconfig.base.json ./ @@ -21,4 +23,4 @@ EXPOSE 9229 # Command to run the NestJS application in development with hot-reloading # Run from the correct workspace context -CMD ["npm", "run", "start:dev", "--workspace=api"] +CMD ["npm", "run", "start:dev", "--workspace=@gridpilot/api"] diff --git a/apps/api/Dockerfile.prod b/apps/api/Dockerfile.prod index c04b04ec0..7187ae603 100644 --- a/apps/api/Dockerfile.prod +++ b/apps/api/Dockerfile.prod @@ -2,20 +2,21 @@ FROM node:20-alpine AS builder WORKDIR /app -# Copy root package.json and install dependencies (for monorepo) +# Copy package manifests and install dependencies (incl. workspaces) COPY package.json package-lock.json ./ -RUN npm ci +COPY apps/api/package.json apps/api/package.json +RUN npm ci --workspaces --include-workspace-root -# Copy apps/api and packages for building +# Copy sources for building (monorepo) COPY apps/api apps/api/ -COPY packages core/ +COPY core core/ +COPY adapters adapters/ COPY apps/api/tsconfig.json apps/api/ COPY tsconfig.base.json ./ -# Build the NestJS application (ensuring correct workspace context) -# Run from the root workspace context -# RUN node ./node_modules/@nestjs/cli/bin/nest.js build --workspace=api # Not needed, npm run handles it -RUN npm run build --workspace=api +# Build shared libs + API (so runtime imports resolve) +RUN npx tsc -b core/tsconfig.json adapters/tsconfig.json +RUN npm run build --workspace=@gridpilot/api # Production stage: slim image with only production dependencies @@ -34,8 +35,9 @@ RUN npm ci --omit=dev # Copy built application from builder stage COPY --from=builder /app/apps/api/dist ./apps/api/dist -# Copy packages (needed for runtime dependencies) -COPY --from=builder /app/packages ./packages +# Provide runtime module resolution for @core/* and @adapters/* +COPY --from=builder /app/dist/core /app/node_modules/@core +COPY --from=builder /app/dist/adapters /app/node_modules/@adapters EXPOSE 3000 diff --git a/apps/api/package.json b/apps/api/package.json index dd5de4d96..242bfd192 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -5,7 +5,7 @@ "main": "dist/index.js", "scripts": { "build": "tsc --build --verbose", - "start:dev": "ts-node-dev --respawn --inspect=0.0.0.0:9229 src/main.ts", + "start:dev": "npx ts-node-dev --respawn --inspect=0.0.0.0:9229 -r tsconfig-paths/register src/main.ts", "start:prod": "node dist/main", "test": "vitest run --config vitest.api.config.ts --root ../..", "test:coverage": "vitest run --config vitest.api.config.ts --root ../.. --coverage", @@ -17,7 +17,8 @@ "license": "ISC", "devDependencies": { "@nestjs/testing": "^10.4.20", - "ts-node-dev": "^2.0.0" + "ts-node-dev": "^2.0.0", + "tsconfig-paths": "^3.15.0" }, "dependencies": { "@nestjs/common": "^10.4.20", diff --git a/apps/api/src/domain/database/DatabaseModule.ts b/apps/api/src/domain/database/DatabaseModule.ts index 2abb6efaa..66d12bd3a 100644 --- a/apps/api/src/domain/database/DatabaseModule.ts +++ b/apps/api/src/domain/database/DatabaseModule.ts @@ -5,13 +5,17 @@ import { TypeOrmModule } from '@nestjs/typeorm'; imports: [ TypeOrmModule.forRoot({ type: 'postgres', - host: process.env.DATABASE_HOST || 'localhost', - port: parseInt(process.env.DATABASE_PORT || '5432', 10), - username: process.env.DATABASE_USER || 'user', - password: process.env.DATABASE_PASSWORD || 'password', - database: process.env.DATABASE_NAME || 'gridpilot', + ...(process.env.DATABASE_URL + ? { url: process.env.DATABASE_URL } + : { + host: process.env.DATABASE_HOST || 'localhost', + port: parseInt(process.env.DATABASE_PORT || '5432', 10), + username: process.env.DATABASE_USER || 'user', + password: process.env.DATABASE_PASSWORD || 'password', + database: process.env.DATABASE_NAME || 'gridpilot', + }), // entities: [AnalyticsSnapshotOrmEntity, EngagementOrmEntity], - synchronize: true, // Use carefully in production + synchronize: process.env.NODE_ENV !== 'production', }), ], exports: [TypeOrmModule], diff --git a/apps/api/src/domain/hello/HelloController.ts b/apps/api/src/domain/hello/HelloController.ts index 5118f59d6..ea3910ed3 100644 --- a/apps/api/src/domain/hello/HelloController.ts +++ b/apps/api/src/domain/hello/HelloController.ts @@ -7,6 +7,11 @@ import { HelloService } from './HelloService'; export class HelloController { constructor(private readonly helloService: HelloService) {} + @Get('health') + health() { + return { status: 'ok' }; + } + @Get() getHello() { return this.helloService.getHello(); diff --git a/apps/api/src/domain/hello/HelloModule.ts b/apps/api/src/domain/hello/HelloModule.ts index 1ccc3c167..34069a126 100644 --- a/apps/api/src/domain/hello/HelloModule.ts +++ b/apps/api/src/domain/hello/HelloModule.ts @@ -1,9 +1,10 @@ -import { Module } from "@nestjs/common"; -import { HelloController } from "./HelloController"; -import { HelloService } from "./HelloService"; +import { Module } from '@nestjs/common'; +import { HelloController } from './HelloController'; +import { HelloService } from './HelloService'; @Module({ controllers: [HelloController], + providers: [HelloService], exports: [HelloService], }) export class HelloModule {} \ No newline at end of file diff --git a/apps/api/src/domain/league/LeagueProviders.ts b/apps/api/src/domain/league/LeagueProviders.ts index 4f11ce75b..dd899d056 100644 --- a/apps/api/src/domain/league/LeagueProviders.ts +++ b/apps/api/src/domain/league/LeagueProviders.ts @@ -61,11 +61,28 @@ import { WithdrawFromLeagueWalletUseCase } from '@core/racing/application/use-ca // Import presenters import { AllLeaguesWithCapacityPresenter } from './presenters/AllLeaguesWithCapacityPresenter'; +import { ApproveLeagueJoinRequestPresenter } from './presenters/ApproveLeagueJoinRequestPresenter'; +import { CreateLeaguePresenter } from './presenters/CreateLeaguePresenter'; +import { GetLeagueAdminPermissionsPresenter } from './presenters/GetLeagueAdminPermissionsPresenter'; +import { GetLeagueMembershipsPresenter } from './presenters/GetLeagueMembershipsPresenter'; +import { GetLeagueOwnerSummaryPresenter } from './presenters/GetLeagueOwnerSummaryPresenter'; import { GetLeagueProtestsPresenter } from './presenters/GetLeagueProtestsPresenter'; +import { GetLeagueSeasonsPresenter } from './presenters/GetLeagueSeasonsPresenter'; +import { GetLeagueWalletPresenter } from './presenters/GetLeagueWalletPresenter'; import { GetSeasonSponsorshipsPresenter } from './presenters/GetSeasonSponsorshipsPresenter'; +import { JoinLeaguePresenter } from './presenters/JoinLeaguePresenter'; +import { LeagueConfigPresenter } from './presenters/LeagueConfigPresenter'; +import { LeagueJoinRequestsPresenter } from './presenters/LeagueJoinRequestsPresenter'; +import { LeagueSchedulePresenter, LeagueRacesPresenter } from './presenters/LeagueSchedulePresenter'; +import { LeagueScoringConfigPresenter } from './presenters/LeagueScoringConfigPresenter'; import { LeagueScoringPresetsPresenter } from './presenters/LeagueScoringPresetsPresenter'; import { LeagueStandingsPresenter } from './presenters/LeagueStandingsPresenter'; -import { GetLeagueWalletPresenter } from './presenters/GetLeagueWalletPresenter'; +import { LeagueStatsPresenter } from './presenters/LeagueStatsPresenter'; +import { RejectLeagueJoinRequestPresenter } from './presenters/RejectLeagueJoinRequestPresenter'; +import { RemoveLeagueMemberPresenter } from './presenters/RemoveLeagueMemberPresenter'; +import { TotalLeaguesPresenter } from './presenters/TotalLeaguesPresenter'; +import { TransferLeagueOwnershipPresenter } from './presenters/TransferLeagueOwnershipPresenter'; +import { UpdateLeagueMemberRolePresenter } from './presenters/UpdateLeagueMemberRolePresenter'; import { WithdrawFromLeagueWalletPresenter } from './presenters/WithdrawFromLeagueWalletPresenter'; export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository'; @@ -204,11 +221,29 @@ export const LeagueProviders: Provider[] = [ }, // Presenters AllLeaguesWithCapacityPresenter, - LeagueStandingsPresenter, + ApproveLeagueJoinRequestPresenter, + CreateLeaguePresenter, + GetLeagueAdminPermissionsPresenter, + GetLeagueMembershipsPresenter, + GetLeagueOwnerSummaryPresenter, GetLeagueProtestsPresenter, - GetSeasonSponsorshipsPresenter, - LeagueScoringPresetsPresenter, + GetLeagueSeasonsPresenter, GetLeagueWalletPresenter, + GetSeasonSponsorshipsPresenter, + JoinLeaguePresenter, + LeagueConfigPresenter, + LeagueJoinRequestsPresenter, + LeagueRacesPresenter, + LeagueSchedulePresenter, + LeagueScoringConfigPresenter, + LeagueScoringPresetsPresenter, + LeagueStandingsPresenter, + LeagueStatsPresenter, + RejectLeagueJoinRequestPresenter, + RemoveLeagueMemberPresenter, + TotalLeaguesPresenter, + TransferLeagueOwnershipPresenter, + UpdateLeagueMemberRolePresenter, WithdrawFromLeagueWalletPresenter, // Output ports { @@ -231,6 +266,70 @@ export const LeagueProviders: Provider[] = [ provide: LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN, useExisting: LeagueScoringPresetsPresenter, }, + { + provide: APPROVE_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN, + useExisting: ApproveLeagueJoinRequestPresenter, + }, + { + provide: CREATE_LEAGUE_OUTPUT_PORT_TOKEN, + useExisting: CreateLeaguePresenter, + }, + { + provide: GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN, + useExisting: GetLeagueAdminPermissionsPresenter, + }, + { + provide: GET_LEAGUE_MEMBERSHIPS_OUTPUT_PORT_TOKEN, + useExisting: GetLeagueMembershipsPresenter, + }, + { + provide: GET_LEAGUE_OWNER_SUMMARY_OUTPUT_PORT_TOKEN, + useExisting: GetLeagueOwnerSummaryPresenter, + }, + { + provide: GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN, + useExisting: GetLeagueSeasonsPresenter, + }, + { + provide: JOIN_LEAGUE_OUTPUT_PORT_TOKEN, + useExisting: JoinLeaguePresenter, + }, + { + provide: GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN, + useExisting: LeagueSchedulePresenter, + }, + { + provide: GET_LEAGUE_STATS_OUTPUT_PORT_TOKEN, + useExisting: LeagueStatsPresenter, + }, + { + provide: REJECT_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN, + useExisting: RejectLeagueJoinRequestPresenter, + }, + { + provide: REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN, + useExisting: RemoveLeagueMemberPresenter, + }, + { + provide: TOTAL_LEAGUES_OUTPUT_PORT_TOKEN, + useExisting: TotalLeaguesPresenter, + }, + { + provide: TRANSFER_LEAGUE_OWNERSHIP_OUTPUT_PORT_TOKEN, + useExisting: TransferLeagueOwnershipPresenter, + }, + { + provide: UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN, + useExisting: UpdateLeagueMemberRolePresenter, + }, + { + provide: GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN, + useExisting: LeagueConfigPresenter, + }, + { + provide: GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN, + useExisting: LeagueScoringConfigPresenter, + }, { provide: GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN, useExisting: GetLeagueWalletPresenter, @@ -248,9 +347,16 @@ export const LeagueProviders: Provider[] = [ }, { provide: GET_LEAGUE_STANDINGS_USE_CASE, - useFactory: (standingRepo: IStandingRepository, driverRepo: IDriverRepository, presenter: LeagueStandingsPresenter) => - new GetLeagueStandingsUseCase(standingRepo, driverRepo, presenter), - inject: [STANDING_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, 'LeagueStandingsPresenter'], + useFactory: ( + standingRepo: IStandingRepository, + driverRepo: IDriverRepository, + output: LeagueStandingsPresenter, + ) => new GetLeagueStandingsUseCase(standingRepo, driverRepo, output), + inject: [ + STANDING_REPOSITORY_TOKEN, + DRIVER_REPOSITORY_TOKEN, + GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN, + ], }, { provide: GET_LEAGUE_STATS_USE_CASE, @@ -303,9 +409,15 @@ export const LeagueProviders: Provider[] = [ protestRepo: IProtestRepository, driverRepo: IDriverRepository, leagueRepo: ILeagueRepository, - presenter: GetLeagueProtestsPresenter, - ) => new GetLeagueProtestsUseCase(raceRepo, protestRepo, driverRepo, leagueRepo, presenter), - inject: [RACE_REPOSITORY_TOKEN, PROTEST_REPOSITORY_TOKEN, DRIVER_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, 'GetLeagueProtestsPresenter'], + output: GetLeagueProtestsPresenter, + ) => new GetLeagueProtestsUseCase(raceRepo, protestRepo, driverRepo, leagueRepo, output), + inject: [ + RACE_REPOSITORY_TOKEN, + PROTEST_REPOSITORY_TOKEN, + DRIVER_REPOSITORY_TOKEN, + LEAGUE_REPOSITORY_TOKEN, + GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN, + ], }, { provide: GET_LEAGUE_SEASONS_USE_CASE, @@ -329,9 +441,14 @@ export const LeagueProviders: Provider[] = [ leagueRepo: ILeagueRepository, walletRepo: ILeagueWalletRepository, transactionRepo: ITransactionRepository, - presenter: GetLeagueWalletPresenter, - ) => new GetLeagueWalletUseCase(leagueRepo, walletRepo, transactionRepo, presenter), - inject: [LEAGUE_REPOSITORY_TOKEN, LEAGUE_WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN, 'GetLeagueWalletPresenter'], + output: GetLeagueWalletPresenter, + ) => new GetLeagueWalletUseCase(leagueRepo, walletRepo, transactionRepo, output), + inject: [ + LEAGUE_REPOSITORY_TOKEN, + LEAGUE_WALLET_REPOSITORY_TOKEN, + TRANSACTION_REPOSITORY_TOKEN, + GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN, + ], }, { provide: WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE, @@ -340,9 +457,15 @@ export const LeagueProviders: Provider[] = [ walletRepo: ILeagueWalletRepository, transactionRepo: ITransactionRepository, logger: Logger, - presenter: WithdrawFromLeagueWalletPresenter, - ) => new WithdrawFromLeagueWalletUseCase(leagueRepo, walletRepo, transactionRepo, logger, presenter), - inject: [LEAGUE_REPOSITORY_TOKEN, LEAGUE_WALLET_REPOSITORY_TOKEN, TRANSACTION_REPOSITORY_TOKEN, LOGGER_TOKEN, 'WithdrawFromLeagueWalletPresenter'], + output: WithdrawFromLeagueWalletPresenter, + ) => new WithdrawFromLeagueWalletUseCase(leagueRepo, walletRepo, transactionRepo, logger, output), + inject: [ + LEAGUE_REPOSITORY_TOKEN, + LEAGUE_WALLET_REPOSITORY_TOKEN, + TRANSACTION_REPOSITORY_TOKEN, + LOGGER_TOKEN, + WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN, + ], }, { provide: GET_SEASON_SPONSORSHIPS_USE_CASE, @@ -352,21 +475,30 @@ export const LeagueProviders: Provider[] = [ leagueRepo: ILeagueRepository, leagueMembershipRepo: ILeagueMembershipRepository, raceRepo: IRaceRepository, - presenter: GetSeasonSponsorshipsPresenter, - ) => new GetSeasonSponsorshipsUseCase(seasonSponsorshipRepo, seasonRepo, leagueRepo, leagueMembershipRepo, raceRepo, presenter), + output: GetSeasonSponsorshipsPresenter, + ) => + new GetSeasonSponsorshipsUseCase( + seasonSponsorshipRepo, + seasonRepo, + leagueRepo, + leagueMembershipRepo, + raceRepo, + output, + ), inject: [ 'ISeasonSponsorshipRepository', SEASON_REPOSITORY_TOKEN, LEAGUE_REPOSITORY_TOKEN, LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN, RACE_REPOSITORY_TOKEN, - 'GetSeasonSponsorshipsPresenter', + GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN, ], }, { // TODO wtf is this here? doesn't look like it adhers to our concepts provide: LIST_LEAGUE_SCORING_PRESETS_USE_CASE, - useFactory: (presenter: LeagueScoringPresetsPresenter) => new ListLeagueScoringPresetsUseCase(listLeagueScoringPresets(), presenter), - inject: ['LeagueScoringPresetsPresenter'], + useFactory: (output: LeagueScoringPresetsPresenter) => + new ListLeagueScoringPresetsUseCase(listLeagueScoringPresets(), output), + inject: [LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN], }, { provide: JOIN_LEAGUE_USE_CASE, diff --git a/apps/api/src/domain/league/LeagueService.ts b/apps/api/src/domain/league/LeagueService.ts index 4cc5bee30..cbbda3e8e 100644 --- a/apps/api/src/domain/league/LeagueService.ts +++ b/apps/api/src/domain/league/LeagueService.ts @@ -94,31 +94,31 @@ import { UpdateLeagueMemberRolePresenter } from './presenters/UpdateLeagueMember import { GetLeagueWalletPresenter } from './presenters/GetLeagueWalletPresenter'; import { WithdrawFromLeagueWalletPresenter } from './presenters/WithdrawFromLeagueWalletPresenter'; // Tokens -import { - LOGGER_TOKEN, - GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE, - GET_LEAGUE_STANDINGS_USE_CASE, - GET_LEAGUE_STATS_USE_CASE, - GET_LEAGUE_FULL_CONFIG_USE_CASE, - GET_LEAGUE_SCORING_CONFIG_USE_CASE, - LIST_LEAGUE_SCORING_PRESETS_USE_CASE, - JOIN_LEAGUE_USE_CASE, - TRANSFER_LEAGUE_OWNERSHIP_USE_CASE, - CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE, - GET_TOTAL_LEAGUES_USE_CASE, - GET_LEAGUE_JOIN_REQUESTS_USE_CASE, - APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE, - REJECT_LEAGUE_JOIN_REQUEST_USE_CASE, - REMOVE_LEAGUE_MEMBER_USE_CASE, - UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE, - GET_LEAGUE_OWNER_SUMMARY_USE_CASE, - GET_LEAGUE_PROTESTS_USE_CASE, - GET_LEAGUE_SEASONS_USE_CASE, - GET_LEAGUE_MEMBERSHIPS_USE_CASE, - GET_LEAGUE_SCHEDULE_USE_CASE, - GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE, - GET_LEAGUE_WALLET_USE_CASE, - WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE, +import { + LOGGER_TOKEN, + GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE, + GET_LEAGUE_STANDINGS_USE_CASE, + GET_LEAGUE_STATS_USE_CASE, + GET_LEAGUE_FULL_CONFIG_USE_CASE, + GET_LEAGUE_SCORING_CONFIG_USE_CASE, + LIST_LEAGUE_SCORING_PRESETS_USE_CASE, + JOIN_LEAGUE_USE_CASE, + TRANSFER_LEAGUE_OWNERSHIP_USE_CASE, + CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE, + GET_TOTAL_LEAGUES_USE_CASE, + GET_LEAGUE_JOIN_REQUESTS_USE_CASE, + APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE, + REJECT_LEAGUE_JOIN_REQUEST_USE_CASE, + REMOVE_LEAGUE_MEMBER_USE_CASE, + UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE, + GET_LEAGUE_OWNER_SUMMARY_USE_CASE, + GET_LEAGUE_PROTESTS_USE_CASE, + GET_LEAGUE_SEASONS_USE_CASE, + GET_LEAGUE_MEMBERSHIPS_USE_CASE, + GET_LEAGUE_SCHEDULE_USE_CASE, + GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE, + GET_LEAGUE_WALLET_USE_CASE, + WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE, GET_SEASON_SPONSORSHIPS_USE_CASE, GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN, GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN, @@ -143,7 +143,7 @@ import { GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN, GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN, WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN, -} from './LeagueProviders'; +} from './LeagueTokens'; @Injectable() export class LeagueService { @@ -197,8 +197,8 @@ export class LeagueService { @Inject(GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN) private readonly leagueScoringConfigPresenter: LeagueScoringConfigPresenter, @Inject(GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN) private readonly getLeagueWalletPresenter: GetLeagueWalletPresenter, @Inject(WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN) private readonly withdrawFromLeagueWalletPresenter: WithdrawFromLeagueWalletPresenter, - @Inject(GET_LEAGUE_JOIN_REQUESTS_USE_CASE) private readonly leagueJoinRequestsPresenter: LeagueJoinRequestsPresenter, - @Inject(GET_LEAGUE_SCHEDULE_USE_CASE) private readonly leagueRacesPresenter: LeagueRacesPresenter, + private readonly leagueJoinRequestsPresenter: LeagueJoinRequestsPresenter, + private readonly leagueRacesPresenter: LeagueRacesPresenter, ) {} async getAllLeaguesWithCapacity(): Promise { diff --git a/apps/api/src/domain/league/LeagueTokens.ts b/apps/api/src/domain/league/LeagueTokens.ts new file mode 100644 index 000000000..f4e44d566 --- /dev/null +++ b/apps/api/src/domain/league/LeagueTokens.ts @@ -0,0 +1,63 @@ +export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository'; +export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository'; +export const LEAGUE_STANDINGS_REPOSITORY_TOKEN = 'ILeagueStandingsRepository'; +export const STANDING_REPOSITORY_TOKEN = 'IStandingRepository'; +export const SEASON_REPOSITORY_TOKEN = 'ISeasonRepository'; +export const LEAGUE_SCORING_CONFIG_REPOSITORY_TOKEN = 'ILeagueScoringConfigRepository'; +export const GAME_REPOSITORY_TOKEN = 'IGameRepository'; +export const PROTEST_REPOSITORY_TOKEN = 'IProtestRepository'; +export const RACE_REPOSITORY_TOKEN = 'IRaceRepository'; +export const DRIVER_REPOSITORY_TOKEN = 'IDriverRepository'; +export const LEAGUE_WALLET_REPOSITORY_TOKEN = 'ILeagueWalletRepository'; +export const TRANSACTION_REPOSITORY_TOKEN = 'ITransactionRepository'; +export const LOGGER_TOKEN = 'Logger'; + +export const GET_LEAGUE_STANDINGS_USE_CASE = 'GetLeagueStandingsUseCase'; +export const GET_ALL_LEAGUES_WITH_CAPACITY_USE_CASE = 'GetAllLeaguesWithCapacityUseCase'; +export const GET_LEAGUE_STATS_USE_CASE = 'GetLeagueStatsUseCase'; +export const GET_LEAGUE_FULL_CONFIG_USE_CASE = 'GetLeagueFullConfigUseCase'; +export const GET_LEAGUE_SCORING_CONFIG_USE_CASE = 'GetLeagueScoringConfigUseCase'; +export const LIST_LEAGUE_SCORING_PRESETS_USE_CASE = 'ListLeagueScoringPresetsUseCase'; +export const JOIN_LEAGUE_USE_CASE = 'JoinLeagueUseCase'; +export const TRANSFER_LEAGUE_OWNERSHIP_USE_CASE = 'TransferLeagueOwnershipUseCase'; +export const CREATE_LEAGUE_WITH_SEASON_AND_SCORING_USE_CASE = 'CreateLeagueWithSeasonAndScoringUseCase'; +export const GET_RACE_PROTESTS_USE_CASE = 'GetRaceProtestsUseCase'; +export const GET_TOTAL_LEAGUES_USE_CASE = 'GetTotalLeaguesUseCase'; +export const GET_LEAGUE_JOIN_REQUESTS_USE_CASE = 'GetLeagueJoinRequestsUseCase'; +export const APPROVE_LEAGUE_JOIN_REQUEST_USE_CASE = 'ApproveLeagueJoinRequestUseCase'; +export const REJECT_LEAGUE_JOIN_REQUEST_USE_CASE = 'RejectLeagueJoinRequestUseCase'; +export const REMOVE_LEAGUE_MEMBER_USE_CASE = 'RemoveLeagueMemberUseCase'; +export const UPDATE_LEAGUE_MEMBER_ROLE_USE_CASE = 'UpdateLeagueMemberRoleUseCase'; +export const GET_LEAGUE_OWNER_SUMMARY_USE_CASE = 'GetLeagueOwnerSummaryUseCase'; +export const GET_LEAGUE_PROTESTS_USE_CASE = 'GetLeagueProtestsUseCase'; +export const GET_LEAGUE_SEASONS_USE_CASE = 'GetLeagueSeasonsUseCase'; +export const GET_LEAGUE_MEMBERSHIPS_USE_CASE = 'GetLeagueMembershipsUseCase'; +export const GET_LEAGUE_SCHEDULE_USE_CASE = 'GetLeagueScheduleUseCase'; +export const GET_LEAGUE_ADMIN_PERMISSIONS_USE_CASE = 'GetLeagueAdminPermissionsUseCase'; +export const GET_LEAGUE_WALLET_USE_CASE = 'GetLeagueWalletUseCase'; +export const WITHDRAW_FROM_LEAGUE_WALLET_USE_CASE = 'WithdrawFromLeagueWalletUseCase'; +export const GET_SEASON_SPONSORSHIPS_USE_CASE = 'GetSeasonSponsorshipsUseCase'; + +export const GET_ALL_LEAGUES_WITH_CAPACITY_OUTPUT_PORT_TOKEN = 'GetAllLeaguesWithCapacityOutputPort_TOKEN'; +export const GET_LEAGUE_STANDINGS_OUTPUT_PORT_TOKEN = 'GetLeagueStandingsOutputPort_TOKEN'; +export const GET_LEAGUE_PROTESTS_OUTPUT_PORT_TOKEN = 'GetLeagueProtestsOutputPort_TOKEN'; +export const GET_SEASON_SPONSORSHIPS_OUTPUT_PORT_TOKEN = 'GetSeasonSponsorshipsOutputPort_TOKEN'; +export const LIST_LEAGUE_SCORING_PRESETS_OUTPUT_PORT_TOKEN = 'ListLeagueScoringPresetsOutputPort_TOKEN'; +export const APPROVE_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN = 'ApproveLeagueJoinRequestOutputPort_TOKEN'; +export const CREATE_LEAGUE_OUTPUT_PORT_TOKEN = 'CreateLeagueOutputPort_TOKEN'; +export const GET_LEAGUE_ADMIN_PERMISSIONS_OUTPUT_PORT_TOKEN = 'GetLeagueAdminPermissionsOutputPort_TOKEN'; +export const GET_LEAGUE_MEMBERSHIPS_OUTPUT_PORT_TOKEN = 'GetLeagueMembershipsOutputPort_TOKEN'; +export const GET_LEAGUE_OWNER_SUMMARY_OUTPUT_PORT_TOKEN = 'GetLeagueOwnerSummaryOutputPort_TOKEN'; +export const GET_LEAGUE_SEASONS_OUTPUT_PORT_TOKEN = 'GetLeagueSeasonsOutputPort_TOKEN'; +export const JOIN_LEAGUE_OUTPUT_PORT_TOKEN = 'JoinLeagueOutputPort_TOKEN'; +export const GET_LEAGUE_SCHEDULE_OUTPUT_PORT_TOKEN = 'GetLeagueScheduleOutputPort_TOKEN'; +export const GET_LEAGUE_STATS_OUTPUT_PORT_TOKEN = 'GetLeagueStatsOutputPort_TOKEN'; +export const REJECT_LEAGUE_JOIN_REQUEST_OUTPUT_PORT_TOKEN = 'RejectLeagueJoinRequestOutputPort_TOKEN'; +export const REMOVE_LEAGUE_MEMBER_OUTPUT_PORT_TOKEN = 'RemoveLeagueMemberOutputPort_TOKEN'; +export const TOTAL_LEAGUES_OUTPUT_PORT_TOKEN = 'TotalLeaguesOutputPort_TOKEN'; +export const TRANSFER_LEAGUE_OWNERSHIP_OUTPUT_PORT_TOKEN = 'TransferLeagueOwnershipOutputPort_TOKEN'; +export const UPDATE_LEAGUE_MEMBER_ROLE_OUTPUT_PORT_TOKEN = 'UpdateLeagueMemberRoleOutputPort_TOKEN'; +export const GET_LEAGUE_FULL_CONFIG_OUTPUT_PORT_TOKEN = 'GetLeagueFullConfigOutputPort_TOKEN'; +export const GET_LEAGUE_SCORING_CONFIG_OUTPUT_PORT_TOKEN = 'GetLeagueScoringConfigOutputPort_TOKEN'; +export const GET_LEAGUE_WALLET_OUTPUT_PORT_TOKEN = 'GetLeagueWalletOutputPort_TOKEN'; +export const WITHDRAW_FROM_LEAGUE_WALLET_OUTPUT_PORT_TOKEN = 'WithdrawFromLeagueWalletOutputPort_TOKEN'; \ No newline at end of file diff --git a/apps/api/src/domain/sponsor/SponsorProviders.ts b/apps/api/src/domain/sponsor/SponsorProviders.ts index 4fc14f849..e90f72c0c 100644 --- a/apps/api/src/domain/sponsor/SponsorProviders.ts +++ b/apps/api/src/domain/sponsor/SponsorProviders.ts @@ -32,8 +32,11 @@ import { RejectSponsorshipRequestUseCase } from '@core/racing/application/use-ca // Import concrete in-memory implementations import { ConsoleLogger } from '@adapters/logging/ConsoleLogger'; +import { InMemoryPaymentRepository } from '@adapters/payments/persistence/inmemory/InMemoryPaymentRepository'; +import { InMemoryWalletRepository } from '@adapters/payments/persistence/inmemory/InMemoryWalletRepository'; import { InMemoryLeagueMembershipRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueMembershipRepository'; import { InMemoryLeagueRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueRepository'; +import { InMemoryLeagueWalletRepository } from '@adapters/racing/persistence/inmemory/InMemoryLeagueWalletRepository'; import { InMemoryRaceRepository } from '@adapters/racing/persistence/inmemory/InMemoryRaceRepository'; import { InMemorySeasonRepository } from '@adapters/racing/persistence/inmemory/InMemorySeasonRepository'; import { InMemorySeasonSponsorshipRepository } from '@adapters/racing/persistence/inmemory/InMemorySeasonSponsorshipRepository'; @@ -62,6 +65,10 @@ export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository'; export const RACE_REPOSITORY_TOKEN = 'IRaceRepository'; export const SPONSORSHIP_PRICING_REPOSITORY_TOKEN = 'ISponsorshipPricingRepository'; export const SPONSORSHIP_REQUEST_REPOSITORY_TOKEN = 'ISponsorshipRequestRepository'; +export const PAYMENT_REPOSITORY_TOKEN = 'IPaymentRepository'; +export const WALLET_REPOSITORY_TOKEN = 'IWalletRepository'; +export const LEAGUE_WALLET_REPOSITORY_TOKEN = 'ILeagueWalletRepository'; +export const NOTIFICATION_SERVICE_TOKEN = 'INotificationService'; export const LOGGER_TOKEN = 'Logger'; // Presenter tokens @@ -145,6 +152,30 @@ export const SponsorProviders: Provider[] = [ useFactory: (logger: Logger) => new InMemorySponsorshipRequestRepository(logger), inject: [LOGGER_TOKEN], }, + { + provide: PAYMENT_REPOSITORY_TOKEN, + useFactory: (logger: Logger) => new InMemoryPaymentRepository(logger), + inject: [LOGGER_TOKEN], + }, + { + provide: WALLET_REPOSITORY_TOKEN, + useFactory: (logger: Logger) => new InMemoryWalletRepository(logger), + inject: [LOGGER_TOKEN], + }, + { + provide: LEAGUE_WALLET_REPOSITORY_TOKEN, + useFactory: (logger: Logger) => new InMemoryLeagueWalletRepository(logger), + inject: [LOGGER_TOKEN], + }, + { + provide: NOTIFICATION_SERVICE_TOKEN, + useFactory: (logger: Logger): NotificationService => ({ + async sendNotification(command: any): Promise { + logger.info('[InMemoryNotificationService] sendNotification', { command }); + }, + }), + inject: [LOGGER_TOKEN], + }, { provide: LOGGER_TOKEN, useClass: ConsoleLogger, @@ -161,6 +192,10 @@ export const SponsorProviders: Provider[] = [ RejectSponsorshipRequestPresenter, SponsorBillingPresenter, // Output ports + { + provide: GET_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN, + useExisting: GetEntitySponsorshipPricingPresenter, + }, { provide: GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN, useExisting: GetEntitySponsorshipPricingPresenter, @@ -267,7 +302,7 @@ export const SponsorProviders: Provider[] = [ ) => { return new GetSponsorBillingUseCase(paymentRepo, seasonSponsorshipRepo); }, - inject: ['IPaymentRepository', SEASON_SPONSORSHIP_REPOSITORY_TOKEN], + inject: [PAYMENT_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN], }, { provide: GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN, @@ -330,9 +365,9 @@ export const SponsorProviders: Provider[] = [ SPONSORSHIP_REQUEST_REPOSITORY_TOKEN, SEASON_SPONSORSHIP_REPOSITORY_TOKEN, SEASON_REPOSITORY_TOKEN, - 'INotificationService', - 'IWalletRepository', - 'ILeagueWalletRepository', + NOTIFICATION_SERVICE_TOKEN, + WALLET_REPOSITORY_TOKEN, + LEAGUE_WALLET_REPOSITORY_TOKEN, LOGGER_TOKEN, ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN, ], diff --git a/apps/api/src/domain/sponsor/SponsorService.ts b/apps/api/src/domain/sponsor/SponsorService.ts index b41b54cea..29ed2421f 100644 --- a/apps/api/src/domain/sponsor/SponsorService.ts +++ b/apps/api/src/domain/sponsor/SponsorService.ts @@ -65,19 +65,9 @@ import { GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN, ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN, REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN, - LOGGER_TOKEN, - GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN, - GET_SPONSORS_PRESENTER_TOKEN, - CREATE_SPONSOR_PRESENTER_TOKEN, - GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN, - GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN, - GET_SPONSOR_PRESENTER_TOKEN, - GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN, - ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN, - REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN, - GET_SPONSOR_BILLING_PRESENTER_TOKEN, GET_SPONSOR_BILLING_USE_CASE_TOKEN, -} from './SponsorProviders'; + LOGGER_TOKEN, +} from './SponsorTokens'; @Injectable() export class SponsorService { @@ -105,25 +95,15 @@ export class SponsorService { @Inject(LOGGER_TOKEN) private readonly logger: Logger, // Injected presenters - @Inject(GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN) private readonly getEntitySponsorshipPricingPresenter: GetEntitySponsorshipPricingPresenter, - @Inject(GET_SPONSORS_PRESENTER_TOKEN) private readonly getSponsorsPresenter: GetSponsorsPresenter, - @Inject(CREATE_SPONSOR_PRESENTER_TOKEN) private readonly createSponsorPresenter: CreateSponsorPresenter, - @Inject(GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN) private readonly getSponsorDashboardPresenter: GetSponsorDashboardPresenter, - @Inject(GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN) private readonly getSponsorSponsorshipsPresenter: GetSponsorSponsorshipsPresenter, - @Inject(GET_SPONSOR_PRESENTER_TOKEN) private readonly getSponsorPresenter: GetSponsorPresenter, - @Inject(GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN) private readonly getPendingSponsorshipRequestsPresenter: GetPendingSponsorshipRequestsPresenter, - @Inject(ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN) private readonly acceptSponsorshipRequestPresenter: AcceptSponsorshipRequestPresenter, - @Inject(REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN) private readonly rejectSponsorshipRequestPresenter: RejectSponsorshipRequestPresenter, - @Inject(GET_SPONSOR_BILLING_PRESENTER_TOKEN) private readonly sponsorBillingPresenter: SponsorBillingPresenter, ) {} diff --git a/apps/api/src/domain/sponsor/SponsorTokens.ts b/apps/api/src/domain/sponsor/SponsorTokens.ts new file mode 100644 index 000000000..1b15720a2 --- /dev/null +++ b/apps/api/src/domain/sponsor/SponsorTokens.ts @@ -0,0 +1,47 @@ +export const SPONSOR_REPOSITORY_TOKEN = 'ISponsorRepository'; +export const SEASON_SPONSORSHIP_REPOSITORY_TOKEN = 'ISeasonSponsorshipRepository'; +export const SEASON_REPOSITORY_TOKEN = 'ISeasonRepository'; +export const LEAGUE_REPOSITORY_TOKEN = 'ILeagueRepository'; +export const LEAGUE_MEMBERSHIP_REPOSITORY_TOKEN = 'ILeagueMembershipRepository'; +export const RACE_REPOSITORY_TOKEN = 'IRaceRepository'; +export const SPONSORSHIP_PRICING_REPOSITORY_TOKEN = 'ISponsorshipPricingRepository'; +export const SPONSORSHIP_REQUEST_REPOSITORY_TOKEN = 'ISponsorshipRequestRepository'; +export const LOGGER_TOKEN = 'Logger'; + +// Presenter tokens +export const GET_ENTITY_SPONSORSHIP_PRICING_PRESENTER_TOKEN = 'GetEntitySponsorshipPricingPresenter'; +export const GET_SPONSORS_PRESENTER_TOKEN = 'GetSponsorsPresenter'; +export const CREATE_SPONSOR_PRESENTER_TOKEN = 'CreateSponsorPresenter'; +export const GET_SPONSOR_DASHBOARD_PRESENTER_TOKEN = 'GetSponsorDashboardPresenter'; +export const GET_SPONSOR_SPONSORSHIPS_PRESENTER_TOKEN = 'GetSponsorSponsorshipsPresenter'; +export const GET_SPONSOR_PRESENTER_TOKEN = 'GetSponsorPresenter'; +export const GET_PENDING_SPONSORSHIP_REQUESTS_PRESENTER_TOKEN = 'GetPendingSponsorshipRequestsPresenter'; +export const ACCEPT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN = 'AcceptSponsorshipRequestPresenter'; +export const REJECT_SPONSORSHIP_REQUEST_PRESENTER_TOKEN = 'RejectSponsorshipRequestPresenter'; +export const GET_SPONSOR_BILLING_PRESENTER_TOKEN = 'SponsorBillingPresenter'; + +// Use case / application service tokens +export const GET_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetSponsorshipPricingUseCase'; +export const GET_SPONSORS_USE_CASE_TOKEN = 'GetSponsorsUseCase'; +export const CREATE_SPONSOR_USE_CASE_TOKEN = 'CreateSponsorUseCase'; +export const GET_SPONSOR_DASHBOARD_USE_CASE_TOKEN = 'GetSponsorDashboardUseCase'; +export const GET_SPONSOR_SPONSORSHIPS_USE_CASE_TOKEN = 'GetSponsorSponsorshipsUseCase'; +export const GET_ENTITY_SPONSORSHIP_PRICING_USE_CASE_TOKEN = 'GetEntitySponsorshipPricingUseCase'; +export const GET_SPONSOR_USE_CASE_TOKEN = 'GetSponsorUseCase'; +export const GET_PENDING_SPONSORSHIP_REQUESTS_USE_CASE_TOKEN = 'GetPendingSponsorshipRequestsUseCase'; +export const ACCEPT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'AcceptSponsorshipRequestUseCase'; +export const REJECT_SPONSORSHIP_REQUEST_USE_CASE_TOKEN = 'RejectSponsorshipRequestUseCase'; +export const GET_SPONSOR_BILLING_USE_CASE_TOKEN = 'GetSponsorBillingUseCase'; + +// Output port tokens +export const GET_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN = 'GetSponsorshipPricingOutputPort_TOKEN'; +export const GET_SPONSORS_OUTPUT_PORT_TOKEN = 'GetSponsorsOutputPort_TOKEN'; +export const CREATE_SPONSOR_OUTPUT_PORT_TOKEN = 'CreateSponsorOutputPort_TOKEN'; +export const GET_SPONSOR_DASHBOARD_OUTPUT_PORT_TOKEN = 'GetSponsorDashboardOutputPort_TOKEN'; +export const GET_SPONSOR_SPONSORSHIPS_OUTPUT_PORT_TOKEN = 'GetSponsorSponsorshipsOutputPort_TOKEN'; +export const GET_ENTITY_SPONSORSHIP_PRICING_OUTPUT_PORT_TOKEN = 'GetEntitySponsorshipPricingOutputPort_TOKEN'; +export const GET_SPONSOR_OUTPUT_PORT_TOKEN = 'GetSponsorOutputPort_TOKEN'; +export const GET_PENDING_SPONSORSHIP_REQUESTS_OUTPUT_PORT_TOKEN = 'GetPendingSponsorshipRequestsOutputPort_TOKEN'; +export const ACCEPT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN = 'AcceptSponsorshipRequestOutputPort_TOKEN'; +export const REJECT_SPONSORSHIP_REQUEST_OUTPUT_PORT_TOKEN = 'RejectSponsorshipRequestOutputPort_TOKEN'; +export const GET_SPONSOR_BILLING_OUTPUT_PORT_TOKEN = 'GetSponsorBillingOutputPort_TOKEN'; \ No newline at end of file diff --git a/apps/website/Dockerfile.dev b/apps/website/Dockerfile.dev index 112ddc9e7..963c78d2c 100644 --- a/apps/website/Dockerfile.dev +++ b/apps/website/Dockerfile.dev @@ -5,15 +5,16 @@ WORKDIR /app # Install bash for better shell capabilities RUN apk add --no-cache bash -# Copy root package.json and install dependencies +# Copy package manifests and install dependencies (incl. workspaces) COPY package.json package-lock.json ./ -RUN npm ci +COPY apps/website/package.json apps/website/package.json +RUN npm ci --workspaces --include-workspace-root RUN find ./node_modules -name "next" -print || true # Debugging line -# Copy apps/website and packages for development +# Copy sources for development (monorepo) COPY apps/website apps/website/ -COPY packages core/ -COPY apps/website/tsconfig.json apps/website/ +COPY core core/ +COPY adapters adapters/ COPY scripts scripts/ COPY tsconfig.base.json ./ diff --git a/apps/website/Dockerfile.prod b/apps/website/Dockerfile.prod index d287b7437..bab0b944a 100644 --- a/apps/website/Dockerfile.prod +++ b/apps/website/Dockerfile.prod @@ -2,21 +2,21 @@ FROM node:20-alpine AS builder WORKDIR /app -# Copy package files and install dependencies +# Copy package manifests and install dependencies (incl. workspaces) COPY package.json package-lock.json ./ -RUN npm ci +COPY apps/website/package.json apps/website/package.json +RUN npm ci --workspaces --include-workspace-root -# Copy apps/website, packages, and config for building +# Copy sources and config for building (monorepo) COPY apps/website apps/website/ -COPY packages core/ -COPY apps/website/tsconfig.json apps/website/ +COPY core core/ +COPY adapters adapters/ COPY scripts scripts/ COPY tsconfig.base.json ./ -# Build the Next.js application -# Run from the root workspace context -RUN node ./node_modules/next/dist/bin/next build +# Build the Next.js application from the workspace +RUN npm run env:website:merge && npm run build --workspace=@gridpilot/website # Production stage: slim image with only production dependencies and built files @@ -28,10 +28,11 @@ WORKDIR /app RUN apk add --no-cache wget -# Copy package files and install production dependencies only +# Copy package files and install production dependencies (incl. workspaces) COPY --from=builder /app/package.json ./ COPY --from=builder /app/package-lock.json ./ -RUN npm ci --omit=dev +COPY --from=builder /app/apps/website/package.json ./apps/website/package.json +RUN npm ci --omit=dev --workspaces --include-workspace-root # Copy built Next.js application COPY --from=builder /app/apps/website/.next ./apps/website/.next @@ -39,10 +40,8 @@ COPY --from=builder /app/apps/website/public ./apps/website/public COPY --from=builder /app/apps/website/package.json ./apps/website/package.json COPY --from=builder /app/apps/website/next.config.mjs ./apps/website/next.config.mjs -# Copy packages (needed for runtime dependencies) -COPY --from=builder /app/packages ./packages ENV NODE_ENV=production ENV NEXT_TELEMETRY_DISABLED=1 -# Run Next.js in production mode from the root workspace context -CMD ["node", "./node_modules/next/dist/bin/next", "start"] \ No newline at end of file +# Run Next.js in production mode (workspace context) +CMD ["npm", "run", "start", "--workspace=@gridpilot/website"] \ No newline at end of file diff --git a/apps/website/next.config.mjs b/apps/website/next.config.mjs index 4e26c5fc0..7373bb26e 100644 --- a/apps/website/next.config.mjs +++ b/apps/website/next.config.mjs @@ -1,9 +1,14 @@ /** @type {import('next').NextConfig} */ +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + const nextConfig = { reactStrictMode: true, - // Fix for Next.js 13+ Turbopack in monorepos to correctly identify the workspace root - outputFileTracingRoot: '/Users/marcmintel/Projects/gridpilot', + // Fix for monorepos: point tracing to repo root (portable across machines/containers) + outputFileTracingRoot: path.join(__dirname, '../..'), images: { remotePatterns: [ { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 277f99daa..6b4cd2f01 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -1,5 +1,3 @@ -version: '3.8' - services: api: build: @@ -12,6 +10,11 @@ services: ports: - "3000:3000" - "9229:9229" + volumes: + - ./apps/api:/app/apps/api + - ./core:/app/core + - ./adapters:/app/adapters + - ./tsconfig.base.json:/app/tsconfig.base.json:ro depends_on: db: condition: service_healthy @@ -29,6 +32,12 @@ services: - NEXT_TELEMETRY_DISABLED=1 ports: - "3001:3000" + volumes: + - ./apps/website:/app/apps/website + - ./core:/app/core + - ./adapters:/app/adapters + - ./scripts:/app/scripts + - ./tsconfig.base.json:/app/tsconfig.base.json:ro networks: - gridpilot-network restart: unless-stopped @@ -36,10 +45,8 @@ services: db: image: postgres:15-alpine restart: unless-stopped - environment: - - POSTGRES_DB=${POSTGRES_DB:-gridpilot_db} - - POSTGRES_USER=${POSTGRES_USER:-user} - - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} + env_file: + - .env.development ports: - "5432:5432" volumes: @@ -47,7 +54,7 @@ services: networks: - gridpilot-network healthcheck: - test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-user} -d ${POSTGRES_DB:-gridpilot_db}"] + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] interval: 5s timeout: 5s retries: 5 diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 27282df3b..7e286db2a 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,6 +1,4 @@ -version: '3.8' - services: api: build: @@ -151,7 +149,7 @@ services: cpus: '0.25' memory: 256M healthcheck: - test: ["CMD", "redis-cli", "--raw", "incr", "ping"] + test: ["CMD-SHELL", "redis-cli -a \"$${REDIS_PASSWORD:-CHANGE_ME_IN_PRODUCTION}\" ping"] interval: 10s timeout: 3s retries: 5