···11import { Logger as NestLogger } from "@nestjs/common";
22-import type { Logger } from "@riotbyte/project-q-core";
22+import type { Logger } from "@riotbyte-com/project-q-core";
3344export class NestProjectQLogger implements Logger {
55 private readonly logger = new NestLogger("ProjectQ");
···11-import * as fs from "node:fs/promises";
22-import * as path from "node:path";
31import { Inject, Injectable, Logger } from "@nestjs/common";
44-import type { Envelope, Handler } from "@riotbyte/project-q-core";
55-import { HandlerTag } from "@riotbyte/project-q-nestjs";
22+import type { Envelope, Handler } from "@riotbyte-com/project-q-core";
33+import { HandlerTag } from "@riotbyte-com/project-q-nestjs";
44+import { FILE_STORAGE, type FileStorage } from "@cv/file-storage";
65import type { WorkerConfig } from "../config";
76import { HtmlToPdfService } from "../pdf/html-to-pdf.service";
87import { WORKER_CONFIG } from "../worker.module";
···2120 constructor(
2221 private readonly pdfService: HtmlToPdfService,
2322 @Inject(WORKER_CONFIG) private readonly config: WorkerConfig,
2323+ @Inject(FILE_STORAGE) private readonly storage: FileStorage,
2424 ) {}
25252626 async handle(envelope: Envelope): Promise<void> {
···32323333 const pdf = await this.pdfService.convert(html, this.config.pdfTimeoutMs);
34343535- await fs.mkdir(this.config.pdfOutputDir, { recursive: true });
3636- const outputPath = path.join(this.config.pdfOutputDir, `${cvId}.pdf`);
3737- await fs.writeFile(outputPath, pdf);
3535+ const key = `${cvId}.pdf`;
3636+ await this.storage.write(key, pdf);
38373939- this.logger.log(`PDF written to ${outputPath} (${pdf.length} bytes)`);
3838+ this.logger.log(`PDF stored as ${key} (${pdf.length} bytes)`);
4039 }
4140}
+1-1
apps/worker/src/heartbeat/heartbeat.listener.ts
···33 WorkerRunningEvent,
44 WorkerStartedEvent,
55 WorkerStoppedEvent,
66-} from "@riotbyte/project-q-core";
66+} from "@riotbyte-com/project-q-core";
77import { EventEmitter2 } from "eventemitter2";
88import type { HeartbeatStrategy } from "./heartbeat.strategy";
99import { HEARTBEAT_STRATEGIES } from "./heartbeat.strategy";
+1-1
apps/worker/src/logger.provider.ts
···11import { Logger as NestLogger } from "@nestjs/common";
22-import type { Logger } from "@riotbyte/project-q-core";
22+import type { Logger } from "@riotbyte-com/project-q-core";
3344export class NestProjectQLogger implements Logger {
55 private readonly logger = new NestLogger("ProjectQ");
···11+import { type DynamicModule, Module, type Type } from "@nestjs/common";
22+import { DiskFileStorage } from "./disk-file-storage";
33+import { FILE_STORAGE } from "./file-storage.interface";
44+import { R2FileStorage, type R2FileStorageConfig } from "./r2-file-storage";
55+66+type DiskConfig = { driver: "disk"; baseDir: string };
77+type R2Config = { driver: "r2" } & R2FileStorageConfig;
88+export type FileStorageModuleConfig = DiskConfig | R2Config;
99+1010+/**
1111+ * Implement this interface to provide FileStorage configuration
1212+ * via NestJS DI. Register it with `FileStorageModule.forRootAsync()`.
1313+ */
1414+export interface FileStorageConfigProvider {
1515+ createFileStorageConfig(): FileStorageModuleConfig;
1616+}
1717+1818+export const FILE_STORAGE_CONFIG_PROVIDER = Symbol("FILE_STORAGE_CONFIG_PROVIDER");
1919+2020+@Module({})
2121+export class FileStorageModule {
2222+ /**
2323+ * Static configuration — pass the config directly.
2424+ */
2525+ static forRoot(config: FileStorageModuleConfig): DynamicModule {
2626+ return {
2727+ module: FileStorageModule,
2828+ providers: [
2929+ {
3030+ provide: FILE_STORAGE,
3131+ useFactory: () => createStorage(config),
3232+ },
3333+ ],
3434+ exports: [FILE_STORAGE],
3535+ global: true,
3636+ };
3737+ }
3838+3939+ /**
4040+ * Async configuration — provide a class that implements
4141+ * `FileStorageConfigProvider`. The class can inject ConfigService
4242+ * or any other NestJS provider.
4343+ */
4444+ static forRootAsync(options: {
4545+ imports?: Type[];
4646+ useClass: Type<FileStorageConfigProvider>;
4747+ }): DynamicModule {
4848+ return {
4949+ module: FileStorageModule,
5050+ imports: options.imports ?? [],
5151+ providers: [
5252+ {
5353+ provide: FILE_STORAGE_CONFIG_PROVIDER,
5454+ useClass: options.useClass,
5555+ },
5656+ {
5757+ provide: FILE_STORAGE,
5858+ useFactory: (provider: FileStorageConfigProvider) =>
5959+ createStorage(provider.createFileStorageConfig()),
6060+ inject: [FILE_STORAGE_CONFIG_PROVIDER],
6161+ },
6262+ ],
6363+ exports: [FILE_STORAGE],
6464+ global: true,
6565+ };
6666+ }
6767+}
6868+6969+const createStorage = (config: FileStorageModuleConfig) =>
7070+ config.driver === "r2"
7171+ ? new R2FileStorage(config)
7272+ : new DiskFileStorage(config.baseDir);
+8
packages/file-storage/src/index.ts
···11+export { FILE_STORAGE, type FileStorage } from "./file-storage.interface";
22+export { DiskFileStorage } from "./disk-file-storage";
33+export { R2FileStorage, type R2FileStorageConfig } from "./r2-file-storage";
44+export {
55+ FileStorageModule,
66+ type FileStorageModuleConfig,
77+ type FileStorageConfigProvider,
88+} from "./file-storage.module";