import { Injectable } from '@nestjs/common'; import { Cron, CronExpression, Interval } from '@nestjs/schedule'; import { NextFunction, Request, Response } from 'express'; import { readFileSync } from 'node:fs'; import { ONE_HOUR } from 'src/constants'; import { ConfigRepository } from 'src/repositories/config.repository'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { AuthService } from 'src/services/auth.service'; import { JobService } from 'src/services/job.service'; import { SharedLinkService } from 'src/services/shared-link.service'; import { VersionService } from 'src/services/version.service'; import { OpenGraphTags } from 'src/utils/misc'; const render = (index: string, meta: OpenGraphTags) => { const tags = ` ${meta.imageUrl ? `` : ''} ${meta.imageUrl ? `` : ''}`; return index.replace('', tags); }; @Injectable() export class ApiService { constructor( private authService: AuthService, private jobService: JobService, private sharedLinkService: SharedLinkService, private versionService: VersionService, private configRepository: ConfigRepository, private logger: LoggingRepository, ) { this.logger.setContext(ApiService.name); } @Interval(ONE_HOUR.as('milliseconds')) async onVersionCheck() { await this.versionService.handleQueueVersionCheck(); } @Cron(CronExpression.EVERY_DAY_AT_MIDNIGHT) async onNightlyJob() { await this.jobService.handleNightlyJobs(); } ssr(excludePaths: string[]) { const { resourcePaths } = this.configRepository.getEnv(); let index = ''; try { index = readFileSync(resourcePaths.web.indexHtml).toString(); } catch { this.logger.warn(`Unable to open ${resourcePaths.web.indexHtml}, skipping SSR.`); } return async (request: Request, res: Response, next: NextFunction) => { if ( request.url.startsWith('/api') || request.method.toLowerCase() !== 'get' || excludePaths.some((item) => request.url.startsWith(item)) ) { return next(); } const targets = [ { regex: /^\/share\/(.+)$/, onMatch: async (matches: RegExpMatchArray) => { const key = matches[1]; const auth = await this.authService.validateSharedLink(key); return this.sharedLinkService.getMetadataTags(auth); }, }, ]; let html = index; try { for (const { regex, onMatch } of targets) { const matches = request.url.match(regex); if (matches) { const meta = await onMatch(matches); if (meta) { html = render(index, meta); } break; } } } catch {} res.type('text/html').header('Cache-Control', 'no-store').send(html); }; } }