Files
dashback/docs/api/openapi.json
2026-07-31 18:25:42 +02:00

1385 lines
35 KiB
JSON

{
"openapi": "3.0.3",
"info": {
"title": "Dash API",
"version": "1.0.0",
"description": "Backend API for Dash — Google SSO auth, MSSQL, session cookies.\n\nBrowser clients must send cookies (`credentials: 'include'`). Sessions use an HTTP-only `token` cookie (`Secure`, `SameSite=Strict`, 24h); HTTPS is required for login."
},
"servers": [
{
"url": "http://localhost:4208/api",
"description": "Local default (PORT / .env)"
}
],
"tags": [
{
"name": "Auth",
"description": "Google SSO and session"
},
{
"name": "Health",
"description": "Connectivity checks"
},
{
"name": "Article",
"description": "Articles under categories"
},
{
"name": "Category",
"description": "Category tree and membership"
},
{
"name": "Company",
"description": "Company CRUD"
}
],
"paths": {
"/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"
}
}
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
}
}
},
"/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"
}
}
}
}
}
}
}
},
"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
}
}
}
}