u
This commit is contained in:
@@ -98,6 +98,9 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
| [`routes/auth/logout.md`](routes/auth/logout.md) | `POST /auth/logout` |
|
||||
| [`routes/auth/me.md`](routes/auth/me.md) | `GET /auth/me` |
|
||||
| [`routes/auth/selected-company.md`](routes/auth/selected-company.md) | `PUT /auth/selected-company` |
|
||||
| [`routes/autoinvite/list.md`](routes/autoinvite/list.md) | `GET /autoinvite` |
|
||||
| [`routes/autoinvite/create.md`](routes/autoinvite/create.md) | `POST /autoinvite` |
|
||||
| [`routes/autoinvite/delete.md`](routes/autoinvite/delete.md) | `DELETE /autoinvite/{id}` |
|
||||
| [`routes/backup.md`](routes/backup.md) | `GET /backup` |
|
||||
| [`routes/backup/_id/vfs.md`](routes/backup/_id/vfs.md) | `GET /backup/{id}/vfs` |
|
||||
| [`routes/backup/_id/vfs/recover.md`](routes/backup/_id/vfs/recover.md) | `POST /backup/{id}/vfs/recover` |
|
||||
@@ -140,6 +143,10 @@ Generated from `@openapi` annotations in [`src/api/routes/`](../../src/api/route
|
||||
| [`routes/companysetting/set.md`](routes/companysetting/set.md) | `PUT /company/setting/{key}` |
|
||||
| [`routes/company/delete.md`](routes/company/delete.md) | `DELETE /company/{id}` |
|
||||
| [`routes/company/update.md`](routes/company/update.md) | `PUT /company/{id}` |
|
||||
| [`routes/companyuser/list.md`](routes/companyuser/list.md) | `GET /companyuser` |
|
||||
| [`routes/companyuser/create.md`](routes/companyuser/create.md) | `POST /companyuser` |
|
||||
| [`routes/companyuser/delete.md`](routes/companyuser/delete.md) | `DELETE /companyuser/{id}` |
|
||||
| [`routes/companyuser/update.md`](routes/companyuser/update.md) | `PATCH /companyuser/{id}` |
|
||||
| [`routes/customergroup/list.md`](routes/customergroup/list.md) | `GET /customergroup` |
|
||||
| [`routes/customergroup/create.md`](routes/customergroup/create.md) | `POST /customergroup` |
|
||||
| [`routes/customergroup/order.md`](routes/customergroup/order.md) | `PUT /customergroup/order` |
|
||||
@@ -352,10 +359,12 @@ Clients subscribe via [`GET /events`](routes/events.md) (`text/event-stream`). A
|
||||
| `article-<sku>` | `PUT /article/{sku}/brand`, `PUT /article/{sku}/categories`, `PUT /article/{sku}/eans`, `POST /article/{sku}/image`, `DELETE /article/{sku}/image/{fullHash}`, `PUT /article/{sku}/images/order`, `PUT /article/{sku}/prices`, `POST /article/{sku}/supplier`, `PUT /article/{sku}/supplier/order`, `DELETE /article/{sku}/supplier/{id}`, `PATCH /article/{sku}/supplier/{id}`, `PUT /article/{sku}/track_stock`, `PUT /article/{sku}/weight`, `POST /stock/adjust` |
|
||||
| `articles` | `POST /article`, `DELETE /article/{sku}`, `PATCH /article/{sku}`, `DELETE /category/{id}`, `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
||||
| `attributename` | `POST /attributename`, `PUT /attributename/order`, `DELETE /attributename/{id}`, `PATCH /attributename/{id}`, `DELETE /attributevalue/{id}/image`, `POST /attributevalue/{id}/image` |
|
||||
| `autoinvite` | `POST /autoinvite`, `DELETE /autoinvite/{id}` |
|
||||
| `brand` | `POST /brand`, `DELETE /brand/{id}`, `PATCH /brand/{id}`, `DELETE /brand/{id}/image`, `POST /brand/{id}/image` |
|
||||
| `carrier` | `POST /carrier`, `PUT /carrier/order`, `DELETE /carrier/{id}`, `PATCH /carrier/{id}` |
|
||||
| `category` | `POST /category`, `PUT /category/order`, `DELETE /category/{id}`, `PATCH /category/{id}`, `DELETE /category/{id}/image`, `POST /category/{id}/image`, `POST /category/{id}/image/generate` |
|
||||
| `company` | `PUT /auth/selected-company`, `POST /company`, `DELETE /company/{id}`, `PUT /company/{id}` |
|
||||
| `companyuser` | `POST /companyuser`, `DELETE /companyuser/{id}`, `PATCH /companyuser/{id}` |
|
||||
| `delivery` | `DELETE /delivery/{id}/file/{fileId}`, `POST /delivery/{id}/pdf`, `POST /stock/accept` |
|
||||
| `labeltemplate` | `POST /labeltemplate`, `DELETE /labeltemplate/{id}`, `PATCH /labeltemplate/{id}`, `PUT /labeltemplate/{id}` |
|
||||
| `ledger` | `DELETE /shipment/{id}`, `POST /stock/accept`, `POST /stock/adjust`, `POST /stock/ship` |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Dash API Routes Overview (All)
|
||||
|
||||
Overview of all 307 API endpoints across all modules.
|
||||
Overview of all 314 API endpoints across all modules.
|
||||
|
||||
### Notation
|
||||
|
||||
@@ -27,7 +27,7 @@ Params: path values first, then `query:` and `body:` (`body?` = optional body).
|
||||
- `T16` = `{ id: int, manifestarticle_id: int, transaction_type: str, amount: num, transaction_text: str, hash_version: int, prev_hash?: str, sku?: str|null, created_at?: str }`
|
||||
- `T17` = `{ id: int, name: str, sort: int, exclusive: bool, values: T30[], articleCount?: int }`
|
||||
- `T18` = `{ id: int, companyuser_id: int, provider: str, model: str, created_at: str, updated_at: str, state: str, started_by?: int|null, first_message?: str|null }`
|
||||
- `T19` = `{ id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num }`
|
||||
- `T19` = `{ id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num, role: str }`
|
||||
- `T20` = `{ id: int, sku: str|null, name: str|null, pu: num, amount: num, net: num, rebate: num, taxrate: num, comment: str|null, shippable: bool }`
|
||||
- `T21` = `{ amount: num, net: num, taxrate: num, sku?: str|null, name?: str|null, pu?: num, rebate?: num, comment?: str|null, shippable?: bool }`
|
||||
- `T22` = `{ id: int, agentprinterId: int, type: str, state: str, data: str, createdAt: str, printeragentId?: int|null, completedAt?: str|null }`
|
||||
@@ -276,6 +276,18 @@ _none_
|
||||
- Params: `body: { companyId: int }`
|
||||
- Returns: `200 { user: T35 }`
|
||||
|
||||
### Section: /autoinvite
|
||||
|
||||
#### List auto-invitations — `GET /autoinvite`
|
||||
_none_
|
||||
- Returns: `200 { autoinvites: { id: int, email: str, role: str, createdAt: str }[] }`
|
||||
#### Create auto-invitation — `POST /autoinvite`
|
||||
- Params: `body: { email: str, role?: str }`
|
||||
- Returns: `201 { autoinvite: { id: int, email: str, role: str } }`
|
||||
#### Delete auto-invitation — `DELETE /autoinvite/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
|
||||
### Section: /backup
|
||||
|
||||
#### List backups and service status — `GET /backup`
|
||||
@@ -417,6 +429,21 @@ _none_
|
||||
- Params: `id: int, body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
|
||||
- Returns: `200 { company: T19 }`
|
||||
|
||||
### Section: /companyuser
|
||||
|
||||
#### List company users — `GET /companyuser`
|
||||
_none_
|
||||
- Returns: `200 { companyusers: { id: int, googleAuthId: int, email: str, role: str }[] }`
|
||||
#### Add an existing user to the company — `POST /companyuser`
|
||||
- Params: `body: { email: str, role?: str }`
|
||||
- Returns: `201 { companyuser: { id: int, googleAuthId: int, email: str, role: str } }`
|
||||
#### Remove a user from the company — `DELETE /companyuser/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Change a company user's role — `PATCH /companyuser/{id}`
|
||||
- Params: `id: int, body: { role: str }`
|
||||
- Returns: `200 { companyuser: { id: int, googleAuthId: int, role: str } }`
|
||||
|
||||
### Section: /customergroup
|
||||
|
||||
#### List customer groups — `GET /customergroup`
|
||||
@@ -523,6 +550,8 @@ _none_
|
||||
- `ledger`: `{}`
|
||||
- `attributename`: `{}`
|
||||
- `propertyname`: `{}`
|
||||
- `companyuser`: `{}`
|
||||
- `autoinvite`: `{}`
|
||||
- `labeltemplate`: `{}`
|
||||
- `printer`: `{}`
|
||||
- `printeragent`: `{}`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Dash API Routes Overview
|
||||
|
||||
Overview of all 189 API endpoints across all modules.
|
||||
Overview of all 196 API endpoints across all modules.
|
||||
|
||||
### Notation
|
||||
|
||||
@@ -183,6 +183,18 @@ _none_
|
||||
- Params: `id: int`
|
||||
- Returns: `200 { attributevalue: { id: int, thumbW: int|null, thumbH: int|null, thumbHash: str|null, pics: T22[] } }`
|
||||
|
||||
### Section: /autoinvite
|
||||
|
||||
#### List auto-invitations — `GET /autoinvite`
|
||||
_none_
|
||||
- Returns: `200 { autoinvites: { id: int, email: str, role: str, createdAt: str }[] }`
|
||||
#### Create auto-invitation — `POST /autoinvite`
|
||||
- Params: `body: { email: str, role?: str }`
|
||||
- Returns: `201 { autoinvite: { id: int, email: str, role: str } }`
|
||||
#### Delete auto-invitation — `DELETE /autoinvite/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
|
||||
### Section: /brand
|
||||
|
||||
#### List brands — `GET /brand`
|
||||
@@ -268,7 +280,7 @@ _none_
|
||||
|
||||
#### List companies — `GET /company`
|
||||
_none_
|
||||
- Returns: `200 { companies: { id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num }[] }`
|
||||
- Returns: `200 { companies: { id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num, role: str }[] }`
|
||||
#### Get company history — `GET /company/history`
|
||||
- Params: `query: limit?: int`
|
||||
- Returns: `200 { history: { id: int, date: str, text: str, companyuserId?: int|null }[], truncated: bool }`
|
||||
@@ -289,7 +301,22 @@ _none_
|
||||
- Returns: `200 { setting: { key: str, value: str|null } }`
|
||||
#### Update company — `PUT /company/{id}`
|
||||
- Params: `id: int, body: { iso639: str, name: str, taxrate: num, reducedtaxrate: num }`
|
||||
- Returns: `200 { company: { id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num } }`
|
||||
- Returns: `200 { company: { id: int, name: str, rootCategoryId: int|null, languageId: int|null, iso639: str|null, taxrate: num, reducedtaxrate: num, taxfreerate: num, role: str } }`
|
||||
|
||||
### Section: /companyuser
|
||||
|
||||
#### List company users — `GET /companyuser`
|
||||
_none_
|
||||
- Returns: `200 { companyusers: { id: int, googleAuthId: int, email: str, role: str }[] }`
|
||||
#### Add an existing user to the company — `POST /companyuser`
|
||||
- Params: `body: { email: str, role?: str }`
|
||||
- Returns: `201 { companyuser: { id: int, googleAuthId: int, email: str, role: str } }`
|
||||
#### Remove a user from the company — `DELETE /companyuser/{id}`
|
||||
- Params: `id: int`
|
||||
- Returns: `204 void`
|
||||
#### Change a company user's role — `PATCH /companyuser/{id}`
|
||||
- Params: `id: int, body: { role: str }`
|
||||
- Returns: `200 { companyuser: { id: int, googleAuthId: int, role: str } }`
|
||||
|
||||
### Section: /customergroup
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ Updated user
|
||||
|--------|-------------|
|
||||
| 400 | Invalid companyId — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (the user is not a member of this company) — `{"error":"…"}` |
|
||||
| 500 | Server error — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
55
docs/api/routes/autoinvite/create.md
Normal file
55
docs/api/routes/autoinvite/create.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# `POST /autoinvite`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Create auto-invitation
|
||||
|
||||
Registers an email for automatic membership. When a user with this email registers,
|
||||
the invitation is removed from the list and the user is added to the company with the given role.
|
||||
Emails that are already registered are rejected (409); use `POST /companyuser` for those. Admin only.
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `email` | yes | string | |
|
||||
| `role` | no | string | |
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "newhire@example.com",
|
||||
"role": "user"
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `autoinvite` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created invitation
|
||||
|
||||
```json
|
||||
{
|
||||
"autoinvite": {
|
||||
"id": 1,
|
||||
"email": "…",
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected, invalid email or invalid role — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
|
||||
| 409 | User already registered or invitation already exists — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
36
docs/api/routes/autoinvite/delete.md
Normal file
36
docs/api/routes/autoinvite/delete.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# `DELETE /autoinvite/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Delete auto-invitation
|
||||
|
||||
Deletes a pending auto-invitation of the selected company. Admin only.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `autoinvite` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `204`
|
||||
|
||||
Deleted
|
||||
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid id or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
|
||||
| 404 | Invitation not found — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
36
docs/api/routes/autoinvite/list.md
Normal file
36
docs/api/routes/autoinvite/list.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# `GET /autoinvite`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List auto-invitations
|
||||
|
||||
Lists pending auto-invitations of the selected company. Admin only.
|
||||
|
||||
## Response `200`
|
||||
|
||||
Pending invitations
|
||||
|
||||
```json
|
||||
{
|
||||
"autoinvites": [
|
||||
{
|
||||
"id": 1,
|
||||
"email": "…",
|
||||
"role": "admin",
|
||||
"createdAt": "…"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -42,7 +42,8 @@ Created; selected company updated
|
||||
"iso639": "…",
|
||||
"taxrate": 1,
|
||||
"reducedtaxrate": 1,
|
||||
"taxfreerate": 1
|
||||
"taxfreerate": 1,
|
||||
"role": "admin"
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
|
||||
@@ -21,7 +21,8 @@ Company list
|
||||
"iso639": "…",
|
||||
"taxrate": 1,
|
||||
"reducedtaxrate": 1,
|
||||
"taxfreerate": 1
|
||||
"taxfreerate": 1,
|
||||
"role": "admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -48,7 +48,8 @@ Updated
|
||||
"iso639": "…",
|
||||
"taxrate": 1,
|
||||
"reducedtaxrate": 1,
|
||||
"taxfreerate": 1
|
||||
"taxfreerate": 1,
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
57
docs/api/routes/companyuser/create.md
Normal file
57
docs/api/routes/companyuser/create.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# `POST /companyuser`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Add an existing user to the company
|
||||
|
||||
Adds an existing (registered) user, identified by email, to the selected company. Admin only.
|
||||
The added user can then switch to this company via `PUT /auth/selected-company`.
|
||||
For emails that are not registered yet use `POST /autoinvite`.
|
||||
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `email` | yes | string | |
|
||||
| `role` | no | string | |
|
||||
|
||||
```json
|
||||
{
|
||||
"email": "colleague@example.com",
|
||||
"role": "user"
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `companyuser` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `201`
|
||||
|
||||
Created company user
|
||||
|
||||
```json
|
||||
{
|
||||
"companyuser": {
|
||||
"id": 1,
|
||||
"googleAuthId": 1,
|
||||
"email": "…",
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected, invalid email or invalid role — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
|
||||
| 404 | No registered user with this email — `{"error":"…"}` |
|
||||
| 409 | User already in company — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
40
docs/api/routes/companyuser/delete.md
Normal file
40
docs/api/routes/companyuser/delete.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# `DELETE /companyuser/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Remove a user from the company
|
||||
|
||||
Removes a member from the selected company. History entries stay but lose their author link;
|
||||
if the removed user had this company selected, their selection is cleared.
|
||||
The last admin cannot be removed. Admin only.
|
||||
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `companyuser` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `204`
|
||||
|
||||
Removed
|
||||
|
||||
_No content_
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid id or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
|
||||
| 404 | Company user not found — `{"error":"…"}` |
|
||||
| 409 | Cannot remove the last admin — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
36
docs/api/routes/companyuser/list.md
Normal file
36
docs/api/routes/companyuser/list.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# `GET /companyuser`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
List company users
|
||||
|
||||
Lists the members of the selected company with their role. Admin only.
|
||||
|
||||
## Response `200`
|
||||
|
||||
Company members
|
||||
|
||||
```json
|
||||
{
|
||||
"companyusers": [
|
||||
{
|
||||
"id": 1,
|
||||
"googleAuthId": 1,
|
||||
"email": "…",
|
||||
"role": "admin"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | No company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
57
docs/api/routes/companyuser/update.md
Normal file
57
docs/api/routes/companyuser/update.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# `PATCH /companyuser/{id}`
|
||||
|
||||
Protected. Requires a valid session cookie (`credentials: 'include'`).
|
||||
|
||||
Change a company user's role
|
||||
|
||||
Changes the role of a member of the selected company. The last admin cannot be demoted. Admin only.
|
||||
|
||||
## Path parameters
|
||||
|
||||
| Param | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `id` | yes | integer | |
|
||||
|
||||
## Body (JSON)
|
||||
|
||||
| Field | Required | Type | Description |
|
||||
|-------|----------|------|-------------|
|
||||
| `role` | yes | string | |
|
||||
|
||||
```json
|
||||
{
|
||||
"role": "admin"
|
||||
}
|
||||
```
|
||||
|
||||
## Signals
|
||||
|
||||
On success, emits SSE signal: `companyuser` (see [`GET /events`](../events.md)).
|
||||
|
||||
## Response `200`
|
||||
|
||||
Updated company user
|
||||
|
||||
```json
|
||||
{
|
||||
"companyuser": {
|
||||
"id": 1,
|
||||
"googleAuthId": 1,
|
||||
"role": "admin"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Errors
|
||||
|
||||
| Status | Description |
|
||||
|--------|-------------|
|
||||
| 400 | Invalid id, role or no company selected — `{"error":"…"}` |
|
||||
| 401 | Unauthorized — `{"error":"…"}` |
|
||||
| 403 | Not authorized (admin of the selected company required) — `{"error":"…"}` |
|
||||
| 404 | Company user not found — `{"error":"…"}` |
|
||||
| 409 | Cannot demote the last admin — `{"error":"…"}` |
|
||||
|
||||
---
|
||||
|
||||
_Generated from `@openapi` JSDoc. Do not edit by hand — run `npm run docs:api`._
|
||||
@@ -48,6 +48,8 @@ emits `{ sender, brandId }`.
|
||||
| `printer` | `{}` | `POST /printer`, `PUT /printer/{id}`, `PATCH /printer/{id}`, `DELETE /printer/{id}` |
|
||||
| `printeragent` | `{}` | `PUT /printeragent`, `PATCH /printeragent/{id}`, `DELETE /printeragent/{id}`, printer agent connect/disconnect/state |
|
||||
| `agentprinter` | `{ printers }` or `{}` | `PATCH /agentprinter/{id}`, `PUT /agentprinter/order`, `DELETE /agentprinter/{id}`, agent state updates / printer discovery |
|
||||
| `companyuser` | `{}` | `POST /companyuser`, `PATCH /companyuser/{id}`, `DELETE /companyuser/{id}` |
|
||||
| `autoinvite` | `{}` | `POST /autoinvite`, `DELETE /autoinvite/{id}` |
|
||||
| `warehouse` | `{}` | `POST /warehouse`, `PUT /warehouse/{id}`, `PATCH /warehouse/{id}`, `DELETE /warehouse/{id}` |
|
||||
| `supplier` | `{}` | `POST /supplier`, `PATCH /supplier/{id}`, `DELETE /supplier/{id}` |
|
||||
| `vfs` | `{}` | VFS uploads and filesystem mutations such as `POST /vfs/upload`, `POST /vfs/mkdir`, `POST /vfs/rename`, `PUT /vfs/{*path}`, `DELETE /vfs/{*path}` |
|
||||
|
||||
@@ -59,6 +59,36 @@
|
||||
* type: number
|
||||
* taxfreerate:
|
||||
* type: number
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [admin, user]
|
||||
* description: The caller's role in this company
|
||||
* CompanyUser:
|
||||
* type: object
|
||||
* required: [id, googleAuthId, email, role]
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* googleAuthId:
|
||||
* type: integer
|
||||
* email:
|
||||
* type: string
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [admin, user]
|
||||
* AutoInvite:
|
||||
* type: object
|
||||
* required: [id, email, role, createdAt]
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* email:
|
||||
* type: string
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [admin, user]
|
||||
* createdAt:
|
||||
* type: string
|
||||
* SuccessResponse:
|
||||
* type: object
|
||||
* required: [ok]
|
||||
|
||||
@@ -53,6 +53,12 @@ export function register(app, basePath = '') {
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized (the user is not a member of this company)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 500:
|
||||
* description: Server error
|
||||
* content:
|
||||
@@ -79,6 +85,9 @@ export function register(app, basePath = '') {
|
||||
|
||||
res.status(200).json({ user });
|
||||
} catch (error) {
|
||||
if (error.message === 'Not authorized') {
|
||||
return res.status(403).json({ error: error.message });
|
||||
}
|
||||
console.error('Set selected company failed:', error);
|
||||
res.status(500).json({ error: 'Failed to set selected company' });
|
||||
}
|
||||
|
||||
271
src/api/routes/autoinvite.js
Normal file
271
src/api/routes/autoinvite.js
Normal file
@@ -0,0 +1,271 @@
|
||||
import * as db from '../../db/autoinvite.js';
|
||||
import { emit } from '../sse.js';
|
||||
|
||||
// #region Helpers
|
||||
|
||||
function requireSelectedCompany(req) {
|
||||
const companyId = req.user?.selectedCompanyId;
|
||||
if (companyId == null) {
|
||||
return { error: 'No company selected' };
|
||||
}
|
||||
return { companyId };
|
||||
}
|
||||
|
||||
function parsePositiveInt(val, field = 'id') {
|
||||
const id = Number(val);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
return { error: `${field} is required` };
|
||||
}
|
||||
return { id };
|
||||
}
|
||||
|
||||
function autoinviteErrorStatus(error) {
|
||||
if (error.message === 'No company selected') return 400;
|
||||
if (error.message === 'Not authorized') return 403;
|
||||
if (error.message === 'Company not found') return 404;
|
||||
if (error.message === 'Invitation not found') return 404;
|
||||
if (error.message === 'Invalid role') return 400;
|
||||
if (error.message === 'Invalid email') return 400;
|
||||
if (error.message === 'User already registered') return 409;
|
||||
if (error.message === 'Invitation already exists') return 409;
|
||||
return 500;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* Registers auto-invitation routes. All routes require the caller to be an
|
||||
* admin of the selected company.
|
||||
* @param {import('express').Application} app
|
||||
* @param {string} [basePath='']
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @openapi
|
||||
* /autoinvite:
|
||||
* get:
|
||||
* tags: [AutoInvite]
|
||||
* summary: List auto-invitations
|
||||
* description: Lists pending auto-invitations of the selected company. Admin only.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: autoinvite/list.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Pending invitations
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* autoinvites:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/AutoInvite'
|
||||
* 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 (admin of the selected company required)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
// #region GET /autoinvite
|
||||
|
||||
app.get(`${basePath}/autoinvite`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
try {
|
||||
const autoinvites = await db.list(req.user.id);
|
||||
res.status(200).json({ autoinvites });
|
||||
} catch (error) {
|
||||
const status = autoinviteErrorStatus(error);
|
||||
if (status === 500) console.error('List auto-invitations failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /autoinvite:
|
||||
* post:
|
||||
* tags: [AutoInvite]
|
||||
* summary: Create auto-invitation
|
||||
* description: |
|
||||
* Registers an email for automatic membership. When a user with this email registers,
|
||||
* the invitation is removed from the list and the user is added to the company with the given role.
|
||||
* Emails that are already registered are rejected (409); use `POST /companyuser` for those. Admin only.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-signals: [autoinvite]
|
||||
* x-markdown: autoinvite/create.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [email]
|
||||
* properties:
|
||||
* email:
|
||||
* type: string
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [admin, user]
|
||||
* default: user
|
||||
* example:
|
||||
* email: newhire@example.com
|
||||
* role: user
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Created invitation
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* autoinvite:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* email:
|
||||
* type: string
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [admin, user]
|
||||
* 400:
|
||||
* description: No company selected, invalid email or invalid role
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized (admin of the selected company required)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 409:
|
||||
* description: User already registered or invitation already exists
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
// #region POST /autoinvite
|
||||
|
||||
app.post(`${basePath}/autoinvite`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
try {
|
||||
const autoinvite = await db.add(req.user.id, {
|
||||
email: req.body?.email,
|
||||
role: req.body?.role,
|
||||
});
|
||||
emit('autoinvite', { companyId: selected.companyId });
|
||||
res.status(201).json({ autoinvite });
|
||||
} catch (error) {
|
||||
const status = autoinviteErrorStatus(error);
|
||||
if (status === 500) console.error('Create auto-invitation failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /autoinvite/{id}:
|
||||
* delete:
|
||||
* tags: [AutoInvite]
|
||||
* summary: Delete auto-invitation
|
||||
* description: Deletes a pending auto-invitation of the selected company. Admin only.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-signals: [autoinvite]
|
||||
* x-markdown: autoinvite/delete.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Deleted
|
||||
* 400:
|
||||
* description: Invalid id or 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 (admin of the selected company required)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Invitation not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
// #region DELETE /autoinvite/:id
|
||||
|
||||
app.delete(`${basePath}/autoinvite/:id`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
const parsedId = parsePositiveInt(req.params.id);
|
||||
if (parsedId.error) {
|
||||
return res.status(400).json({ error: parsedId.error });
|
||||
}
|
||||
|
||||
try {
|
||||
await db.remove(req.user.id, parsedId.id);
|
||||
emit('autoinvite', { companyId: selected.companyId });
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
const status = autoinviteErrorStatus(error);
|
||||
if (status === 500) console.error('Delete auto-invitation failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// #endregion
|
||||
}
|
||||
385
src/api/routes/companyuser.js
Normal file
385
src/api/routes/companyuser.js
Normal file
@@ -0,0 +1,385 @@
|
||||
import * as db from '../../db/companyuser.js';
|
||||
import { clearCachedUser } from '../middleware/auth.js';
|
||||
import { emit } from '../sse.js';
|
||||
|
||||
// #region Helpers
|
||||
|
||||
function requireSelectedCompany(req) {
|
||||
const companyId = req.user?.selectedCompanyId;
|
||||
if (companyId == null) {
|
||||
return { error: 'No company selected' };
|
||||
}
|
||||
return { companyId };
|
||||
}
|
||||
|
||||
function parsePositiveInt(val, field = 'id') {
|
||||
const id = Number(val);
|
||||
if (!Number.isInteger(id) || id <= 0) {
|
||||
return { error: `${field} is required` };
|
||||
}
|
||||
return { id };
|
||||
}
|
||||
|
||||
function companyuserErrorStatus(error) {
|
||||
if (error.message === 'No company selected') return 400;
|
||||
if (error.message === 'Not authorized') return 403;
|
||||
if (error.message === 'Company not found') return 404;
|
||||
if (error.message === 'Company user not found') return 404;
|
||||
if (error.message === 'User not found') return 404;
|
||||
if (error.message === 'Invalid role') return 400;
|
||||
if (error.message === 'Invalid email') return 400;
|
||||
if (error.message === 'User already in company') return 409;
|
||||
if (error.message === 'Cannot demote the last admin') return 409;
|
||||
if (error.message === 'Cannot remove the last admin') return 409;
|
||||
return 500;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* Registers company user (membership) routes. All routes require the caller
|
||||
* to be an admin of the selected company.
|
||||
* @param {import('express').Application} app
|
||||
* @param {string} [basePath='']
|
||||
*/
|
||||
export function register(app, basePath = '') {
|
||||
/**
|
||||
* @openapi
|
||||
* /companyuser:
|
||||
* get:
|
||||
* tags: [CompanyUser]
|
||||
* summary: List company users
|
||||
* description: Lists the members of the selected company with their role. Admin only.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-markdown: companyuser/list.md
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Company members
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* companyusers:
|
||||
* type: array
|
||||
* items:
|
||||
* $ref: '#/components/schemas/CompanyUser'
|
||||
* 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 (admin of the selected company required)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
// #region GET /companyuser
|
||||
|
||||
app.get(`${basePath}/companyuser`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
try {
|
||||
const companyusers = await db.list(req.user.id);
|
||||
res.status(200).json({ companyusers });
|
||||
} catch (error) {
|
||||
const status = companyuserErrorStatus(error);
|
||||
if (status === 500) console.error('List company users failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /companyuser:
|
||||
* post:
|
||||
* tags: [CompanyUser]
|
||||
* summary: Add an existing user to the company
|
||||
* description: |
|
||||
* Adds an existing (registered) user, identified by email, to the selected company. Admin only.
|
||||
* The added user can then switch to this company via `PUT /auth/selected-company`.
|
||||
* For emails that are not registered yet use `POST /autoinvite`.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-signals: [companyuser]
|
||||
* x-markdown: companyuser/create.md
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [email]
|
||||
* properties:
|
||||
* email:
|
||||
* type: string
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [admin, user]
|
||||
* default: user
|
||||
* example:
|
||||
* email: colleague@example.com
|
||||
* role: user
|
||||
* responses:
|
||||
* 201:
|
||||
* description: Created company user
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* companyuser:
|
||||
* $ref: '#/components/schemas/CompanyUser'
|
||||
* 400:
|
||||
* description: No company selected, invalid email or invalid role
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 401:
|
||||
* description: Unauthorized
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 403:
|
||||
* description: Not authorized (admin of the selected company required)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: No registered user with this email
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 409:
|
||||
* description: User already in company
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
// #region POST /companyuser
|
||||
|
||||
app.post(`${basePath}/companyuser`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
|
||||
try {
|
||||
const companyuser = await db.addByEmail(req.user.id, {
|
||||
email: req.body?.email,
|
||||
role: req.body?.role,
|
||||
});
|
||||
emit('companyuser', { companyId: selected.companyId });
|
||||
res.status(201).json({ companyuser });
|
||||
} catch (error) {
|
||||
const status = companyuserErrorStatus(error);
|
||||
if (status === 500) console.error('Add company user failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /companyuser/{id}:
|
||||
* patch:
|
||||
* tags: [CompanyUser]
|
||||
* summary: Change a company user's role
|
||||
* description: Changes the role of a member of the selected company. The last admin cannot be demoted. Admin only.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-signals: [companyuser]
|
||||
* x-markdown: companyuser/update.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* requestBody:
|
||||
* required: true
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* required: [role]
|
||||
* properties:
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [admin, user]
|
||||
* responses:
|
||||
* 200:
|
||||
* description: Updated company user
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* type: object
|
||||
* properties:
|
||||
* companyuser:
|
||||
* type: object
|
||||
* properties:
|
||||
* id:
|
||||
* type: integer
|
||||
* googleAuthId:
|
||||
* type: integer
|
||||
* role:
|
||||
* type: string
|
||||
* enum: [admin, user]
|
||||
* 400:
|
||||
* description: Invalid id, role or 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 (admin of the selected company required)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Company user not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 409:
|
||||
* description: Cannot demote the last admin
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
// #region PATCH /companyuser/:id
|
||||
|
||||
app.patch(`${basePath}/companyuser/:id`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
const parsedId = parsePositiveInt(req.params.id);
|
||||
if (parsedId.error) {
|
||||
return res.status(400).json({ error: parsedId.error });
|
||||
}
|
||||
|
||||
try {
|
||||
const companyuser = await db.setRole(req.user.id, parsedId.id, req.body?.role);
|
||||
clearCachedUser(companyuser.googleAuthId);
|
||||
emit('companyuser', { companyId: selected.companyId });
|
||||
res.status(200).json({ companyuser });
|
||||
} catch (error) {
|
||||
const status = companyuserErrorStatus(error);
|
||||
if (status === 500) console.error('Update company user failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// #endregion
|
||||
|
||||
/**
|
||||
* @openapi
|
||||
* /companyuser/{id}:
|
||||
* delete:
|
||||
* tags: [CompanyUser]
|
||||
* summary: Remove a user from the company
|
||||
* description: |
|
||||
* Removes a member from the selected company. History entries stay but lose their author link;
|
||||
* if the removed user had this company selected, their selection is cleared.
|
||||
* The last admin cannot be removed. Admin only.
|
||||
* security:
|
||||
* - cookieAuth: []
|
||||
* x-signals: [companyuser]
|
||||
* x-markdown: companyuser/delete.md
|
||||
* parameters:
|
||||
* - in: path
|
||||
* name: id
|
||||
* required: true
|
||||
* schema:
|
||||
* type: integer
|
||||
* responses:
|
||||
* 204:
|
||||
* description: Removed
|
||||
* 400:
|
||||
* description: Invalid id or 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 (admin of the selected company required)
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 404:
|
||||
* description: Company user not found
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
* 409:
|
||||
* description: Cannot remove the last admin
|
||||
* content:
|
||||
* application/json:
|
||||
* schema:
|
||||
* $ref: '#/components/schemas/Error'
|
||||
*/
|
||||
// #region DELETE /companyuser/:id
|
||||
|
||||
app.delete(`${basePath}/companyuser/:id`, async (req, res) => {
|
||||
const selected = requireSelectedCompany(req);
|
||||
if (selected.error) {
|
||||
return res.status(400).json({ error: selected.error });
|
||||
}
|
||||
const parsedId = parsePositiveInt(req.params.id);
|
||||
if (parsedId.error) {
|
||||
return res.status(400).json({ error: parsedId.error });
|
||||
}
|
||||
|
||||
try {
|
||||
const removed = await db.remove(req.user.id, parsedId.id);
|
||||
clearCachedUser(removed.googleAuthId);
|
||||
emit('companyuser', { companyId: selected.companyId });
|
||||
res.sendStatus(204);
|
||||
} catch (error) {
|
||||
const status = companyuserErrorStatus(error);
|
||||
if (status === 500) console.error('Remove company user failed:', error);
|
||||
res.status(status).json({ error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
// #endregion
|
||||
}
|
||||
@@ -57,6 +57,8 @@ export function register(app, basePath = '') {
|
||||
* | `printer` | `{}` | `POST /printer`, `PUT /printer/{id}`, `PATCH /printer/{id}`, `DELETE /printer/{id}` |
|
||||
* | `printeragent` | `{}` | `PUT /printeragent`, `PATCH /printeragent/{id}`, `DELETE /printeragent/{id}`, printer agent connect/disconnect/state |
|
||||
* | `agentprinter` | `{ printers }` or `{}` | `PATCH /agentprinter/{id}`, `PUT /agentprinter/order`, `DELETE /agentprinter/{id}`, agent state updates / printer discovery |
|
||||
* | `companyuser` | `{}` | `POST /companyuser`, `PATCH /companyuser/{id}`, `DELETE /companyuser/{id}` |
|
||||
* | `autoinvite` | `{}` | `POST /autoinvite`, `DELETE /autoinvite/{id}` |
|
||||
* | `warehouse` | `{}` | `POST /warehouse`, `PUT /warehouse/{id}`, `PATCH /warehouse/{id}`, `DELETE /warehouse/{id}` |
|
||||
* | `supplier` | `{}` | `POST /supplier`, `PATCH /supplier/{id}`, `DELETE /supplier/{id}` |
|
||||
* | `vfs` | `{}` | VFS uploads and filesystem mutations such as `POST /vfs/upload`, `POST /vfs/mkdir`, `POST /vfs/rename`, `PUT /vfs/{*path}`, `DELETE /vfs/{*path}` |
|
||||
@@ -95,6 +97,10 @@ export function register(app, basePath = '') {
|
||||
* payload: "{}"
|
||||
* - name: propertyname
|
||||
* payload: "{}"
|
||||
* - name: companyuser
|
||||
* payload: "{}"
|
||||
* - name: autoinvite
|
||||
* payload: "{}"
|
||||
* - name: labeltemplate
|
||||
* payload: "{}"
|
||||
* - name: printer
|
||||
|
||||
16
src/db/autoinvite.js
Normal file
16
src/db/autoinvite.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import { getStore } from './store.js';
|
||||
|
||||
export async function list(googleAuthId) {
|
||||
const store = await getStore();
|
||||
return store.autoinvite.list(googleAuthId);
|
||||
}
|
||||
|
||||
export async function add(googleAuthId, data) {
|
||||
const store = await getStore();
|
||||
return store.autoinvite.add(googleAuthId, data);
|
||||
}
|
||||
|
||||
export async function remove(googleAuthId, id) {
|
||||
const store = await getStore();
|
||||
return store.autoinvite.remove(googleAuthId, id);
|
||||
}
|
||||
21
src/db/companyuser.js
Normal file
21
src/db/companyuser.js
Normal file
@@ -0,0 +1,21 @@
|
||||
import { getStore } from './store.js';
|
||||
|
||||
export async function list(googleAuthId) {
|
||||
const store = await getStore();
|
||||
return store.companyuser.list(googleAuthId);
|
||||
}
|
||||
|
||||
export async function addByEmail(googleAuthId, data) {
|
||||
const store = await getStore();
|
||||
return store.companyuser.addByEmail(googleAuthId, data);
|
||||
}
|
||||
|
||||
export async function setRole(googleAuthId, id, role) {
|
||||
const store = await getStore();
|
||||
return store.companyuser.setRole(googleAuthId, id, role);
|
||||
}
|
||||
|
||||
export async function remove(googleAuthId, id) {
|
||||
const store = await getStore();
|
||||
return store.companyuser.remove(googleAuthId, id);
|
||||
}
|
||||
@@ -14,6 +14,7 @@ let schemaVersion;
|
||||
|
||||
const STORE_NAMESPACES = [
|
||||
'companyuser',
|
||||
'autoinvite',
|
||||
'company',
|
||||
'category',
|
||||
'article',
|
||||
|
||||
Reference in New Issue
Block a user