u
This commit is contained in:
77
src/api/openapi.js
Normal file
77
src/api/openapi.js
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Shared OpenAPI components scanned by `scripts/generateApiDocs.js`.
|
||||
* Route-specific paths live as `@openapi` blocks next to each handler.
|
||||
*
|
||||
* @openapi
|
||||
* components:
|
||||
* securitySchemes:
|
||||
* cookieAuth:
|
||||
* type: apiKey
|
||||
* in: cookie
|
||||
* name: token
|
||||
* description: HTTP-only session cookie set by POST /auth/login
|
||||
* schemas:
|
||||
* Error:
|
||||
* type: object
|
||||
* required: [error]
|
||||
* properties:
|
||||
* error:
|
||||
* type: string
|
||||
* User:
|
||||
* type: object
|
||||
* required: [id, email, selectedCompanyId]
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* email:
|
||||
* type: string
|
||||
* format: email
|
||||
* selectedCompanyId:
|
||||
* type: integer
|
||||
* nullable: true
|
||||
* picture:
|
||||
* type: string
|
||||
* format: uri
|
||||
* Company:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* iso639:
|
||||
* type: string
|
||||
* name:
|
||||
* type: string
|
||||
* taxrate:
|
||||
* type: number
|
||||
* reducedtaxrate:
|
||||
* type: number
|
||||
* Category:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* parentId:
|
||||
* type: integer
|
||||
* nullable: true
|
||||
* sort:
|
||||
* type: integer
|
||||
* name:
|
||||
* type: string
|
||||
* ArticleSummary:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* parentId:
|
||||
* type: integer
|
||||
* name:
|
||||
* type: string
|
||||
* sku:
|
||||
* type: string
|
||||
* ArticleDetail:
|
||||
* type: object
|
||||
* description: Full article payload (unit, brand, categoryIds, photos, eans, prices, …)
|
||||
* additionalProperties: true
|
||||
*/
|
||||
|
||||
export {};
|
||||
@@ -23,11 +23,65 @@ function articleErrorStatus(error) {
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @route POST /article
|
||||
* @description Creates an article under an authorized category (`parentId`).
|
||||
* @body {number} parentId - Category id
|
||||
* @body {string} name
|
||||
* @returns {Object} 201 - `{ article: { id, parentId, name, sku } }`
|
||||
* @openapi
|
||||
* /article:
|
||||
* post:
|
||||
* tags: [Article]
|
||||
* summary: Create article
|
||||
* description: Creates an article under an authorized category (`parentId`).
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: article/create.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [parentId, name]
|
||||
* properties:
|
||||
* parentId:
|
||||
* type: integer
|
||||
* description: Category id
|
||||
* name:
|
||||
* type: string
|
||||
* example:
|
||||
* parentId: 10
|
||||
* name: New article
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Created
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* article:
|
||||
* $ref: '#/components/schemas/ArticleSummary'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized for parent category
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Parent not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.post(`${basePath}/article`, async (req, res) => {
|
||||
const parent = parsePositiveInt(req.body?.parentId, 'parentId');
|
||||
@@ -55,9 +109,116 @@ export function register(app, basePath = '') {
|
||||
});
|
||||
|
||||
/**
|
||||
* @route GET /article/:sku
|
||||
* @description Returns article detail by SKU if the user may access its category.
|
||||
* @returns {Object} 200 - `{ article }` detail shape (`unit`, `brand`, `categoryIds`, `photos`, `eans`, `prices`, …)
|
||||
* @openapi
|
||||
* /article/{sku}:
|
||||
* get:
|
||||
* tags: [Article]
|
||||
* summary: Get article by SKU
|
||||
* description: Returns article detail by SKU if the user may access its category.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: article/get.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sku
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Article SKU
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Article detail
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* article:
|
||||
* $ref: '#/components/schemas/ArticleDetail'
|
||||
* 400:
|
||||
* description: Missing sku
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Article not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* patch:
|
||||
* tags: [Article]
|
||||
* summary: Update article by SKU
|
||||
* description: Updates an article name by SKU; returns the same detail shape as GET.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: article/update.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: sku
|
||||
* required: true
|
||||
* schema:
|
||||
* type: string
|
||||
* description: Article SKU
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [name]
|
||||
* properties:
|
||||
* name:
|
||||
* type: string
|
||||
* example:
|
||||
* name: Updated name
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Updated article detail
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* article:
|
||||
* $ref: '#/components/schemas/ArticleDetail'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Article not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.get(`${basePath}/article/:sku`, async (req, res) => {
|
||||
const sku = typeof req.params.sku === 'string' ? req.params.sku.trim() : '';
|
||||
@@ -75,12 +236,6 @@ export function register(app, basePath = '') {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @route PATCH /article/:sku
|
||||
* @description Updates an article name by SKU; returns the same detail shape as GET.
|
||||
* @body {string} name
|
||||
* @returns {Object} 200 - `{ article }` detail shape
|
||||
*/
|
||||
app.patch(`${basePath}/article/:sku`, async (req, res) => {
|
||||
const sku = typeof req.params.sku === 'string' ? req.params.sku.trim() : '';
|
||||
if (!sku) {
|
||||
|
||||
@@ -12,14 +12,64 @@ const googleClient = new OAuth2Client(env.googleClientId);
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @route POST /auth/login
|
||||
* @description Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`), and sets an HTTP-only `token` cookie.
|
||||
* @body {string} idToken - Google ID token from Google Identity Services
|
||||
* @returns {Object} 200 - `{ message, user }` with `{ id, email, selectedCompanyId, picture? }` in `user`
|
||||
* @returns {Object} 400 - Missing token or Google client not configured
|
||||
* @returns {Object} 401 - Invalid Google token
|
||||
* @returns {Object} 403 - Email not on allowlist
|
||||
* @returns {Object} 500 - Server error
|
||||
* @openapi
|
||||
* /auth/login:
|
||||
* post:
|
||||
* tags: [Auth]
|
||||
* summary: Login with Google ID token
|
||||
* description: Verifies a Google ID token, checks allowlist (or creates entry when `IMPLICIT_CREATE=true`), and sets an HTTP-only `token` cookie.
|
||||
* security: []
|
||||
* x-markdown: auth/login.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [idToken]
|
||||
* properties:
|
||||
* idToken:
|
||||
* type: string
|
||||
* description: Google ID token from Google Identity Services
|
||||
* example:
|
||||
* idToken: "<google-id-token>"
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Login successful; `token` cookie set
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* message:
|
||||
* type: string
|
||||
* example: Login successful
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* 400:
|
||||
* description: Missing token or Google client not configured
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Invalid Google token or account
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Email not on allowlist
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.post(`${basePath}/auth/login`, async (req, res) => {
|
||||
try {
|
||||
|
||||
@@ -9,9 +9,25 @@ import { clearCachedUser, clearTokenCookie } from '../../middleware/auth.js';
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @route POST /auth/logout
|
||||
* @description Clears the HTTP-only `token` cookie set at login.
|
||||
* @returns {Object} 200 - `{ message }` confirming logout
|
||||
* @openapi
|
||||
* /auth/logout:
|
||||
* post:
|
||||
* tags: [Auth]
|
||||
* summary: Logout
|
||||
* description: Clears the HTTP-only `token` cookie set at login.
|
||||
* security: []
|
||||
* x-markdown: auth/logout.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Logged out
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* message:
|
||||
* type: string
|
||||
* example: Logged out successfully
|
||||
*/
|
||||
app.post(`${basePath}/auth/logout`, (req, res) => {
|
||||
const token = req.cookies.token;
|
||||
|
||||
@@ -7,9 +7,38 @@ import { meJson } from '../../auth/meJson.js';
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @route GET /auth/me
|
||||
* @description Returns the authenticated user from `req.user` and the Google client ID.
|
||||
* @returns {Object} 200 - `{ user: { id, email, selectedCompanyId, picture? }, googleClientId }`
|
||||
* @openapi
|
||||
* /auth/me:
|
||||
* get:
|
||||
* tags: [Auth]
|
||||
* summary: Current user
|
||||
* description: Returns the authenticated user from the session cookie and the Google client ID. Unauthenticated calls still receive `googleClientId` with 401.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: auth/me.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Authenticated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* googleClientId:
|
||||
* type: string
|
||||
* 401:
|
||||
* description: Unauthorized — still includes `googleClientId` for the frontend
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* error:
|
||||
* type: string
|
||||
* googleClientId:
|
||||
* type: string
|
||||
*/
|
||||
app.get(`${basePath}/auth/me`, (req, res) => {
|
||||
const { id, email, selectedCompanyId, picture } = req.user;
|
||||
|
||||
@@ -8,10 +8,56 @@ import { clearCachedUser } from '../../middleware/auth.js';
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @route PUT /auth/selected-company
|
||||
* @description Sets the authenticated user's selected company.
|
||||
* @body {number} companyId
|
||||
* @returns {Object} 200 - `{ user: { id, email, selectedCompanyId, picture? } }`
|
||||
* @openapi
|
||||
* /auth/selected-company:
|
||||
* put:
|
||||
* tags: [Auth]
|
||||
* summary: Set selected company
|
||||
* description: Sets the authenticated user's selected company.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: auth/selected-company.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [companyId]
|
||||
* properties:
|
||||
* companyId:
|
||||
* type: integer
|
||||
* minimum: 1
|
||||
* example:
|
||||
* companyId: 1
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Updated user
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* 400:
|
||||
* description: Invalid companyId
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.put(`${basePath}/auth/selected-company`, async (req, res) => {
|
||||
const companyId = Number(req.body?.companyId);
|
||||
|
||||
@@ -23,9 +23,59 @@ function categoryErrorStatus(error) {
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @route GET /category/tree
|
||||
* @description Returns the category tree for the authenticated user's selected company.
|
||||
* @returns {Object} 200 - `{ languageId, tree, articles }`
|
||||
* @openapi
|
||||
* /category/tree:
|
||||
* get:
|
||||
* tags: [Category]
|
||||
* summary: Category tree
|
||||
* description: Returns the category tree for the authenticated user's selected company.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: category/tree.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Tree and articles for the selected company
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* languageId:
|
||||
* type: integer
|
||||
* tree:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* additionalProperties: true
|
||||
* articles:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* additionalProperties: true
|
||||
* 400:
|
||||
* description: No company selected
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Company not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.get(`${basePath}/category/tree`, async (req, res) => {
|
||||
const companyId = req.user.selectedCompanyId;
|
||||
@@ -44,11 +94,64 @@ export function register(app, basePath = '') {
|
||||
});
|
||||
|
||||
/**
|
||||
* @route POST /category
|
||||
* @description Creates a category under an authorized parent.
|
||||
* @body {number} parentId
|
||||
* @body {string} name
|
||||
* @returns {Object} 201 - `{ category: { id, parentId, sort, name } }`
|
||||
* @openapi
|
||||
* /category:
|
||||
* post:
|
||||
* tags: [Category]
|
||||
* summary: Create category
|
||||
* description: Creates a category under an authorized parent.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: category/create.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [parentId, name]
|
||||
* properties:
|
||||
* parentId:
|
||||
* type: integer
|
||||
* name:
|
||||
* type: string
|
||||
* example:
|
||||
* parentId: 1
|
||||
* name: New category
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Created
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* category:
|
||||
* $ref: '#/components/schemas/Category'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Parent not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.post(`${basePath}/category`, async (req, res) => {
|
||||
const parent = parsePositiveInt(req.body?.parentId, 'parentId');
|
||||
@@ -76,9 +179,53 @@ export function register(app, basePath = '') {
|
||||
});
|
||||
|
||||
/**
|
||||
* @route GET /category/:id/articles
|
||||
* @description Returns articles in a category the user is authorized for.
|
||||
* @returns {Object} 200 - `{ articles }`
|
||||
* @openapi
|
||||
* /category/{id}/articles:
|
||||
* get:
|
||||
* tags: [Category]
|
||||
* summary: Articles in category
|
||||
* description: Returns articles in a category the user is authorized for.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: category/articles.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Category id
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Articles list
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* articles:
|
||||
* type: array
|
||||
* items:
|
||||
* type: object
|
||||
* additionalProperties: true
|
||||
* 400:
|
||||
* description: Invalid id
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.get(`${basePath}/category/:id/articles`, async (req, res) => {
|
||||
const parsed = parsePositiveInt(req.params.id, 'id');
|
||||
@@ -97,9 +244,43 @@ export function register(app, basePath = '') {
|
||||
});
|
||||
|
||||
/**
|
||||
* @route DELETE /category/:id
|
||||
* @description Deletes a category the user is authorized for (not the company root).
|
||||
* @returns {Object} 204 - No content
|
||||
* @openapi
|
||||
* /category/{id}:
|
||||
* delete:
|
||||
* tags: [Category]
|
||||
* summary: Delete category
|
||||
* description: Deletes a category the user is authorized for (not the company root).
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: category/delete.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* description: Category id
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Deleted
|
||||
* 400:
|
||||
* description: Invalid id or cannot delete root
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.delete(`${basePath}/category/:id`, async (req, res) => {
|
||||
const parsed = parsePositiveInt(req.params.id, 'id');
|
||||
|
||||
@@ -40,9 +40,97 @@ function companyErrorStatus(error) {
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @route GET /company
|
||||
* @description Lists companies for the authenticated user.
|
||||
* @returns {Object} 200 - `{ companies }`
|
||||
* @openapi
|
||||
* /company:
|
||||
* get:
|
||||
* tags: [Company]
|
||||
* summary: List companies
|
||||
* description: Lists companies for the authenticated user.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: company/list.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Company list
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* companies:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/Company'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* post:
|
||||
* tags: [Company]
|
||||
* summary: Create company
|
||||
* description: Creates a company and selects it for the current user.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: company/create.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [iso639, name, taxrate, reducedtaxrate]
|
||||
* properties:
|
||||
* iso639:
|
||||
* type: string
|
||||
* name:
|
||||
* type: string
|
||||
* taxrate:
|
||||
* type: number
|
||||
* reducedtaxrate:
|
||||
* type: number
|
||||
* example:
|
||||
* iso639: de
|
||||
* name: Acme GmbH
|
||||
* taxrate: 19
|
||||
* reducedtaxrate: 7
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Created; selected company updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* company:
|
||||
* $ref: '#/components/schemas/Company'
|
||||
* user:
|
||||
* $ref: '#/components/schemas/User'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 409:
|
||||
* description: Company name not available
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.get(`${basePath}/company`, async (req, res) => {
|
||||
try {
|
||||
@@ -54,15 +142,6 @@ export function register(app, basePath = '') {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @route POST /company
|
||||
* @description Creates a company.
|
||||
* @body {string} iso639
|
||||
* @body {string} name
|
||||
* @body {number} taxrate
|
||||
* @body {number} reducedtaxrate
|
||||
* @returns {Object} 201 - `{ company }`
|
||||
*/
|
||||
app.post(`${basePath}/company`, async (req, res) => {
|
||||
const body = parseCompanyBody(req.body);
|
||||
if (body.error) {
|
||||
@@ -83,9 +162,105 @@ export function register(app, basePath = '') {
|
||||
});
|
||||
|
||||
/**
|
||||
* @route PUT /company/:id
|
||||
* @description Updates a company.
|
||||
* @returns {Object} 200 - `{ company }`
|
||||
* @openapi
|
||||
* /company/{id}:
|
||||
* put:
|
||||
* tags: [Company]
|
||||
* summary: Update company
|
||||
* description: Updates a company.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: company/update.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [iso639, name, taxrate, reducedtaxrate]
|
||||
* properties:
|
||||
* iso639:
|
||||
* type: string
|
||||
* name:
|
||||
* type: string
|
||||
* taxrate:
|
||||
* type: number
|
||||
* reducedtaxrate:
|
||||
* type: number
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Updated
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* company:
|
||||
* $ref: '#/components/schemas/Company'
|
||||
* 400:
|
||||
* description: Validation error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Company not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 409:
|
||||
* description: Company name not available
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* delete:
|
||||
* tags: [Company]
|
||||
* summary: Delete company
|
||||
* description: Deletes a company.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: company/delete.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Deleted
|
||||
* 400:
|
||||
* description: Invalid id
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
app.put(`${basePath}/company/:id`, async (req, res) => {
|
||||
const parsedId = parseCompanyId(req.params.id);
|
||||
@@ -108,11 +283,6 @@ export function register(app, basePath = '') {
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @route DELETE /company/:id
|
||||
* @description Deletes a company.
|
||||
* @returns {Object} 204 - No content
|
||||
*/
|
||||
app.delete(`${basePath}/company/:id`, async (req, res) => {
|
||||
const parsedId = parseCompanyId(req.params.id);
|
||||
if (parsedId.error) {
|
||||
|
||||
@@ -7,10 +7,37 @@ import { ensureConnection } from '../../db/store.js';
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @route GET /health
|
||||
* @description Verifies the database client can establish a connection.
|
||||
* @returns {Object} 200 - `{ ok: true }` when the database is reachable
|
||||
* @returns {Object} 503 - `{ ok: false, error }` when the check fails
|
||||
* @openapi
|
||||
* /health:
|
||||
* get:
|
||||
* tags: [Health]
|
||||
* summary: Database health check
|
||||
* description: Verifies the database client can establish a connection.
|
||||
* security: []
|
||||
* x-markdown: health.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Database reachable
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* ok:
|
||||
* type: boolean
|
||||
* example: true
|
||||
* 503:
|
||||
* description: Database unreachable
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* ok:
|
||||
* type: boolean
|
||||
* example: false
|
||||
* error:
|
||||
* type: string
|
||||
*/
|
||||
app.get(`${basePath}/health`, async (_request, response) => {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user