240 lines
6.9 KiB
JavaScript
240 lines
6.9 KiB
JavaScript
import fs from 'node:fs';
|
|
const id = {
|
|
name: 'id',
|
|
in: 'path',
|
|
required: true,
|
|
schema: { type: 'string', format: 'uuid' },
|
|
};
|
|
const string = { type: 'string' };
|
|
const schema = (properties) => ({ type: 'object', properties });
|
|
const paths = {};
|
|
const operation = (summary, parameters = [], body = null) => ({
|
|
summary,
|
|
security: [{ laravelSession: [] }],
|
|
parameters: [
|
|
...parameters,
|
|
...(body
|
|
? [
|
|
{
|
|
name: 'Idempotency-Key',
|
|
in: 'header',
|
|
required: true,
|
|
schema: { type: 'string', format: 'uuid' },
|
|
},
|
|
]
|
|
: []),
|
|
],
|
|
...(body
|
|
? {
|
|
requestBody: {
|
|
required: true,
|
|
content: { 'application/json': { schema: body } },
|
|
},
|
|
}
|
|
: {}),
|
|
responses: Object.fromEntries(
|
|
[
|
|
200,
|
|
...(summary.startsWith('Create source') ? [201] : []),
|
|
401,
|
|
403,
|
|
404,
|
|
409,
|
|
422,
|
|
429,
|
|
].map((code) => [
|
|
code,
|
|
{
|
|
description:
|
|
code < 300
|
|
? 'JSON result with X-Correlation-ID; private, no-store'
|
|
: 'Request rejected; validation and workflow errors use Problem Details within territorial middleware',
|
|
},
|
|
]),
|
|
),
|
|
});
|
|
for (const collection of [
|
|
'countries',
|
|
'provinces',
|
|
'districts',
|
|
'admin-posts',
|
|
'localities',
|
|
'search',
|
|
])
|
|
paths['/' + collection] = {
|
|
get: operation(
|
|
'Read currently published territories: ' + collection,
|
|
[
|
|
'q',
|
|
'cursor',
|
|
'parent_id',
|
|
'country_id',
|
|
'province_id',
|
|
'district_id',
|
|
'admin_post_id',
|
|
]
|
|
.map((name) => ({ name, in: 'query', schema: string }))
|
|
.concat([
|
|
{
|
|
name: 'limit',
|
|
in: 'query',
|
|
schema: { type: 'integer', minimum: 1, maximum: 100 },
|
|
},
|
|
]),
|
|
),
|
|
};
|
|
paths['/territories/{type}/{id}'] = {
|
|
get: operation('Read published territory and translated names', [
|
|
id,
|
|
{
|
|
name: 'type',
|
|
in: 'path',
|
|
required: true,
|
|
schema: {
|
|
enum: [
|
|
'country',
|
|
'province',
|
|
'district',
|
|
'admin_post',
|
|
'locality',
|
|
],
|
|
},
|
|
},
|
|
]),
|
|
};
|
|
const territory = schema({
|
|
name: string,
|
|
code: string,
|
|
location_type: {
|
|
enum: ['country', 'province', 'district', 'admin_post', 'locality'],
|
|
},
|
|
parent_id: { type: ['string', 'null'], format: 'uuid' },
|
|
source_id: { type: 'string', format: 'uuid' },
|
|
valid_from: { type: 'string', format: 'date' },
|
|
valid_to: { type: ['string', 'null'], format: 'date' },
|
|
reason: { type: 'string', minLength: 10, maxLength: 1000 },
|
|
});
|
|
territory.required = [
|
|
'name',
|
|
'code',
|
|
'location_type',
|
|
'source_id',
|
|
'valid_from',
|
|
'reason',
|
|
];
|
|
paths['/territories'] = {
|
|
post: operation(
|
|
'Create private territorial draft; designated editor with MFA',
|
|
[],
|
|
territory,
|
|
),
|
|
};
|
|
paths['/territories/{id}'] = {
|
|
patch: operation(
|
|
'Replace draft attributes and invalidate editorial approvals',
|
|
[id],
|
|
{
|
|
...territory,
|
|
required: [...territory.required, 'version_no'],
|
|
properties: {
|
|
...territory.properties,
|
|
version_no: { type: 'integer', minimum: 1 },
|
|
},
|
|
},
|
|
),
|
|
};
|
|
paths['/territories/{id}/transitions'] = {
|
|
post: operation(
|
|
'Submit, review, publish, reject or archive; independent actors required',
|
|
[id],
|
|
{
|
|
...schema({
|
|
action: {
|
|
enum: [
|
|
'pending_review',
|
|
'approved',
|
|
'published',
|
|
'rejected',
|
|
'archived',
|
|
],
|
|
},
|
|
version_no: { type: 'integer', minimum: 1 },
|
|
reason: { type: 'string', minLength: 10 },
|
|
}),
|
|
required: ['action', 'version_no', 'reason'],
|
|
},
|
|
),
|
|
};
|
|
paths['/sources'] = {
|
|
post: operation('Create source in pending state', [], {
|
|
...schema({
|
|
name: string,
|
|
responsible_entity: string,
|
|
reference: string,
|
|
license: string,
|
|
checksum: { type: 'string', pattern: '^[0-9a-f]{64}$' },
|
|
source_version: string,
|
|
collected_at: { type: 'string', format: 'date' },
|
|
original_crs: string,
|
|
}),
|
|
required: [
|
|
'name',
|
|
'reference',
|
|
'checksum',
|
|
'source_version',
|
|
'collected_at',
|
|
],
|
|
}),
|
|
};
|
|
paths['/sources/{id}/transitions'] = {
|
|
post: operation(
|
|
'Independent source approval or revocation with evidence reference',
|
|
[id],
|
|
{
|
|
...schema({
|
|
action: { enum: ['approved', 'revoked'] },
|
|
approval_reference: { type: 'string', minLength: 10 },
|
|
}),
|
|
required: ['action', 'approval_reference'],
|
|
},
|
|
),
|
|
};
|
|
paths['/sources'].get = operation(
|
|
'Management-only source metadata and decision evidence, MFA required',
|
|
);
|
|
paths['/drafts'] = {
|
|
get: operation(
|
|
'Management-only territorial editorial records, MFA required',
|
|
),
|
|
};
|
|
fs.writeFileSync(
|
|
'docs/investmoz/gis/territorial-openapi.json',
|
|
JSON.stringify(
|
|
{
|
|
openapi: '3.1.0',
|
|
info: {
|
|
title: 'InvestMoz territorial registry — implemented subset',
|
|
version: '0.1.0',
|
|
description:
|
|
'Local session adapter. OAuth/OIDC, geometric queries, assignments, imports, hierarchy endpoint and administrative UI remain unimplemented. Response schemas require expansion before external certification.',
|
|
},
|
|
servers: [{ url: '/api/v1/geo' }],
|
|
paths,
|
|
components: {
|
|
securitySchemes: {
|
|
laravelSession: {
|
|
type: 'apiKey',
|
|
in: 'cookie',
|
|
name: 'deployment_session_cookie',
|
|
description:
|
|
'Actual name configured by Laravel; write requests also require Laravel CSRF/origin protection. This is not OAuth/OIDC.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
null,
|
|
2,
|
|
) + '\n',
|
|
);
|
|
console.log('Territorial OpenAPI: ' + Object.keys(paths).length + ' paths');
|