All checks were successful
CI / Build container images (pull_request) Successful in 3m53s
CI / Auth e2e pack (pull_request) Successful in 8m42s
CI / Auth e2e pack (push) Successful in 8m41s
CI / Lint, typecheck, test (pull_request) Successful in 6m30s
CI / Import/export fidelity gate (pull_request) Successful in 58s
CD / Build and push images (push) Successful in 18s
CD / Smoke tests against Test (push) Successful in 1m19s
CD / Deploy to Test (push) Successful in 16s
CD / Promote to Int (push) Successful in 12s
CI / Lint, typecheck, test (push) Successful in 6m41s
CI / Build container images (push) Has been skipped
CI / Import/export fidelity gate (push) Successful in 52s
An operator holding a font licence could only use it by baking the file into a custom image, which tied every change to a rebuild and left the file out of the backup. ADR 0016 said there is no runtime font management. It also listed this exact case under "Alternatives considered" — *may become a Site-Admin- level feature later*. The amendment takes that option and answers the two objections it raised: licensing risk (Site Admins only, licence recorded with the family) and file-format attack surface (magic-byte check and a size cap, never a parse). - `CUSTOM_FONTS_DIR` (default `./data/fonts`) — a sibling of uploads and plugins, NOT inside the image-baked `FONTS_DIR`, where a deploy would overwrite it and no backup would ever see it. - One list of data directories (`apps/backup/src/data-dirs.ts`) now feeds both the nightly archive and the restore, so they cannot drift. #306 and #307 add one line each instead of a second mechanism. - Both Dockerfiles bake the path. The backup image sets its volume paths itself ("self-sufficient without compose env" — #71's lesson) and reads no *_DIR from compose; without the ENV entry the archive would have skipped the directory silently. - The PDF path already read WOFF2 from disk at request time, so it only had to pick the other base directory for a custom family. - `fontStack`/`fontEntry` take the instance's uploaded families as an argument — they are runtime data. The catalog is searched first, and a colliding family name is rejected at upload, so a custom font can never shadow a catalog one. - Deletion is never blocked by usage: an unknown family already falls back to the system stack, so affected ponds degrade instead of breaking. The count of affected ponds travels into the audit entry. - Audit catalogue v1.6 (`font.uploaded`, `font.deleted`). Verified: api full suite against a fresh database, 102 files / 571 tests. The upload suite writes into a real temp directory and reads the bytes back off disk, so the storage layer is exercised rather than mocked.
119 lines
4.7 KiB
TypeScript
119 lines
4.7 KiB
TypeScript
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
|
import { APP_FILTER } from '@nestjs/core';
|
|
import { LoggerModule } from 'nestjs-pino';
|
|
|
|
import { AdminModule } from './admin/admin.module';
|
|
import { AuditModule } from './audit/audit.module';
|
|
import { AuthModule } from './auth/auth.module';
|
|
import { BackupModule } from './backup/backup.module';
|
|
import { ApiExceptionFilter } from './common/api-exception.filter';
|
|
import { maskTokenParam } from './common/mask-token-param';
|
|
import { SecurityHeadersMiddleware } from './common/security-headers.middleware';
|
|
import { CommentsModule } from './comments/comments.module';
|
|
import { CompactionModule } from './compaction/compaction.module';
|
|
import { AppConfig } from './config/app-config.service';
|
|
import { ConfigModule } from './config/config.module';
|
|
import { FilesModule } from './files/files.module';
|
|
import { GrantsModule } from './grants/grants.module';
|
|
import { HealthModule } from './health/health.module';
|
|
import { HomeModule } from './home/home.module';
|
|
import { FontsModule } from './fonts/fonts.module';
|
|
import { ImportExportModule } from './import-export/import-export.module';
|
|
import { LabelsModule } from './labels/labels.module';
|
|
import { LegalModule } from './legal/legal.module';
|
|
import { LinksModule } from './links/links.module';
|
|
import { MailModule } from './mail/mail.module';
|
|
import { McpModule } from './mcp/mcp.module';
|
|
import { MembersModule } from './members/members.module';
|
|
import { PagesModule } from './pages/pages.module';
|
|
import { PermissionsModule } from './permissions/permissions.module';
|
|
import { PluginsModule } from './plugins/plugins.module';
|
|
import { PondsModule } from './ponds/ponds.module';
|
|
import { PrismaModule } from './prisma/prisma.module';
|
|
import { PublicApiModule } from './public-api/public-api.module';
|
|
import { PublicModule } from './public/public.module';
|
|
import { RateLimitModule } from './rate-limit/rate-limit.module';
|
|
import { ReadTrailModule } from './read-trail/read-trail.module';
|
|
import { SearchModule } from './search/search.module';
|
|
import { SettingsModule } from './settings/settings.module';
|
|
import { SetupModule } from './setup/setup.module';
|
|
import { TrashModule } from './trash/trash.module';
|
|
import { UsersModule } from './users/users.module';
|
|
import { NotificationsModule } from './notifications/notifications.module';
|
|
import { WatchesModule } from './watches/watches.module';
|
|
import { FavoritesModule } from './favorites/favorites.module';
|
|
import { VersionsModule } from './versions/versions.module';
|
|
|
|
@Module({
|
|
imports: [
|
|
ConfigModule,
|
|
PrismaModule,
|
|
AuditModule,
|
|
ReadTrailModule,
|
|
RateLimitModule,
|
|
MailModule,
|
|
SettingsModule,
|
|
// Before SetupModule and AuthModule: global guards run in registration
|
|
// order, and the maintenance gate (in-app restore, issue #103) must
|
|
// answer before anything touches the mid-restore database.
|
|
BackupModule,
|
|
// Before AuthModule: the setup gate must win over AuthGuard's 401 while
|
|
// setup is pending.
|
|
SetupModule,
|
|
UsersModule,
|
|
PermissionsModule,
|
|
PondsModule,
|
|
PagesModule,
|
|
CommentsModule,
|
|
WatchesModule,
|
|
FavoritesModule,
|
|
NotificationsModule,
|
|
FilesModule,
|
|
TrashModule,
|
|
CompactionModule,
|
|
VersionsModule,
|
|
LabelsModule,
|
|
LegalModule,
|
|
HomeModule,
|
|
LinksModule,
|
|
SearchModule,
|
|
GrantsModule,
|
|
MembersModule,
|
|
PublicModule,
|
|
PublicApiModule,
|
|
McpModule,
|
|
FontsModule,
|
|
ImportExportModule,
|
|
PluginsModule,
|
|
AuthModule,
|
|
AdminModule,
|
|
LoggerModule.forRootAsync({
|
|
inject: [AppConfig],
|
|
useFactory: (config: AppConfig) => ({
|
|
pinoHttp: {
|
|
level: config.env.LOG_LEVEL,
|
|
// Human-readable logs in local development, JSON everywhere else.
|
|
transport: config.env.NODE_ENV === 'development' ? { target: 'pino-pretty' } : undefined,
|
|
autoLogging: config.env.NODE_ENV !== 'test',
|
|
// Request bodies are never logged (operations.md logging rules).
|
|
redact: { paths: ['req.headers.authorization', 'req.headers.cookie'], remove: true },
|
|
// Feed tokens travel as `?token=` (issue #191) — mask them so the
|
|
// request log never stores the credential.
|
|
serializers: {
|
|
req: (req: { url?: string }) => ({ ...req, url: maskTokenParam(req.url) }),
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
HealthModule,
|
|
],
|
|
providers: [{ provide: APP_FILTER, useClass: ApiExceptionFilter }],
|
|
})
|
|
export class AppModule implements NestModule {
|
|
configure(consumer: MiddlewareConsumer): void {
|
|
// Module-level (not main.ts) so createTestApp boots the identical
|
|
// security-header/CORS middleware — see security-headers.middleware.ts.
|
|
consumer.apply(SecurityHeadersMiddleware).forRoutes('{*path}');
|
|
}
|
|
}
|