feat: attachments on tickets and comments
Files are uploaded over REST, stored on disk (a volume in Docker) and recorded on the ticket as events; the bytes are written first, so no attachment ever points at a missing file. Only images, video, audio, PDF and plain text are shown inline, everything with nosniff and a sandbox CSP. The web shows thumbnails, uploads by drag and drop or by pasting a screenshot, and attaches files to new comments. Also adds `nx run api:contracts`, which writes schema.gql and openapi.json without starting the API or any database. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||
import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger';
|
||||
import {
|
||||
PIREON_BEARER,
|
||||
PIREON_OAUTH,
|
||||
@@ -35,19 +35,19 @@ show the previous version (or 404 right after a create).
|
||||
For live updates use the GraphQL subscriptions at \`/graphql\` (graphql-ws).
|
||||
`.trim();
|
||||
|
||||
/**
|
||||
* Builds the OpenAPI document from the REST controllers, serves Swagger UI and
|
||||
* (in development) writes it to OPENAPI_FILE so other apps can generate clients.
|
||||
*/
|
||||
export function setupOpenApi(app: INestApplication): void {
|
||||
const config = app.get(ConfigService);
|
||||
if (!config.get<boolean>('SWAGGER_ENABLED')) return;
|
||||
export interface OpenApiSettings {
|
||||
pireonBaseUrl: string;
|
||||
scopes: string[];
|
||||
passwordGrant: boolean;
|
||||
}
|
||||
|
||||
const pireon =
|
||||
config.get<string>('PIREON_BASE_URL') ?? 'https://oauth.pireon.dev';
|
||||
const scopes = (config.get<string>('PIREON_SCOPES') ?? '')
|
||||
.split(/[\s,]+/)
|
||||
.filter(Boolean);
|
||||
/** The OpenAPI document of the REST controllers. Needs no database. */
|
||||
export function buildOpenApiDocument(
|
||||
app: INestApplication,
|
||||
settings: OpenApiSettings,
|
||||
): OpenAPIObject {
|
||||
const pireon = settings.pireonBaseUrl;
|
||||
const scopes = settings.scopes;
|
||||
const scopeDocs = Object.fromEntries(
|
||||
scopes.map((scope) => [
|
||||
scope,
|
||||
@@ -76,7 +76,7 @@ export function setupOpenApi(app: INestApplication): void {
|
||||
refreshUrl: tokenUrl,
|
||||
scopes: scopeDocs,
|
||||
},
|
||||
...(config.get<boolean>('PIREON_PASSWORD_GRANT')
|
||||
...(settings.passwordGrant
|
||||
? {
|
||||
password: {
|
||||
tokenUrl,
|
||||
@@ -125,6 +125,27 @@ export function setupOpenApi(app: INestApplication): void {
|
||||
}
|
||||
}
|
||||
|
||||
return document;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the OpenAPI document from the REST controllers, serves Swagger UI and
|
||||
* (in development) writes it to OPENAPI_FILE so other apps can generate clients.
|
||||
*/
|
||||
export function setupOpenApi(app: INestApplication): void {
|
||||
const config = app.get(ConfigService);
|
||||
if (!config.get<boolean>('SWAGGER_ENABLED')) return;
|
||||
|
||||
const scopes = (config.get<string>('PIREON_SCOPES') ?? '')
|
||||
.split(/[\s,]+/)
|
||||
.filter(Boolean);
|
||||
const document = buildOpenApiDocument(app, {
|
||||
pireonBaseUrl:
|
||||
config.get<string>('PIREON_BASE_URL') ?? 'https://oauth.pireon.dev',
|
||||
scopes,
|
||||
passwordGrant: !!config.get<boolean>('PIREON_PASSWORD_GRANT'),
|
||||
});
|
||||
|
||||
const file = config.get<string>('OPENAPI_FILE');
|
||||
if (file) {
|
||||
writeFileSync(
|
||||
|
||||
Reference in New Issue
Block a user