Compare commits
137 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
43a9e73d33 | |
|
|
ee4618c67b | |
|
|
aefa2ae0d7 | |
|
|
0cff7d753a | |
|
|
02cb4ab656 | |
|
|
46ea2de206 | |
|
|
dfe36e2614 | |
|
|
6041e355a5 | |
|
|
1a81dccadd | |
|
|
8cd8f05054 | |
|
|
3a6da7c807 | |
|
|
035bfa3731 | |
|
|
6fec8bbd1a | |
|
|
c4d0ec6be0 | |
|
|
4452cb071d | |
|
|
734ca7623d | |
|
|
aa7ae6520f | |
|
|
554391e7fd | |
|
|
7e9242c005 | |
|
|
6ecf9993c7 | |
|
|
1149a20b89 | |
|
|
fcd50df49d | |
|
|
93cb4ba102 | |
|
|
2f53cb4b67 | |
|
|
259aacca67 | |
|
|
62faaf6384 | |
|
|
910b61cd81 | |
|
|
cdf82f4d06 | |
|
|
2e6c97b775 | |
|
|
34303af9fc | |
|
|
3a72c79cd8 | |
|
|
99a189534b | |
|
|
600a010ee9 | |
|
|
de29bb6a3a | |
|
|
30cd9bae25 | |
|
|
429ceeea82 | |
|
|
e5d05e998d | |
|
|
35b86a224a | |
|
|
8b0e00dca8 | |
|
|
ac27d5ac61 | |
|
|
d30cbc5ec7 | |
|
|
93e9063959 | |
|
|
2ec5507c1a | |
|
|
78b5eb4721 | |
|
|
37cbfa0cf2 | |
|
|
52ca67b4e3 | |
|
|
cd34cdc597 | |
|
|
fd5b69feed | |
|
|
5ff6a49819 | |
|
|
958f556307 | |
|
|
dc928d32c9 | |
|
|
c4cf6e8741 | |
|
|
b2b8c62389 | |
|
|
ea1750ecd8 | |
|
|
5867b26997 | |
|
|
eb1948d817 | |
|
|
cb0d700a24 | |
|
|
1c5961a75c | |
|
|
d91b01fee7 | |
|
|
705210a5cc | |
|
|
bc6ddd90a2 | |
|
|
d8c02b8917 | |
|
|
42fb2eab80 | |
|
|
026576eeed | |
|
|
c8f1e568d8 | |
|
|
cc02865318 | |
|
|
9424f35c2d | |
|
|
53839265b6 | |
|
|
558d075931 | |
|
|
ea04e3ba19 | |
|
|
816c0649b9 | |
|
|
7ec1355a3b | |
|
|
8865a4ef8a | |
|
|
1f81207902 | |
|
|
52663e5b14 | |
|
|
aae365f4f3 | |
|
|
78d9f512f6 | |
|
|
77f7a76cd6 | |
|
|
529d7d0bde | |
|
|
8633bf3e53 | |
|
|
703e05e33d | |
|
|
3add883c25 | |
|
|
2542eb4968 | |
|
|
1b916fa7e9 | |
|
|
901375c4cc | |
|
|
f1adb7f0ae | |
|
|
2e298bf267 | |
|
|
3cb28232a6 | |
|
|
4e046647c7 | |
|
|
5e4d73b44d | |
|
|
82a982ebf9 | |
|
|
1fdb60b045 | |
|
|
6366cd60f3 | |
|
|
5423933d3e | |
|
|
61362d9448 | |
|
|
ebf9d0d520 | |
|
|
d6758f478f | |
|
|
5978b40989 | |
|
|
8dd3a112f7 | |
|
|
40d1c34e86 | |
|
|
ecd4192aed | |
|
|
af609f5c10 | |
|
|
4cc48686d5 | |
|
|
f7721dd41b | |
|
|
5d891c2c6d | |
|
|
4a64d03056 | |
|
|
5f31e4ff45 | |
|
|
43b547e044 | |
|
|
264736422f | |
|
|
813ff58daa | |
|
|
8c6f68eb2b | |
|
|
a549db6035 | |
|
|
acfa6b7b5b | |
|
|
ae8e504eaa | |
|
|
407384a75a | |
|
|
4a4e4beb78 | |
|
|
2d15639124 | |
|
|
08cebebd02 | |
|
|
9ceb480c23 | |
|
|
94704d9e4a | |
|
|
ac6d3b3d65 | |
|
|
568cfe243e | |
|
|
a467bcebb7 | |
|
|
b164d6e00b | |
|
|
97c2ed46bf | |
|
|
ba001cc04b | |
|
|
d2b702cb11 | |
|
|
f4fd2bab9a | |
|
|
76f510e84d | |
|
|
73c0a45ce7 | |
|
|
a954ced804 | |
|
|
1a2c3087e3 | |
|
|
38d541c18d | |
|
|
422f37747d | |
|
|
141ce9f5a8 | |
|
|
ec09fd98e4 | |
|
|
c2c50e5899 |
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
target
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
LibreCCM API Client Commons
|
||||||
|
===========================
|
||||||
|
|
||||||
|
This module provides several usaable parts of building clients for
|
||||||
|
specific endpoints of the LibreCCM RESTful API.
|
||||||
|
|
||||||
|
The `entities` module provides several entities used by various endpoints
|
||||||
|
of the RESTful API of LibreCCM.
|
||||||
|
|
||||||
|
The module also provides an HTTP client specially tailored for
|
||||||
|
the RESTful API of LibreCCM. The client is provided in several implementations:
|
||||||
|
For browsers, for node.js and an isomorphic implementation. In most cases
|
||||||
|
you will want to use the isomorphic implementation.
|
||||||
|
|
||||||
|
For most use cases it is recommanded to use the specific client APIs provided
|
||||||
|
by the LibreCCM modules which provided a more high-level interface to the RESTful
|
||||||
|
API of LibreCCM.
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "@libreccm/ccm-apiclient-commons",
|
||||||
|
"version": "7.0.0",
|
||||||
|
"main": "target/dist/ccm-apiclient-commons.js",
|
||||||
|
"types": "dist/ccm-apiclient-commons.d.ts",
|
||||||
|
"description": "Client for the RESTful API provided by ccm-core.",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/libreccm/libreccm/ccm-apiclient-commons"
|
||||||
|
},
|
||||||
|
"author": "Jens Pelzetter",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"check": "eslint",
|
||||||
|
"doc": "typedoc --out target/docs src/main/typescript"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^12.12.50",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^3.6.0",
|
||||||
|
"@typescript-eslint/parser": "^3.6.0",
|
||||||
|
"eslint": "^7.4.0",
|
||||||
|
"typedoc": "^0.17.8",
|
||||||
|
"typescript": "^3.9.6"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.libreccm</groupId>
|
||||||
|
<artifactId>libreccm-parent</artifactId>
|
||||||
|
<version>7.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>org.libreccm</groupId>
|
||||||
|
<artifactId>ccm-apiclient-commons</artifactId>
|
||||||
|
<version>7.0.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>LibreCCM API Client Commons</name>
|
||||||
|
<description>Provides basic classes and common datatypes for RESTful API clients</description>
|
||||||
|
<url>https://www.libreccm.org/ccm-apiclient-commons</url>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Lesser GPL 2.1</name>
|
||||||
|
<url>http://www.gnu.org/licenses/old-licenses/lgpl-2.1</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>ccm-apiclient-commons</finalName>
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/typescript</directory>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.build.directory}/generated-resources</directory>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.eirslett</groupId>
|
||||||
|
<artifactId>frontend-maven-plugin</artifactId>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<installDirectory>../node</installDirectory>
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>Install node.js and NPM</id>
|
||||||
|
<goals>
|
||||||
|
<goal>install-node-and-npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<nodeVersion>${nodeVersion}</nodeVersion>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>npm install</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>install</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>build</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>run build</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
|
||||||
|
<execution>
|
||||||
|
<id>npm publish</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
|
||||||
|
<phase>deploy</phase>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<arguments>publish --userconfig ../libreccm.npmrc</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,895 @@
|
||||||
|
import * as http from "http";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The interface of a client for the LibreCcm RESTful API.
|
||||||
|
*/
|
||||||
|
export interface LibreCcmApiClient {
|
||||||
|
/**
|
||||||
|
* Performs a `GET` request for the provided endpoint.
|
||||||
|
* Implementations should throw an {@link ApiError} if an error
|
||||||
|
* occurs.
|
||||||
|
*
|
||||||
|
* @param endpoint The API endpoint to use.
|
||||||
|
* @param searchParams Optional search parameters.
|
||||||
|
*/
|
||||||
|
get(
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse>;
|
||||||
|
/**
|
||||||
|
* Performs a `POST` request for the provided endpoint.
|
||||||
|
* Implementations should throw an {@link ApiError} if an error
|
||||||
|
* occurs.
|
||||||
|
*
|
||||||
|
* @param endpoint The API endpoint to use.
|
||||||
|
* @param body The request body.
|
||||||
|
* @param searchParams Optional search parameters.
|
||||||
|
*/
|
||||||
|
post(
|
||||||
|
endpoint: string,
|
||||||
|
body?: RequestBody,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse>;
|
||||||
|
/**
|
||||||
|
* Performs a `PUT` request for the provided endpoint.
|
||||||
|
* Implementations should throw an {@link ApiError} if an error
|
||||||
|
* occurs.
|
||||||
|
*
|
||||||
|
* @param endpoint The API endpoint to use.
|
||||||
|
* @param body The request body.
|
||||||
|
* @param searchParams Optional search parameters.
|
||||||
|
*/
|
||||||
|
put(
|
||||||
|
endpoint: string,
|
||||||
|
body?: RequestBody,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse>;
|
||||||
|
/**
|
||||||
|
* Performs a `DELETE` request for the provided endpoint.
|
||||||
|
* Implementations should throw an {@link ApiError} if an error
|
||||||
|
* occurs.
|
||||||
|
*
|
||||||
|
* @param endpoint The API endpoint to use.
|
||||||
|
* @param searchParams Optional search parameters.
|
||||||
|
*/
|
||||||
|
delete(
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse>;
|
||||||
|
/**
|
||||||
|
* Performs a `HEAD` request for the provided endpoint.
|
||||||
|
* Implementations should throw an {@link ApiError} if an error
|
||||||
|
* occurs.
|
||||||
|
*
|
||||||
|
* @param endpoint The API endpoint to use.
|
||||||
|
*/
|
||||||
|
head(endpoint: string): Promise<ApiResponse>;
|
||||||
|
/**
|
||||||
|
* Performs a `OPTIONS` request for the provided endpoint.
|
||||||
|
* Implementations should throw an {@link ApiError} if an error
|
||||||
|
* occurs.
|
||||||
|
*
|
||||||
|
* @param endpoint The API endpoint to use.
|
||||||
|
*/
|
||||||
|
options(endpoint: string): Promise<ApiResponse>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RequestBody =
|
||||||
|
| Record<string, unknown>
|
||||||
|
| ArrayBuffer
|
||||||
|
| string
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The response from the API.
|
||||||
|
*/
|
||||||
|
export interface ApiResponse {
|
||||||
|
/**
|
||||||
|
* Indicates that the request was processed successfully.
|
||||||
|
*/
|
||||||
|
ok: boolean;
|
||||||
|
/**
|
||||||
|
* The HTTP status code returned by the RESTful API.
|
||||||
|
*/
|
||||||
|
status: number;
|
||||||
|
/**
|
||||||
|
* The status text returned by the RESTful API.
|
||||||
|
*/
|
||||||
|
statusText: string;
|
||||||
|
/**
|
||||||
|
* Gets the Response Body as JSON.
|
||||||
|
*/
|
||||||
|
json(): Promise<unknown>;
|
||||||
|
/**
|
||||||
|
* Gets the Response Body as ArrayBuffer.
|
||||||
|
*/
|
||||||
|
arrayBuffer(): Promise<ArrayBuffer>;
|
||||||
|
/**
|
||||||
|
* Gets the Response Body as `string`.
|
||||||
|
*/
|
||||||
|
text(): Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ApiError extends Error {
|
||||||
|
/**
|
||||||
|
* The HTTP status code reported by the API. `-1` if no status is available.
|
||||||
|
*/
|
||||||
|
status: number;
|
||||||
|
/**
|
||||||
|
* The status text reported by the API.
|
||||||
|
*/
|
||||||
|
statusText: string;
|
||||||
|
/**
|
||||||
|
* The HTTP method used for the failed request.
|
||||||
|
*/
|
||||||
|
method: "get" | "post" | "put" | "delete" | "head" | "options";
|
||||||
|
/**
|
||||||
|
* The URL used in the failed request.
|
||||||
|
*/
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
status: number,
|
||||||
|
statusText: string,
|
||||||
|
method: "get" | "post" | "put" | "delete" | "head" | "options",
|
||||||
|
message: string,
|
||||||
|
url: string
|
||||||
|
) {
|
||||||
|
super(message);
|
||||||
|
this.status = status;
|
||||||
|
this.statusText = statusText;
|
||||||
|
this.method = method;
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper function for building an URL. Because it might be useful for implementing
|
||||||
|
* clients for specific APIs in the exported from this module.
|
||||||
|
*
|
||||||
|
* @param base The base URL pointing the an LibreCCM installation.
|
||||||
|
* @param endpoint The endpoint to address.
|
||||||
|
* @param searchParams Optional search parameter to append to the URL.
|
||||||
|
*/
|
||||||
|
export function buildUrl(
|
||||||
|
base: string,
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): string {
|
||||||
|
const url = new URL(base);
|
||||||
|
url.pathname = endpoint;
|
||||||
|
if (searchParams) {
|
||||||
|
const urlSearchParams: URLSearchParams = new URLSearchParams();
|
||||||
|
for (const key in searchParams) {
|
||||||
|
urlSearchParams.append(key, searchParams[key]);
|
||||||
|
}
|
||||||
|
url.search = urlSearchParams.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return url.href;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFetchAvailable(): boolean {
|
||||||
|
if (!fetch) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the {@link ApiResponse} for the Fetch-API supported
|
||||||
|
* by browsers.
|
||||||
|
*/
|
||||||
|
class FetchResponse implements ApiResponse {
|
||||||
|
readonly ok: boolean;
|
||||||
|
readonly status: number;
|
||||||
|
readonly statusText: string;
|
||||||
|
#response: Response;
|
||||||
|
|
||||||
|
constructor(response: Response) {
|
||||||
|
this.status = response.status;
|
||||||
|
this.statusText = response.statusText;
|
||||||
|
this.ok = response.ok;
|
||||||
|
this.#response = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
json(): Promise<unknown> {
|
||||||
|
return this.#response.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayBuffer(): Promise<ArrayBuffer> {
|
||||||
|
return this.#response.arrayBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
text(): Promise<string> {
|
||||||
|
return this.#response.text();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the {@link LibreCcmApiClient} interface
|
||||||
|
* using the Fetch-API supported by browsers.
|
||||||
|
*/
|
||||||
|
export class ApiClientFetchImpl implements LibreCcmApiClient {
|
||||||
|
readonly #baseUrl: string;
|
||||||
|
readonly #fetchOptions: RequestInit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param baseUrl The URL of the LibreCCM installation to use.
|
||||||
|
* @param fetchOptions Basic fetch options for requests.
|
||||||
|
*/
|
||||||
|
constructor(baseUrl: string, fetchOptions: RequestInit) {
|
||||||
|
this.#baseUrl = baseUrl;
|
||||||
|
this.#fetchOptions = fetchOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildFetchOptions(
|
||||||
|
method: "get" | "post" | "put" | "delete" | "head" | "options",
|
||||||
|
body?: RequestBody
|
||||||
|
): RequestInit {
|
||||||
|
const fetchOptions: RequestInit = {
|
||||||
|
...this.#fetchOptions,
|
||||||
|
};
|
||||||
|
fetchOptions.method = method;
|
||||||
|
if (body) {
|
||||||
|
if (body instanceof ArrayBuffer) {
|
||||||
|
fetchOptions.body = body;
|
||||||
|
} else if (typeof body === "string") {
|
||||||
|
fetchOptions.body = body;
|
||||||
|
} else {
|
||||||
|
fetchOptions.body = JSON.stringify(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetchOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, this.buildFetchOptions("get"));
|
||||||
|
if (response.ok) {
|
||||||
|
return new FetchResponse(response);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
"API responded with an error",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"get",
|
||||||
|
"`Failed to execute get: ${err}`",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async post(
|
||||||
|
endpoint: string,
|
||||||
|
body?: RequestBody,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
url,
|
||||||
|
this.buildFetchOptions("post", body)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return new FetchResponse(response);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
"API responded with an error",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"get",
|
||||||
|
`Failed to execute get: ${err}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async put(
|
||||||
|
endpoint: string,
|
||||||
|
body?: RequestBody,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
url,
|
||||||
|
this.buildFetchOptions("put", body)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return new FetchResponse(response);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
"API responded with an error.",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"get",
|
||||||
|
`Failed to execute get: ${err}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, this.buildFetchOptions("delete"));
|
||||||
|
if (response.ok) {
|
||||||
|
return new FetchResponse(response);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
"API responded with an error.",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"get",
|
||||||
|
`Failed to execute get: ${err}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async head(endpoint: string): Promise<ApiResponse> {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint);
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, this.buildFetchOptions("head"));
|
||||||
|
if (response.ok) {
|
||||||
|
return new FetchResponse(response);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
"API responded with an error.",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"get",
|
||||||
|
`Failed to execute get: ${err}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async options(endpoint: string): Promise<ApiResponse> {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint);
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
url,
|
||||||
|
this.buildFetchOptions("options")
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return new FetchResponse(response);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
"API responded with an error.",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
throw new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"get",
|
||||||
|
`Failed to execute get: ${err}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the {@link ApiResponse} for the Node.js HTTP API supported
|
||||||
|
* by browsers.
|
||||||
|
*/
|
||||||
|
class NodeResponse implements ApiResponse {
|
||||||
|
readonly ok: boolean;
|
||||||
|
readonly status: number;
|
||||||
|
readonly statusText: string;
|
||||||
|
|
||||||
|
#data: Buffer | null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
ok: boolean,
|
||||||
|
status: number,
|
||||||
|
statusText: string,
|
||||||
|
data: Buffer | null
|
||||||
|
) {
|
||||||
|
this.ok = ok;
|
||||||
|
this.status = status;
|
||||||
|
this.statusText = statusText;
|
||||||
|
this.#data = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
json(): Promise<unknown> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (this.#data) {
|
||||||
|
resolve(this.#data.toJSON());
|
||||||
|
} else {
|
||||||
|
resolve({});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayBuffer(): Promise<ArrayBuffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (this.#data) {
|
||||||
|
const arrayBuf: ArrayBuffer = this.#data.buffer.slice(
|
||||||
|
this.#data.byteOffset,
|
||||||
|
this.#data.byteLength + this.#data.byteLength
|
||||||
|
);
|
||||||
|
resolve(arrayBuf);
|
||||||
|
} else {
|
||||||
|
resolve(new ArrayBuffer(0));
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
text(): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
if (this.#data) {
|
||||||
|
resolve(this.#data.toString());
|
||||||
|
} else {
|
||||||
|
resolve("");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An implementation of the {@link LibreCcmApiClient} interface using the HTTP API
|
||||||
|
* provided by node.js.
|
||||||
|
*/
|
||||||
|
export class ApiClientNodeImpl implements LibreCcmApiClient {
|
||||||
|
readonly #baseUrl: string;
|
||||||
|
readonly #jwt: string;
|
||||||
|
|
||||||
|
constructor(baseUrl: string, jwt: string) {
|
||||||
|
this.#baseUrl = baseUrl;
|
||||||
|
this.#jwt = jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||||
|
const request: http.ClientRequest = http.request(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.#jwt,
|
||||||
|
},
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("response", (response) => {
|
||||||
|
const status: number = response.statusCode
|
||||||
|
? response.statusCode
|
||||||
|
: -1;
|
||||||
|
const statusText: string = response.statusMessage
|
||||||
|
? response.statusMessage
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const data: Array<Buffer> = [];
|
||||||
|
|
||||||
|
response.on("data", (chunk: Buffer) => {
|
||||||
|
data.push(chunk);
|
||||||
|
});
|
||||||
|
response.on("end", () => {
|
||||||
|
const buffer: Buffer = Buffer.concat(data);
|
||||||
|
const apiResponse: ApiResponse = new NodeResponse(
|
||||||
|
status === 200,
|
||||||
|
status,
|
||||||
|
statusText,
|
||||||
|
buffer
|
||||||
|
);
|
||||||
|
resolve(apiResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
request.on("error", (error) =>
|
||||||
|
reject(
|
||||||
|
new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"get",
|
||||||
|
`Failed to do GET: ${error}`,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
post(
|
||||||
|
endpoint: string,
|
||||||
|
body?: ArrayBuffer,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||||
|
const request: http.ClientRequest = http.request(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.#jwt,
|
||||||
|
},
|
||||||
|
method: "POST",
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("response", (response) => {
|
||||||
|
const status: number = response.statusCode
|
||||||
|
? response.statusCode
|
||||||
|
: -1;
|
||||||
|
const statusText: string = response.statusMessage
|
||||||
|
? response.statusMessage
|
||||||
|
: "";
|
||||||
|
|
||||||
|
response.on("end", () => {
|
||||||
|
const apiResponse: ApiResponse = new NodeResponse(
|
||||||
|
status === 200 || status === 201,
|
||||||
|
status,
|
||||||
|
statusText,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
resolve(apiResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("error", (error) =>
|
||||||
|
reject(
|
||||||
|
new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"post",
|
||||||
|
`Failed to do POST: ${error}`,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
request.write(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
put(
|
||||||
|
endpoint: string,
|
||||||
|
body?: ArrayBuffer,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||||
|
const request: http.ClientRequest = http.request(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.#jwt,
|
||||||
|
},
|
||||||
|
method: "PUT",
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("response", (response) => {
|
||||||
|
const status: number = response.statusCode
|
||||||
|
? response.statusCode
|
||||||
|
: -1;
|
||||||
|
const statusText: string = response.statusMessage
|
||||||
|
? response.statusMessage
|
||||||
|
: "";
|
||||||
|
|
||||||
|
response.on("end", () => {
|
||||||
|
const apiResponse: ApiResponse = new NodeResponse(
|
||||||
|
status === 200 || status === 201,
|
||||||
|
status,
|
||||||
|
statusText,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
resolve(apiResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("error", (error) =>
|
||||||
|
reject(
|
||||||
|
new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"put",
|
||||||
|
`Failed to do PUT: ${error}`,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (body) {
|
||||||
|
request.write(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint, searchParams);
|
||||||
|
const request: http.ClientRequest = http.request(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.#jwt,
|
||||||
|
},
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("response", (response) => {
|
||||||
|
const status: number = response.statusCode
|
||||||
|
? response.statusCode
|
||||||
|
: -1;
|
||||||
|
const statusText: string = response.statusMessage
|
||||||
|
? response.statusMessage
|
||||||
|
: "";
|
||||||
|
|
||||||
|
response.on("end", () => {
|
||||||
|
const apiResponse: ApiResponse = new NodeResponse(
|
||||||
|
status === 200 || status === 204,
|
||||||
|
status,
|
||||||
|
statusText,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
resolve(apiResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("error", (error) =>
|
||||||
|
reject(
|
||||||
|
new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"delete",
|
||||||
|
`Failed to do DELETE: ${error}`,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
head(endpoint: string): Promise<ApiResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint);
|
||||||
|
const request: http.ClientRequest = http.request(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.#jwt,
|
||||||
|
},
|
||||||
|
method: "HEAD",
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("response", (response) => {
|
||||||
|
const status: number = response.statusCode
|
||||||
|
? response.statusCode
|
||||||
|
: -1;
|
||||||
|
const statusText: string = response.statusMessage
|
||||||
|
? response.statusMessage
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const data: Array<Buffer> = [];
|
||||||
|
|
||||||
|
response.on("data", (chunk: Buffer) => {
|
||||||
|
data.push(chunk);
|
||||||
|
});
|
||||||
|
response.on("end", () => {
|
||||||
|
const buffer: Buffer = Buffer.concat(data);
|
||||||
|
const apiResponse: ApiResponse = new NodeResponse(
|
||||||
|
status === 200,
|
||||||
|
status,
|
||||||
|
statusText,
|
||||||
|
buffer
|
||||||
|
);
|
||||||
|
resolve(apiResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
request.on("error", (error) =>
|
||||||
|
reject(
|
||||||
|
new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"head",
|
||||||
|
`Failed to do HEAD: ${error}`,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
options(endpoint: string): Promise<ApiResponse> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const url = buildUrl(this.#baseUrl, endpoint);
|
||||||
|
const request: http.ClientRequest = http.request(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: this.#jwt,
|
||||||
|
},
|
||||||
|
method: "OPTIONS",
|
||||||
|
});
|
||||||
|
|
||||||
|
request.on("response", (response) => {
|
||||||
|
const status: number = response.statusCode
|
||||||
|
? response.statusCode
|
||||||
|
: -1;
|
||||||
|
const statusText: string = response.statusMessage
|
||||||
|
? response.statusMessage
|
||||||
|
: "";
|
||||||
|
|
||||||
|
const data: Array<Buffer> = [];
|
||||||
|
|
||||||
|
response.on("data", (chunk: Buffer) => {
|
||||||
|
data.push(chunk);
|
||||||
|
});
|
||||||
|
response.on("end", () => {
|
||||||
|
const buffer: Buffer = Buffer.concat(data);
|
||||||
|
const apiResponse: ApiResponse = new NodeResponse(
|
||||||
|
status === 200,
|
||||||
|
status,
|
||||||
|
statusText,
|
||||||
|
buffer
|
||||||
|
);
|
||||||
|
resolve(apiResponse);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
request.on("error", (error) =>
|
||||||
|
reject(
|
||||||
|
new ApiError(
|
||||||
|
-1,
|
||||||
|
"n/a",
|
||||||
|
"options",
|
||||||
|
`Failed to do OPTIONS: ${error}`,
|
||||||
|
url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
request.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An isomorphic implementation of the {@link LibreCcmApiClient} interface.
|
||||||
|
* Under the hood the implementation checks if the the Fetch-API is avaiable
|
||||||
|
* and used either the {@link ApiClientFetchImpl} or the {@link ApiClientNodeImpl}.
|
||||||
|
*/
|
||||||
|
export class IsomorphicClientImpl implements LibreCcmApiClient {
|
||||||
|
readonly #fetchClient: LibreCcmApiClient;
|
||||||
|
readonly #nodeClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
constructor(fetchClient: LibreCcmApiClient, nodeClient: LibreCcmApiClient) {
|
||||||
|
this.#fetchClient = fetchClient;
|
||||||
|
this.#nodeClient = nodeClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
get(
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
if (isFetchAvailable()) {
|
||||||
|
return this.#fetchClient.get(endpoint, searchParams);
|
||||||
|
} else {
|
||||||
|
return this.#nodeClient.get(endpoint, searchParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
post(
|
||||||
|
endpoint: string,
|
||||||
|
body: ArrayBuffer,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
if (isFetchAvailable()) {
|
||||||
|
return this.#fetchClient.post(endpoint, body, searchParams);
|
||||||
|
} else {
|
||||||
|
return this.#nodeClient.post(endpoint, body, searchParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
put(
|
||||||
|
endpoint: string,
|
||||||
|
body?: ArrayBuffer,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
if (isFetchAvailable()) {
|
||||||
|
return this.#fetchClient.put(endpoint, body, searchParams);
|
||||||
|
} else {
|
||||||
|
return this.#nodeClient.put(endpoint, body, searchParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(
|
||||||
|
endpoint: string,
|
||||||
|
searchParams?: Record<string, string>
|
||||||
|
): Promise<ApiResponse> {
|
||||||
|
if (isFetchAvailable()) {
|
||||||
|
return this.#fetchClient.delete(endpoint, searchParams);
|
||||||
|
} else {
|
||||||
|
return this.#nodeClient.delete(endpoint, searchParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
head(endpoint: string): Promise<ApiResponse> {
|
||||||
|
if (isFetchAvailable()) {
|
||||||
|
return this.#fetchClient.head(endpoint);
|
||||||
|
} else {
|
||||||
|
return this.#nodeClient.head(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
options(endpoint: string): Promise<ApiResponse> {
|
||||||
|
if (isFetchAvailable()) {
|
||||||
|
return this.#fetchClient.options(endpoint);
|
||||||
|
} else {
|
||||||
|
return this.#nodeClient.options(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,189 @@
|
||||||
|
import {
|
||||||
|
LibreCcmApiClient,
|
||||||
|
ApiResponse,
|
||||||
|
ApiError,
|
||||||
|
buildUrl,
|
||||||
|
ApiClientFetchImpl,
|
||||||
|
ApiClientNodeImpl,
|
||||||
|
isFetchAvailable,
|
||||||
|
IsomorphicClientImpl,
|
||||||
|
} from "./ApiClient";
|
||||||
|
|
||||||
|
export * from "./entities";
|
||||||
|
export { LibreCcmApiClient, ApiResponse, ApiError, buildUrl };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build an client for the LibreCCM RESTful API suitable for use in
|
||||||
|
* clientside code served by the Application Server.
|
||||||
|
*
|
||||||
|
* The base URL for accessing the LibreCCM RESTful API is automatically
|
||||||
|
* determined from the URL of the current document. The API client will
|
||||||
|
* use the credentials stored in the browser (cookies) to authenticate itself.
|
||||||
|
*/
|
||||||
|
export function buildEmbeddedApiClient(): LibreCcmApiClient {
|
||||||
|
if (!isFetchAvailable()) {
|
||||||
|
throw new Error(
|
||||||
|
"Fetch API is not available. Please use buildIsomorpicApiClient."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const baseUrl = new URL(document.documentURI);
|
||||||
|
baseUrl.hash = "";
|
||||||
|
baseUrl.password = "";
|
||||||
|
baseUrl.pathname = "";
|
||||||
|
baseUrl.search = "";
|
||||||
|
baseUrl.username = "";
|
||||||
|
|
||||||
|
return new ApiClientFetchImpl(baseUrl.href, {
|
||||||
|
credentials: "include",
|
||||||
|
mode: "same-origin",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a client for the LibreCCM RESTful API suitable for running in a browser.
|
||||||
|
*
|
||||||
|
* @param baseUrl The URL of the LibreCCM installation to access, including the port.
|
||||||
|
* @param jwt The JSON Web Token to use by the client to authenticate itself.
|
||||||
|
*/
|
||||||
|
export function buildRemoteApiClient(
|
||||||
|
baseUrl: string,
|
||||||
|
jwt: string
|
||||||
|
): LibreCcmApiClient {
|
||||||
|
if (!isFetchAvailable()) {
|
||||||
|
throw new Error(
|
||||||
|
"Fetch API is not available. Please use buildIsomorpicApiClient."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ApiClientFetchImpl(baseUrl, {
|
||||||
|
headers: {
|
||||||
|
Authorization: jwt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a client for the LibreCCM RESTful API suitable for running inside a node.js
|
||||||
|
* environment.
|
||||||
|
*
|
||||||
|
* @param baseUrl The URL of the LibreCCM installation to access, including the port.
|
||||||
|
* @param jwt The JSON Web Token to use by the client to authenticate itself.
|
||||||
|
*/
|
||||||
|
export function buildNodeApiClient(
|
||||||
|
baseUrl: string,
|
||||||
|
jwt: string
|
||||||
|
): LibreCcmApiClient {
|
||||||
|
return new ApiClientNodeImpl(baseUrl, jwt);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an isomorphic client for the LibreCCM RESTful API which will work in the
|
||||||
|
* browser and in a node.js environment. Use this function to create an API client
|
||||||
|
* for JavaScript applications which use Server-Side-Rendering.
|
||||||
|
*
|
||||||
|
* @param baseUrl The URL of the LibreCCM installation to access, including the port.
|
||||||
|
* @param jwt The JSON Web Token to use by the client to authenticate itself.
|
||||||
|
*/
|
||||||
|
export function buildIsomorpicApiClient(
|
||||||
|
baseUrl: string,
|
||||||
|
jwt: string
|
||||||
|
): LibreCcmApiClient {
|
||||||
|
const fetchClient: LibreCcmApiClient = buildRemoteApiClient(baseUrl, jwt);
|
||||||
|
const nodeClient: LibreCcmApiClient = buildNodeApiClient(baseUrl, jwt);
|
||||||
|
return new IsomorphicClientImpl(fetchClient, nodeClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility function for checking if an record has specific properties.
|
||||||
|
*
|
||||||
|
* @param record The record to check.
|
||||||
|
* @param properties The required properties.
|
||||||
|
*
|
||||||
|
* @return `true` if all properties are found in the record, `false` otherwise.
|
||||||
|
*/
|
||||||
|
export function hasProperties(
|
||||||
|
record: Record<string, unknown>,
|
||||||
|
properties: string[]
|
||||||
|
): boolean {
|
||||||
|
const keys = Object.keys(record);
|
||||||
|
return properties.every((property) => keys.includes(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility function for checking if an record has specific properties. If
|
||||||
|
* one of the properties is not found, the function will throw an error.
|
||||||
|
*
|
||||||
|
* @param record The record to check.
|
||||||
|
* @param properties The required properties.
|
||||||
|
*/
|
||||||
|
export function assertProperties(
|
||||||
|
record: Record<string, unknown>,
|
||||||
|
properties: string[]
|
||||||
|
): void {
|
||||||
|
const missing = findMissingProperties(record, properties);
|
||||||
|
if (missing.length > 0) {
|
||||||
|
throw new MissingPropertiesError(missing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MissingPropertiesError extends Error {
|
||||||
|
constructor(missingProperties: string[]) {
|
||||||
|
super(`record is missing the following required properties:
|
||||||
|
${missingProperties.join(",")}`);
|
||||||
|
|
||||||
|
this.name = "MissingPropertiesError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An utility function which checks which of the given properties the provided
|
||||||
|
* record is missing.
|
||||||
|
*
|
||||||
|
* @param record The record to check.
|
||||||
|
* @param properties The requird properties.
|
||||||
|
*
|
||||||
|
* @return An array with all missing properties.
|
||||||
|
*/
|
||||||
|
export function findMissingProperties(
|
||||||
|
record: Record<string, unknown>,
|
||||||
|
properties: string[]
|
||||||
|
): string[] {
|
||||||
|
const keys = Object.keys(record);
|
||||||
|
return properties.filter((property) => !keys.includes(property));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds an identifier parameter from the the provided parameter. If the
|
||||||
|
* parameter is a number it is converted to a string and prefixed with `ID-`.
|
||||||
|
* If the parameter matches the regex for a UUID, the identifier is prefixed
|
||||||
|
* with `UUID-`. Otherwise the identifier is returned as it is.
|
||||||
|
*
|
||||||
|
* @param identifier The identifier to process.
|
||||||
|
*/
|
||||||
|
export function buildIdentifierParam(identifier: string | number): string {
|
||||||
|
if (typeof identifier === "number") {
|
||||||
|
return `ID-${identifier}`;
|
||||||
|
} else {
|
||||||
|
const strIdentifier: string = identifier as string;
|
||||||
|
if (
|
||||||
|
strIdentifier.match(
|
||||||
|
/[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}/g
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return `UUID-${identifier}`;
|
||||||
|
} else {
|
||||||
|
return identifier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error which should be thrown by clients for errors of the client side.
|
||||||
|
*/
|
||||||
|
export class ApiClientError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
|
||||||
|
this.name = "ApiClientError";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* A list view contains an array of objects and some data about the list.
|
||||||
|
*/
|
||||||
|
export interface ListView<T> {
|
||||||
|
/**
|
||||||
|
* The list of objects
|
||||||
|
*/
|
||||||
|
list: T[],
|
||||||
|
/**
|
||||||
|
* The number of elements for the query.
|
||||||
|
*/
|
||||||
|
count: number,
|
||||||
|
/**
|
||||||
|
* Items per page
|
||||||
|
*/
|
||||||
|
limit: number,
|
||||||
|
/**
|
||||||
|
* The first item shown
|
||||||
|
*/
|
||||||
|
offset: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Properties of the type `LocalizedString` are used in several entities
|
||||||
|
* returned by various of the endpoints the RESTful API of LibreCCM.
|
||||||
|
* Each `LocalizedString` consists of various values with the language
|
||||||
|
* as key.
|
||||||
|
*/
|
||||||
|
export interface LocalizedString {
|
||||||
|
values: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "es6",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "dist",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "es6"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/main/typescript/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -5,7 +5,7 @@
|
||||||
"build": "webpack"
|
"build": "webpack"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@librecms/ccm-cms-pagemodelseditor": "7.0.0",
|
"@librecms/ccm-cms-pagemodelseditor": "file:../ccm-cms-pagemodelseditor",
|
||||||
"tinymce": "^4.8.2"
|
"tinymce": "^4.8.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@
|
||||||
<goal>install-node-and-npm</goal>
|
<goal>install-node-and-npm</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<nodeVersion>v8.11.4</nodeVersion>
|
<nodeVersion>${nodeVersion}</nodeVersion>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<!-- <execution>
|
<!-- <execution>
|
||||||
|
|
@ -200,6 +200,7 @@
|
||||||
<type>jar</type>
|
<type>jar</type>
|
||||||
<includes>
|
<includes>
|
||||||
<include>assets/</include>
|
<include>assets/</include>
|
||||||
|
<include>_admin/</include>
|
||||||
</includes>
|
</includes>
|
||||||
</overlay>
|
</overlay>
|
||||||
<overlay>
|
<overlay>
|
||||||
|
|
@ -244,6 +245,9 @@
|
||||||
<configuration>
|
<configuration>
|
||||||
<skip>false</skip>
|
<skip>false</skip>
|
||||||
<propertiesFile>${project.basedir}/wildfly.properties</propertiesFile>
|
<propertiesFile>${project.basedir}/wildfly.properties</propertiesFile>
|
||||||
|
<java-opts>
|
||||||
|
<java-opt>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8787</java-opt>
|
||||||
|
</java-opts>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -101,5 +101,10 @@
|
||||||
<Logger name="com.arsdigita.web.DefaultApplicationFileResolver"
|
<Logger name="com.arsdigita.web.DefaultApplicationFileResolver"
|
||||||
level="debug">
|
level="debug">
|
||||||
</Logger>
|
</Logger>
|
||||||
|
|
||||||
|
|
||||||
|
<Logger name="org.libreccm.ui.admin.AdminUi"
|
||||||
|
level="debug">
|
||||||
|
</Logger>
|
||||||
</Loggers>
|
</Loggers>
|
||||||
</Configuration>
|
</Configuration>
|
||||||
|
|
|
||||||
|
|
@ -75,16 +75,16 @@
|
||||||
<goal>install-node-and-npm</goal>
|
<goal>install-node-and-npm</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<nodeVersion>v8.11.4</nodeVersion>
|
<nodeVersion>${nodeVersion}</nodeVersion>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
<id>npm link @libreccm/ccm-pagemodelseditor</id>
|
<id>install ccm-pagemodelseditor</id>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>npm</goal>
|
<goal>npm</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<arguments>link @libreccm/ccm-pagemodelseditor</arguments>
|
<arguments>install ../ccm-pagemodelseditor</arguments>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
|
|
@ -102,15 +102,6 @@
|
||||||
<arguments>run build</arguments>
|
<arguments>run build</arguments>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
|
||||||
<id>npm link</id>
|
|
||||||
<goals>
|
|
||||||
<goal>npm</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<arguments>link</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -15,7 +15,7 @@
|
||||||
"tslint": "tslint --project ."
|
"tslint": "tslint --project ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@libreccm/ccm-pagemodelseditor": "7.0.0",
|
"@libreccm/ccm-pagemodelseditor": "file:../ccm-pagemodelseditor",
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
"react-dom": "^16.4.2"
|
"react-dom": "^16.4.2"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -57,71 +57,6 @@
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
||||||
<!-- <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-remote-resources-plugin</artifactId> <version>1.5</version> <configuration> <resourceBundles>
|
|
||||||
<resourceBundle>org.libreccm:ccm-pagemodelseditor:${project.version}</resourceBundle> </resourceBundles> </configuration> <executions> <execution> <goals> <goal>process</goal> </goals> </execution> </executions> </plugin> -->
|
|
||||||
|
|
||||||
<!--<plugin>
|
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
|
||||||
<artifactId>exec-maven-plugin</artifactId>
|
|
||||||
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>npm install</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>exec</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<executable>npm</executable>
|
|
||||||
<arguments>
|
|
||||||
<argument>install</argument>
|
|
||||||
</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>npm link @libreccm/ccm-pagemodelseditor</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>exec</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<executable>npm</executable>
|
|
||||||
<arguments>
|
|
||||||
<argument>link</argument>
|
|
||||||
<argument>@libreccm/ccm-pagemodelseditor</argument>
|
|
||||||
</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>npm run build</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>exec</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<executable>npm</executable>
|
|
||||||
<arguments>
|
|
||||||
<argument>run</argument>
|
|
||||||
<argument>build</argument>
|
|
||||||
</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>npm link</id>
|
|
||||||
<phase>package</phase>
|
|
||||||
<goals>
|
|
||||||
<goal>exec</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<executable>npm</executable>
|
|
||||||
<arguments>
|
|
||||||
<argument>link</argument>
|
|
||||||
</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>-->
|
|
||||||
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>com.github.eirslett</groupId>
|
<groupId>com.github.eirslett</groupId>
|
||||||
<artifactId>frontend-maven-plugin</artifactId>
|
<artifactId>frontend-maven-plugin</artifactId>
|
||||||
|
|
@ -137,16 +72,16 @@
|
||||||
<goal>install-node-and-npm</goal>
|
<goal>install-node-and-npm</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<nodeVersion>v8.11.4</nodeVersion>
|
<nodeVersion>${nodeVersion}</nodeVersion>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
<id>npm link @libreccm/ccm-pagemodelseditor</id>
|
<id>install ccm-pagemodelseditor</id>
|
||||||
<goals>
|
<goals>
|
||||||
<goal>npm</goal>
|
<goal>npm</goal>
|
||||||
</goals>
|
</goals>
|
||||||
<configuration>
|
<configuration>
|
||||||
<arguments>link @libreccm/ccm-pagemodelseditor</arguments>
|
<arguments>install ../ccm-pagemodelseditor</arguments>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
<execution>
|
||||||
|
|
@ -164,15 +99,6 @@
|
||||||
<arguments>run build</arguments>
|
<arguments>run build</arguments>
|
||||||
</configuration>
|
</configuration>
|
||||||
</execution>
|
</execution>
|
||||||
<execution>
|
|
||||||
<id>npm link</id>
|
|
||||||
<goals>
|
|
||||||
<goal>npm</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<arguments>link</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
|
|
||||||
<execution>
|
<execution>
|
||||||
<id>npm publish</id>
|
<id>npm publish</id>
|
||||||
|
|
|
||||||
|
|
@ -39,62 +39,9 @@
|
||||||
<resource>
|
<resource>
|
||||||
<directory>src/main/resources</directory>
|
<directory>src/main/resources</directory>
|
||||||
</resource>
|
</resource>
|
||||||
<!-- <resource>
|
|
||||||
<directory>src/main/typescript</directory>
|
|
||||||
</resource>
|
|
||||||
<resource>
|
|
||||||
<directory>${project.build.directory}/generated-resources</directory>
|
|
||||||
</resource> -->
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
||||||
<!-- <plugin>
|
|
||||||
<groupId>com.github.eirslett</groupId>
|
|
||||||
<artifactId>frontend-maven-plugin</artifactId>
|
|
||||||
|
|
||||||
<configuration>
|
|
||||||
<installDirectory>../node</installDirectory>
|
|
||||||
</configuration>
|
|
||||||
|
|
||||||
<executions>
|
|
||||||
<execution>
|
|
||||||
<id>Install node.js and NPM</id>
|
|
||||||
<goals>
|
|
||||||
<goal>install-node-and-npm</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<nodeVersion>v8.11.4</nodeVersion>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>npm install</id>
|
|
||||||
<goals>
|
|
||||||
<goal>npm</goal>
|
|
||||||
</goals>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>build</id>
|
|
||||||
<goals>
|
|
||||||
<goal>npm</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<arguments>run build</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
<execution>
|
|
||||||
<id>npm link</id>
|
|
||||||
<goals>
|
|
||||||
<goal>npm</goal>
|
|
||||||
</goals>
|
|
||||||
<configuration>
|
|
||||||
<arguments>link</arguments>
|
|
||||||
</configuration>
|
|
||||||
</execution>
|
|
||||||
</executions>
|
|
||||||
</plugin>-->
|
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
plugins: [
|
||||||
|
'@typescript-eslint',
|
||||||
|
],
|
||||||
|
extends: [
|
||||||
|
'eslint:recommended',
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
dist
|
||||||
|
node_modules
|
||||||
|
target
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"name": "@libreccm/ccm-core-apiclient",
|
||||||
|
"version": "7.0.0",
|
||||||
|
"main": "target/dist/ccm-core-apiclient.js",
|
||||||
|
"types": "target/dist/ccm-core-apiclient.d.ts",
|
||||||
|
"description": "Client for the RESTful API provided by ccm-core.",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/libreccm/libreccm"
|
||||||
|
},
|
||||||
|
"author": "Jens Pelzetter",
|
||||||
|
"license": "GPL-3.0",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"doc": "typedoc --out target/docs src/main/typescript"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@libreccm/ccm-apiclient-commons": "file:../ccm-apiclient-commons"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^3.6.0",
|
||||||
|
"@typescript-eslint/parser": "^3.6.0",
|
||||||
|
"eslint": "^7.4.0",
|
||||||
|
"typedoc": "^0.17.8",
|
||||||
|
"typescript": "^3.9.6"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,117 @@
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.libreccm</groupId>
|
||||||
|
<artifactId>libreccm-parent</artifactId>
|
||||||
|
<version>7.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>org.libreccm</groupId>
|
||||||
|
<artifactId>ccm-core-apiclient</artifactId>
|
||||||
|
<version>7.0.0-SNAPSHOT</version>
|
||||||
|
|
||||||
|
<name>LibreCCM RESTful Admin API Clients</name>
|
||||||
|
<url>https://www.libreccm.org/ccm-core/api</url>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>Lesser GPL 2.1</name>
|
||||||
|
<url>http://www.gnu.org/licenses/old-licenses/lgpl-2.1</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>ccm-core-apiclient</finalName>
|
||||||
|
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/typescript</directory>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>${project.build.directory}/generated-resources</directory>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.eirslett</groupId>
|
||||||
|
<artifactId>frontend-maven-plugin</artifactId>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<installDirectory>../node</installDirectory>
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>Install node.js and NPM</id>
|
||||||
|
<goals>
|
||||||
|
<goal>install-node-and-npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<nodeVersion>${nodeVersion}</nodeVersion>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>npm install ../ccm-apiclient-commons</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>install ../ccm-apiclient-commons</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>npm install</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>install</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>build</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>run build</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
|
||||||
|
<execution>
|
||||||
|
<id>npm publish</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
|
||||||
|
<phase>deploy</phase>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<arguments>publish --userconfig ../libreccm.npmrc</arguments>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.libreccm</groupId>
|
||||||
|
<artifactId>ccm-apiclient-commons</artifactId>
|
||||||
|
<version>${project.parent.version}</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
export { CategorizationApiClient } from "./clients/categorization-api";
|
||||||
|
export { SystemInformationClient } from "./clients/systeminformation-api";
|
||||||
|
|
||||||
|
export * from "./entities/categorization";
|
||||||
|
export * from "./entities/configuration";
|
||||||
|
export * from "./entities/core";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,333 @@
|
||||||
|
import {
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
ListView,
|
||||||
|
ApiClientError,
|
||||||
|
ApiError,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import { buildIdentifierParam } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
|
||||||
|
import {
|
||||||
|
CcmApplication,
|
||||||
|
buildCcmApplicationFromRecord,
|
||||||
|
} from "../entities/applications";
|
||||||
|
import {
|
||||||
|
DomainOwnership,
|
||||||
|
buildDomainOwnershipFromRecord,
|
||||||
|
} from "../entities/categorization";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client for managing application instances using the RESTful API of LibreCCM
|
||||||
|
*/
|
||||||
|
export class WebApiClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #APPS_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/applications`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAllApplications(
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
): Promise<ListView<CcmApplication>> {
|
||||||
|
const url = `${this.#APPS_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const apps: CcmApplication[] = list.map((record) =>
|
||||||
|
buildCcmApplicationFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: apps,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
"Failed to get application instances",
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiClientError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get application instances: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getApplication(
|
||||||
|
appIdentifier: string | number
|
||||||
|
): Promise<CcmApplication> {
|
||||||
|
const url = `${this.#APPS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
appIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return buildCcmApplicationFromRecord(
|
||||||
|
(await response.json()) as Record<string, unknown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get application instance ${appIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiClientError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get application instance ${appIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createApplication(application: CcmApplication): Promise<void> {
|
||||||
|
const url = `${this.#APPS_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(application)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to create new application instance of type
|
||||||
|
${application.applicationType}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to create new application instance of type
|
||||||
|
${application.applicationType}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateApplication(
|
||||||
|
appIdentifier: string | number,
|
||||||
|
application: CcmApplication
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${this.#APPS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
appIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
JSON.stringify(application)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update application instance ${appIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update application instance ${appIdentifier}:
|
||||||
|
${application.applicationType}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteApplication(appIdentifier: string | number): Promise<void> {
|
||||||
|
const url = `${this.#APPS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
appIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete application instance ${appIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete application instance ${appIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDomains(
|
||||||
|
appIdentifier: string | number,
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
): Promise<ListView<DomainOwnership>> {
|
||||||
|
const url = `${this.#APPS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
appIdentifier
|
||||||
|
)}/domains`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const domains: DomainOwnership[] = list.map((record) =>
|
||||||
|
buildDomainOwnershipFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: domains,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.limit as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get domains of application instance ${appIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get domains of application instance ${appIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addDomin(
|
||||||
|
appIdentifier: string | number,
|
||||||
|
domainIdenfifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const appParam = buildIdentifierParam(appIdentifier);
|
||||||
|
const domainParam = buildIdentifierParam(domainIdenfifier);
|
||||||
|
const url = `${
|
||||||
|
this.#APPS_API_PREFIX
|
||||||
|
}/${appParam}/domains/${domainParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to add domain ${domainIdenfifier} to application ${appIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add domain ${domainIdenfifier} to application ${appIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeDomain(
|
||||||
|
appIdentifier: string | number,
|
||||||
|
domainIdenfifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const appParam = buildIdentifierParam(appIdentifier);
|
||||||
|
const domainParam = buildIdentifierParam(domainIdenfifier);
|
||||||
|
const url = `${
|
||||||
|
this.#APPS_API_PREFIX
|
||||||
|
}/${appParam}/domains/${domainParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove domain ${domainIdenfifier} from application instance ${appIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remove domain ${domainIdenfifier} from application ${appIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,921 @@
|
||||||
|
import {
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
ListView,
|
||||||
|
ApiError,
|
||||||
|
ApiClientError,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Category,
|
||||||
|
Categorization,
|
||||||
|
buildCategoryFromRecord,
|
||||||
|
buildCategorizationFromRecord,
|
||||||
|
} from "../entities/categorization";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
|
||||||
|
export class CategorizationApiClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #CATEGORIES_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/categories`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCategoryByDomainAndPath(
|
||||||
|
domain: string,
|
||||||
|
path: string
|
||||||
|
): Promise<Category> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/${domain}/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return buildCategoryFromRecord(
|
||||||
|
(await response.json()) as Record<string, unknown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get category ${path} of domain ${domain}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get category ${path} of domain ${domain}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCategoryById(categoryId: number): Promise<Category> {
|
||||||
|
const url = `${
|
||||||
|
this.#CATEGORIES_API_PREFIX
|
||||||
|
}/categories/ID-${categoryId}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return buildCategoryFromRecord(
|
||||||
|
(await response.json()) as Record<string, unknown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get category with ID ${categoryId}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get category with Id ${categoryId}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCategoryByUuid(uuid: string): Promise<Category> {
|
||||||
|
try {
|
||||||
|
const url = `${
|
||||||
|
this.#CATEGORIES_API_PREFIX
|
||||||
|
}/categories/UUID-${uuid}`;
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return buildCategoryFromRecord(
|
||||||
|
(await response.json()) as Record<string, unknown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get category with UUID ${uuid}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get category with UUID ${uuid}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCategoryOfDomain(
|
||||||
|
domain: string,
|
||||||
|
path: string,
|
||||||
|
category: Category
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/${domain}/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
JSON.stringify(category)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update category ${path} of domain ${domain}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update category ${path} of domain ${domain}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCategoryWithId(
|
||||||
|
categoryId: number,
|
||||||
|
category: Category
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/ID-${categoryId}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
JSON.stringify(category)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update category with ID ${categoryId}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update category ${categoryId}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateCategoryWithUuid(
|
||||||
|
uuid: string,
|
||||||
|
category: Category
|
||||||
|
): Promise<void> {
|
||||||
|
try {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/UUID-${uuid}`;
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
JSON.stringify(category)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update category with UUID ${uuid}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update category with UUID ${uuid}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCategoryOfDomain(domain: string, path: string): Promise<void> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/${domain}/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to update category ${path} of domain ${domain}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete category ${path} of domain ${domain}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCategoryWithId(categoryId: number): Promise<void> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/ID-${categoryId}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete category with ID ${categoryId}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete category ${categoryId}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCategoryWithUuid(uuid: string): Promise<void> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/UUID-${uuid}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete category with UUID ${uuid}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete category with UUID ${uuid}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSubCategoriesByDomainAndPath(
|
||||||
|
domain: string,
|
||||||
|
path: string,
|
||||||
|
limit = 20,
|
||||||
|
offset = 0
|
||||||
|
): Promise<ListView<Category>> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/${domain}/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const categories: Category[] = list.map((record) =>
|
||||||
|
buildCategoryFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: categories,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get subcategories of category ${path} of domain ${domain}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get subcategories of category ${path} of domain ${domain}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSubCategoriesById(
|
||||||
|
categoryId: number,
|
||||||
|
limit = 20,
|
||||||
|
offset = 0
|
||||||
|
): Promise<ListView<Category>> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/ID-${categoryId}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const categories: Category[] = list.map((record) =>
|
||||||
|
buildCategoryFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: categories,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get subcategories of category with ID ${categoryId}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get subcategories of category with ID ${categoryId}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSubCategoriesByUuid(
|
||||||
|
uuid: string,
|
||||||
|
limit = 20,
|
||||||
|
offset = 0
|
||||||
|
): Promise<ListView<Category>> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/UUID-${uuid}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const categories: Category[] = list.map((record) =>
|
||||||
|
buildCategoryFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: categories,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get subcategories of category with UUID ${uuid}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get subcategories of category with UUID ${uuid}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new subcategory to category
|
||||||
|
*
|
||||||
|
* @param domain
|
||||||
|
* @param path
|
||||||
|
* @param category
|
||||||
|
*
|
||||||
|
* @returns A Promise which resolves to the full path (including domain)
|
||||||
|
* of the new subcategory.
|
||||||
|
*/
|
||||||
|
async addSubCategoryToCategoryOfDomain(
|
||||||
|
domain: string,
|
||||||
|
path: string,
|
||||||
|
category: Category
|
||||||
|
): Promise<string> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/${domain}/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(category)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: string = await response.text();
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add new subcategory to category ${path} of domain ${domain}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add new subcategory to category ${path} of domain ${domain}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addSubCategoryToWithId(
|
||||||
|
categoryId: number,
|
||||||
|
category: Category
|
||||||
|
): Promise<string> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/ID-${categoryId}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
`${this.#CATEGORIES_API_PREFIX}/ID-${categoryId}`,
|
||||||
|
JSON.stringify(category)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: string = await response.text();
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add new subcategory to category with ID ${categoryId}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add new subcategory to category with ID ${categoryId}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addSubCategoryToWithUuid(
|
||||||
|
uuid: string,
|
||||||
|
category: Category
|
||||||
|
): Promise<string> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/UUID-${uuid}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(category)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: string = await response.text();
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add new subcategory to category with UUID ${uuid}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add new subcategory to category with UUID ${uuid}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjectsInCategoryOfDomain(
|
||||||
|
domain: string,
|
||||||
|
path: string,
|
||||||
|
limit = 20,
|
||||||
|
offset = 0
|
||||||
|
): Promise<ListView<Categorization>> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/$domain/${path}/@objects`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const objects: Categorization[] = list.map((record) =>
|
||||||
|
buildCategorizationFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: objects,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get objects in category ${path} of domain ${domain}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get objects in category ${path} of domain ${domain}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjectsInCategoryWithId(
|
||||||
|
categoryId: number,
|
||||||
|
limit = 20,
|
||||||
|
offset = 0
|
||||||
|
): Promise<ListView<Categorization>> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/ID-${categoryId}/@objects`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const objects: Categorization[] = list.map((record) =>
|
||||||
|
buildCategorizationFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: objects,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get objects in category with ID ${categoryId}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get objects in category with Id ${categoryId}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getObjectsInCategoryWithUuid(
|
||||||
|
uuid: string,
|
||||||
|
limit = 20,
|
||||||
|
offset = 0
|
||||||
|
): Promise<ListView<Categorization>> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/UUID-${uuid}/@objects`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const objects: Categorization[] = list.map((record) =>
|
||||||
|
buildCategorizationFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: objects,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get objects in category with UUID ${uuid}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get objects in category with UUId ${uuid}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addObjectToCategoryOfDomain(
|
||||||
|
domain: string,
|
||||||
|
path: string,
|
||||||
|
categorization: Categorization
|
||||||
|
): Promise<string> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/${domain}/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(categorization)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: string = await response.text();
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add object to category ${path} of domain ${domain}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add object to category ${path} of domain ${domain}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addObjectToCategoryWithId(
|
||||||
|
categoryId: number,
|
||||||
|
categorization: Categorization
|
||||||
|
): Promise<string> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/ID-${categoryId}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(categorization)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: string = await response.text();
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add object to category with ID ${categoryId}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add object to category with ID ${categoryId}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addObjectToCategoryWithUuid(
|
||||||
|
uuid: string,
|
||||||
|
categorization: Categorization
|
||||||
|
): Promise<string> {
|
||||||
|
const url = `${this.#CATEGORIES_API_PREFIX}/UUID-${uuid}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(categorization)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: string = await response.text();
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add object to category with UUID ${uuid}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add object to category with UUID ${uuid}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeObjectFromCategoryOfDomain(
|
||||||
|
domain: string,
|
||||||
|
path: string,
|
||||||
|
objectIdentifier: string
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${
|
||||||
|
this.#CATEGORIES_API_PREFIX
|
||||||
|
}/${domain}/${path}/@objects/${objectIdentifier}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove object ${objectIdentifier} from category ${path} of domain ${domain}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(`Failed to remove object ${objectIdentifier} from
|
||||||
|
category ${path} of domain ${domain}: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeObjectFromCategoryWithId(
|
||||||
|
categoryId: number,
|
||||||
|
objectIdentifier: string
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${
|
||||||
|
this.#CATEGORIES_API_PREFIX
|
||||||
|
}/ID-${categoryId}/@objects/${objectIdentifier}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove object ${objectIdentifier} from category with ID ${categoryId}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remove object ${objectIdentifier} from category with ID ${categoryId}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeObjectFromCategoryWithUuid(
|
||||||
|
uuid: string,
|
||||||
|
objectIdentifier: string
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${
|
||||||
|
this.#CATEGORIES_API_PREFIX
|
||||||
|
}/UUID-${uuid}/@objects/${objectIdentifier}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove object ${objectIdentifier} from category with UUID ${uuid}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remove object ${objectIdentifier} from category with UUID ${uuid}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
import {
|
||||||
|
ApiClientError,
|
||||||
|
ApiError,
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ConfigurationInfo,
|
||||||
|
buildConfigurationInfoFromRecord,
|
||||||
|
} from "../entities/configuration";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
import { RequestBody } from "@libreccm/ccm-apiclient-commons/dist/ApiClient";
|
||||||
|
|
||||||
|
export class ConfigurationApiClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #CONFIGURATION_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/configurations`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConfigurations(): Promise<ConfigurationInfo[]> {
|
||||||
|
const url = `${this.#CONFIGURATION_API_PREFIX}/`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[] = (await response.json()) as Record<string, unknown>[];
|
||||||
|
|
||||||
|
return result.map((record) =>
|
||||||
|
buildConfigurationInfoFromRecord(record)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to retrieve configuration info`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to retrieve configuration info: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getConfiguration(confName: string): Promise<ConfigurationInfo> {
|
||||||
|
const url = `${this.#CONFIGURATION_API_PREFIX}/${confName}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
|
||||||
|
return buildConfigurationInfoFromRecord(result);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get configuration info for configuration ${confName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get configuration info for configuration ${confName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSettings(confName: string): Promise<Record<string, unknown>> {
|
||||||
|
const url = `${this.#CONFIGURATION_API_PREFIX}/${confName}/settings`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return (await response.json()) as Record<string, unknown>;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get settings of configuration ${confName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get settings of configuration ${confName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSetting(confName: string, setting: string): Promise<unknown> {
|
||||||
|
const url = `${
|
||||||
|
this.#CONFIGURATION_API_PREFIX
|
||||||
|
}/${confName}/settings/${setting}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return await response.json();
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get setting ${setting} of configuration ${confName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get setting ${setting} of configuration ${confName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSetting(
|
||||||
|
confName: string,
|
||||||
|
setting: string,
|
||||||
|
value: unknown
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${
|
||||||
|
this.#CONFIGURATION_API_PREFIX
|
||||||
|
}/${confName}/settings/${setting}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
value as RequestBody
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update setting ${setting} of configuration ${confName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update setting ${setting} of configuration ${confName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,428 @@
|
||||||
|
import {
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
ListView,
|
||||||
|
ApiError,
|
||||||
|
ApiClientError,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import { buildIdentifierParam } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
import {
|
||||||
|
Group,
|
||||||
|
buildGroupFromRecord,
|
||||||
|
buildGroupUserMembershipFromRecord,
|
||||||
|
GroupUserMembership,
|
||||||
|
} from "../entities/group";
|
||||||
|
import {
|
||||||
|
buildPartyRoleMembershipFromRecord,
|
||||||
|
PartyRoleMembership,
|
||||||
|
} from "../entities/party";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client for managaging groups using the RESTful API of
|
||||||
|
* LibreCCM.
|
||||||
|
*/
|
||||||
|
export class GroupsApiClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #GROUPS_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/groups`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGroups(limit: number, offset: number): Promise<ListView<Group>> {
|
||||||
|
const url = `${this.#GROUPS_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const groups: Group[] = list.map((record) =>
|
||||||
|
buildGroupFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: groups,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get groups`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(`Failed to get groups: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGroup(groupIdentifier: string | number): Promise<Group> {
|
||||||
|
const url = `${this.#GROUPS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
groupIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return buildGroupFromRecord(
|
||||||
|
(await response.json()) as Record<string, unknown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get group ${groupIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get group ${groupIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addGroup(group: Group): Promise<string> {
|
||||||
|
const url = `${this.#GROUPS_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(group)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return group.name;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add group ${group.name}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add group ${group.name}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateGroup(groupIdentifier: string, group: Group): Promise<void> {
|
||||||
|
const url = `${this.#GROUPS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
groupIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
JSON.stringify(group)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update group ${groupIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update group ${groupIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteGroup(groupIdentifier: string): Promise<void> {
|
||||||
|
const url = `${this.#GROUPS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
groupIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete group ${name}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete group ${name}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMembers(
|
||||||
|
groupIdentifier: string,
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
): Promise<ListView<GroupUserMembership>> {
|
||||||
|
const groupParam: string = buildIdentifierParam(groupIdentifier);
|
||||||
|
const url = `${this.#GROUPS_API_PREFIX}/${groupParam}/members`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const members: GroupUserMembership[] = list.map((record) =>
|
||||||
|
buildGroupUserMembershipFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: members,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get members of group ${groupIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof err) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get members of group ${groupIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addMember(
|
||||||
|
groupIdentifier: string | number,
|
||||||
|
userIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const groupParam = buildIdentifierParam(groupIdentifier);
|
||||||
|
const memberParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#GROUPS_API_PREFIX
|
||||||
|
}/${groupParam}/members/${memberParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to add user ${userIdentifier} to group ${groupIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add user ${userIdentifier} to group ${groupIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeMember(
|
||||||
|
groupIdentifier: string | number,
|
||||||
|
userIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const groupParam = buildIdentifierParam(groupIdentifier);
|
||||||
|
const memberParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#GROUPS_API_PREFIX
|
||||||
|
}/${groupParam}/members/${memberParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove user ${userIdentifier} from group ${groupIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remote user ${userIdentifier} from group ${groupIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoles(
|
||||||
|
groupIdentifier: string
|
||||||
|
): Promise<ListView<PartyRoleMembership>> {
|
||||||
|
const groupParam: string = buildIdentifierParam(groupIdentifier);
|
||||||
|
const url = `${this.#GROUPS_API_PREFIX}/${groupParam}/roles`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const roles: PartyRoleMembership[] = list.map((record) =>
|
||||||
|
buildPartyRoleMembershipFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: roles,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get roles of group ${groupIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get roles of group ${groupIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addRole(
|
||||||
|
groupIdentifier: string | number,
|
||||||
|
roleIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const groupParam = buildIdentifierParam(groupIdentifier);
|
||||||
|
const roleParam = buildIdentifierParam(roleIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#GROUPS_API_PREFIX
|
||||||
|
}/${groupParam}/roles/${roleParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to add role ${roleIdentifier} to group ${groupIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add role ${roleIdentifier} to group ${groupIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeRole(
|
||||||
|
groupIdentifier: string | number,
|
||||||
|
roleIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const groupParam = buildIdentifierParam(groupIdentifier);
|
||||||
|
const roleParam = buildIdentifierParam(roleIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#GROUPS_API_PREFIX
|
||||||
|
}/${groupParam}/roles/${roleParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove role ${roleIdentifier} from group ${groupIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remote role ${roleIdentifier} from group ${groupIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
import {
|
||||||
|
ApiClientError,
|
||||||
|
ApiError,
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ImportManifest,
|
||||||
|
buildImportManifestFromRecord,
|
||||||
|
} from "../entities/imexport";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
|
||||||
|
export class ImExportClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #IMEXPORT_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/imexport`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async listImports(): Promise<ImportManifest[]> {
|
||||||
|
const url = `${this.#IMEXPORT_API_PREFIX}/imports`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const records: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[] = (await response.json()) as Record<string, unknown>[];
|
||||||
|
|
||||||
|
return records.map((record) =>
|
||||||
|
buildImportManifestFromRecord(record)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to list available import archives`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to list available import archives: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async importArchive(importName: string): Promise<void> {
|
||||||
|
const url = `${this.#IMEXPORT_API_PREFIX}/imports/${importName}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to import archive ${importName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiClientError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to import archive ${importName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async exportEntities(
|
||||||
|
exportName: string,
|
||||||
|
entityIds: string[]
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${this.#IMEXPORT_API_PREFIX}/exports/${exportName}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(entityIds)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to export entities ${entityIds.join(
|
||||||
|
", "
|
||||||
|
)} as ${exportName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to export entities ${entityIds.join(
|
||||||
|
", "
|
||||||
|
)} as ${exportName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,433 @@
|
||||||
|
import {
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
ListView,
|
||||||
|
ApiError,
|
||||||
|
ApiClientError,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import { buildIdentifierParam } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Role,
|
||||||
|
buildRoleFromRecord,
|
||||||
|
buildRolePartyMembershipFromRecord,
|
||||||
|
buildRolePermissionFromRecord,
|
||||||
|
RolePartyMembership,
|
||||||
|
RolePermission,
|
||||||
|
} from "../entities/role";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client for managing users using the RESTful API of
|
||||||
|
* LibreCCM.
|
||||||
|
*/
|
||||||
|
export class RolesApiClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #ROLES_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/roles`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoles(limit: number, offset: number): Promise<ListView<Role>> {
|
||||||
|
const url = this.#ROLES_API_PREFIX;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const roles: Role[] = list.map((record) =>
|
||||||
|
buildRoleFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: roles,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get roles`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(`Failed to get roles: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRole(roleIdentifier: string): Promise<Role> {
|
||||||
|
const roleParam: string = buildIdentifierParam(roleIdentifier);
|
||||||
|
const url = `${this.#ROLES_API_PREFIX}/${roleParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return buildRoleFromRecord(
|
||||||
|
(await response.json()) as Record<string, unknown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get role ${roleIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(`Failed to get role: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addRole(role: Role): Promise<string> {
|
||||||
|
const url = `${this.#ROLES_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
`${this.#ROLES_API_PREFIX}`,
|
||||||
|
JSON.stringify(role)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return role.name;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add role ${role.name}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add role ${role.name}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateRole(roleIdentifier: string, role: Role): Promise<void> {
|
||||||
|
const url = `${this.#ROLES_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
roleIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
JSON.stringify(role)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update role ${roleIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update role ${roleIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteRole(roleIdentifier: string): Promise<void> {
|
||||||
|
const url = `${this.#ROLES_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
roleIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete role ${name}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete role ${name}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMembers(
|
||||||
|
roleIdentifier: string,
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
): Promise<ListView<RolePartyMembership>> {
|
||||||
|
const url = `${this.#ROLES_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
roleIdentifier
|
||||||
|
)}/members`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const members: RolePartyMembership[] = list.map((record) =>
|
||||||
|
buildRolePartyMembershipFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: members,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get members of role ${roleIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get members of role ${roleIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addMember(
|
||||||
|
roleIdentifier: string | number,
|
||||||
|
userIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const roleParam = buildIdentifierParam(roleIdentifier);
|
||||||
|
const memberParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#ROLES_API_PREFIX
|
||||||
|
}/${roleParam}/members/${memberParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to add user ${userIdentifier} to role ${roleIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add user ${userIdentifier} to role ${roleIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeMember(
|
||||||
|
roleIdentifier: string | number,
|
||||||
|
userIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const roleParam = buildIdentifierParam(roleIdentifier);
|
||||||
|
const memberParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#ROLES_API_PREFIX
|
||||||
|
}/${roleParam}/members/${memberParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove user ${userIdentifier} from role ${roleIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remote user ${userIdentifier} from role ${roleIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPermissions(
|
||||||
|
roleIdentifier: string,
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
): Promise<ListView<RolePermission>> {
|
||||||
|
const roleParam: string = buildIdentifierParam(roleIdentifier);
|
||||||
|
const url = `${this.#ROLES_API_PREFIX}/${roleParam}/permissions`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const permissions: RolePermission[] = list.map((record) =>
|
||||||
|
buildRolePermissionFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: permissions,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get permissions of role ${roleIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get permissions of role ${roleIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addPermission(
|
||||||
|
roleIdentifier: string,
|
||||||
|
permission: RolePermission
|
||||||
|
): Promise<void> {
|
||||||
|
const roleParam: string = buildIdentifierParam(roleIdentifier);
|
||||||
|
const url = `${this.#ROLES_API_PREFIX}/${roleParam}/permissions`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(permission)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add permission to role ${roleIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add permission to role ${roleIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removePermission(
|
||||||
|
roleIdentifier: string | number,
|
||||||
|
permissionIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const roleParam = buildIdentifierParam(roleIdentifier);
|
||||||
|
const permissionParam = buildIdentifierParam(permissionIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#ROLES_API_PREFIX
|
||||||
|
}/${roleParam}/permissions/${permissionParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(
|
||||||
|
`${
|
||||||
|
this.#ROLES_API_PREFIX
|
||||||
|
}/${roleParam}/permissions/${permissionParam}`
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove permission ${permissionIdentifier} from role ${roleIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remote permission ${permissionIdentifier} from role ${roleIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,317 @@
|
||||||
|
import {
|
||||||
|
ApiClientError,
|
||||||
|
ApiError,
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
ListView,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import { buildIdentifierParam } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
|
||||||
|
import { Site, buildSiteFromRecord } from "../entities/site";
|
||||||
|
import {
|
||||||
|
CcmApplicationId,
|
||||||
|
buildCcmApplicationIdFromRecord,
|
||||||
|
} from "../entities/applications";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client for managing sites using the RESTful API
|
||||||
|
*/
|
||||||
|
export class SitesApiClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #SITES_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/sites`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSites(limit: number, offset: number): Promise<ListView<Site>> {
|
||||||
|
const url = `${this.#SITES_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const sites: Site[] = list.map((record) =>
|
||||||
|
buildSiteFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: sites,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get sites`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(`Failed to get sites: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSite(siteIdentifier: string | number): Promise<Site> {
|
||||||
|
try {
|
||||||
|
const url = `${this.#SITES_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
siteIdentifier
|
||||||
|
)}`;
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return buildSiteFromRecord(
|
||||||
|
(await response.json()) as Record<string, unknown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get site ${siteIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get site ${siteIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addSite(site: Site): Promise<string> {
|
||||||
|
const url = `${this.#SITES_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(site)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return site.domainOfSite;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add site ${site.domainOfSite}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiClientError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add site ${site.domainOfSite}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateSite(
|
||||||
|
siteIdentifier: string | number,
|
||||||
|
site: Site
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${this.#SITES_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
siteIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
JSON.stringify(site)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update site ${siteIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update site ${siteIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteSite(siteIdentifier: string | number): Promise<void> {
|
||||||
|
const url = `${this.#SITES_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
siteIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete site ${siteIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete site ${siteIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getApplications(
|
||||||
|
siteIdentifier: string | number,
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
): Promise<ListView<CcmApplicationId>> {
|
||||||
|
const siteParam = buildIdentifierParam(siteIdentifier);
|
||||||
|
const url = `${this.#SITES_API_PREFIX}/${siteParam}/applications`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const applications: CcmApplicationId[] = list.map((record) =>
|
||||||
|
buildCcmApplicationIdFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: applications,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.limit as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get applications of site ${siteIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get applications of site ${siteIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addApplication(
|
||||||
|
siteIdentifier: string | number,
|
||||||
|
applicationIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const siteParam: string = buildIdentifierParam(siteIdentifier);
|
||||||
|
const applicationParam: string = buildIdentifierParam(
|
||||||
|
applicationIdentifier
|
||||||
|
);
|
||||||
|
const url = `${
|
||||||
|
this.#SITES_API_PREFIX
|
||||||
|
}/${siteParam}/groups/${applicationParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to add application ${applicationIdentifier} to site ${siteIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add application ${applicationIdentifier} to site ${siteIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeApplication(
|
||||||
|
siteIdentifier: string | number,
|
||||||
|
applicationIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const siteParam: string = buildIdentifierParam(siteIdentifier);
|
||||||
|
const applicationParam: string = buildIdentifierParam(
|
||||||
|
applicationIdentifier
|
||||||
|
);
|
||||||
|
const url = `${
|
||||||
|
this.#SITES_API_PREFIX
|
||||||
|
}/${siteParam}/groups/${applicationParam}`;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove application ${applicationIdentifier} fromsite ${siteIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remove application ${applicationIdentifier} from site ${siteIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
import {
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
ApiError,
|
||||||
|
ApiClientError,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
|
||||||
|
export class SystemInformationClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #SYSINFO_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/systeminformation`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSystemInformation(): Promise<Record<string, string>> {
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(
|
||||||
|
this.#SYSINFO_API_PREFIX
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return (await response.json()) as Record<string, string>;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
"Failed to get system information",
|
||||||
|
this.#SYSINFO_API_PREFIX
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get system information: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,459 @@
|
||||||
|
import {
|
||||||
|
ApiClientError,
|
||||||
|
ApiError,
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
ThemeInfo,
|
||||||
|
ThemeFileInfo,
|
||||||
|
buildThemeFileInfoFromRecord,
|
||||||
|
buildThemeInfoFromRecord,
|
||||||
|
} from "../entities/themes";
|
||||||
|
|
||||||
|
export class ThemesApiClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #THEMES_API_PREFIX = "/api/themes";
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getThemeProviders(): Promise<string[]> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/providers`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: unknown = await response.json();
|
||||||
|
return result as string[];
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get theme providers`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get theme providers: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAvailableThemes(): Promise<ThemeInfo[]> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/themes`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
if (response.ok) {
|
||||||
|
const list: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[] = (await response.json()) as Record<string, unknown>[];
|
||||||
|
|
||||||
|
return list.map((record) => buildThemeInfoFromRecord(record));
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get avaiable themes`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get avaiable themes: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTheme(themeName: string): Promise<ThemeInfo> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/themes/${themeName}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const record: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
return buildThemeInfoFromRecord(record);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTheme(
|
||||||
|
themeName: string,
|
||||||
|
providerName: string
|
||||||
|
): Promise<ThemeInfo> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/themes/${themeName}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url, {
|
||||||
|
provider: providerName,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const record: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
return buildThemeInfoFromRecord(record);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to create theme ${themeName} using provider ${providerName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiClientError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to create theme ${themeName} using provider ${providerName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteTheme(themeName: string): Promise<void> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/themes/${themeName}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async publishTheme(themeName: string): Promise<void> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/themes/${themeName}/live`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to publish theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to publish theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async unpublishTheme(themeName: string): Promise<void> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/themes/${themeName}/live`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to unpublish theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to unpublish theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getThemeFileInfo(
|
||||||
|
themeName: string,
|
||||||
|
path = ""
|
||||||
|
): Promise<ThemeFileInfo> {
|
||||||
|
const url = `${
|
||||||
|
this.#THEMES_API_PREFIX
|
||||||
|
}/${themeName}/files/${path}/@info`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
|
||||||
|
return buildThemeFileInfoFromRecord(result);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get theme file info for file ${path} of theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get theme file info for file ${path} of theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async listThemesFiles(
|
||||||
|
themeName: string,
|
||||||
|
path = ""
|
||||||
|
): Promise<ThemeFileInfo[]> {
|
||||||
|
const isDirectory = await this.getThemeFileInfo(themeName, path);
|
||||||
|
if (!isDirectory) {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to list files in directory ${path} of theme ${themeName}: Is not a directory.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/${themeName}/files/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return (await response.json()) as ThemeFileInfo[];
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to list files in directory ${path} of theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to list files in directory ${path} of theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getThemeFile(themeName: string, path = ""): Promise<ArrayBuffer> {
|
||||||
|
const isDirectory = await this.getThemeFileInfo(themeName, path);
|
||||||
|
if (!isDirectory) {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get file ${path} from theme ${themeName}: Is a directory.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/${themeName}/files/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return response.arrayBuffer();
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get file ${path} of theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get file ${path} of theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createOrUpdateThemeFile(
|
||||||
|
themeName: string,
|
||||||
|
path: string,
|
||||||
|
data: string | Record<string, unknown> | ArrayBuffer
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/${themeName}/files/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url, data);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to creat update theme file ${path} in theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to create or update file ${path} in theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteThemeFile(
|
||||||
|
themeName: string,
|
||||||
|
path: string,
|
||||||
|
recursive: boolean
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/${themeName}/files/${path}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url, {
|
||||||
|
recursive: recursive.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete file ${path} in theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete file ${path} in theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async downloadTheme(themeName: string): Promise<ArrayBuffer> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/${themeName}/@download`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return response.arrayBuffer();
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to download theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to download theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateTheme(
|
||||||
|
themeName: string,
|
||||||
|
updateTheme: ArrayBuffer
|
||||||
|
): Promise<void> {
|
||||||
|
const url = `${this.#THEMES_API_PREFIX}/${themeName}/@update`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
updateTheme
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to update theme ${themeName}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update theme ${themeName}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,423 @@
|
||||||
|
import {
|
||||||
|
ApiClientError,
|
||||||
|
ApiError,
|
||||||
|
ApiResponse,
|
||||||
|
LibreCcmApiClient,
|
||||||
|
ListView,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import { buildIdentifierParam } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import * as Constants from "../constants";
|
||||||
|
|
||||||
|
import {
|
||||||
|
User,
|
||||||
|
buildUserFromRecord,
|
||||||
|
buildUserGroupMembershipFromRecord,
|
||||||
|
UserGroupMembership,
|
||||||
|
} from "../entities/user";
|
||||||
|
import {
|
||||||
|
buildPartyRoleMembershipFromRecord,
|
||||||
|
PartyRoleMembership,
|
||||||
|
} from "../entities/party";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client for managing roles using the RESTful API of LibreCCM
|
||||||
|
*/
|
||||||
|
export class UsersApiClient {
|
||||||
|
#apiClient: LibreCcmApiClient;
|
||||||
|
|
||||||
|
readonly #USERS_API_PREFIX = `${Constants.ADMIN_API_PREFIX}/users`;
|
||||||
|
|
||||||
|
constructor(apiClient: LibreCcmApiClient) {
|
||||||
|
this.#apiClient = apiClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUsers(limit: number, offset: number): Promise<ListView<User>> {
|
||||||
|
const url = `${this.#USERS_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const users: User[] = list.map((record) =>
|
||||||
|
buildUserFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: users,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get users`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(`Failed to get users: ${err}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUser(userIdentifier: string | number): Promise<User> {
|
||||||
|
const url = `${this.#USERS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
userIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
return buildUserFromRecord(
|
||||||
|
(await response.json()) as Record<string, unknown>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addUser(user: User): Promise<string> {
|
||||||
|
const url = `${this.#USERS_API_PREFIX}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.post(
|
||||||
|
url,
|
||||||
|
JSON.stringify(user)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return user.name;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"post",
|
||||||
|
`Failed to add user ${user.name}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add user ${user.name}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateUser(userIdentifier: string, user: User): Promise<void> {
|
||||||
|
const url = `${this.#USERS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
userIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(
|
||||||
|
url,
|
||||||
|
JSON.stringify(user)
|
||||||
|
);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to update user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to update user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteUser(userIdentifier: string): Promise<void> {
|
||||||
|
const url = `${this.#USERS_API_PREFIX}/${buildIdentifierParam(
|
||||||
|
userIdentifier
|
||||||
|
)}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to delete user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to delete user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGroupMemberships(
|
||||||
|
userIdentifier: string | number,
|
||||||
|
limit: number,
|
||||||
|
offset: number
|
||||||
|
): Promise<ListView<UserGroupMembership>> {
|
||||||
|
const userParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const url = `${this.#USERS_API_PREFIX}/${userParam}/groups`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url, {
|
||||||
|
limit: limit.toString(),
|
||||||
|
offset: offset.toString(),
|
||||||
|
});
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const groups: UserGroupMembership[] = list.map((record) =>
|
||||||
|
buildUserGroupMembershipFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: groups,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.limit as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get group memberships of user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get group memberships of user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addGroupMembership(
|
||||||
|
userIdentifier: string | number,
|
||||||
|
groupIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const userParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const groupParam = buildIdentifierParam(groupIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#USERS_API_PREFIX
|
||||||
|
}/${userParam}/groups/${groupParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to add group membership for group ${groupIdentifier} to user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add group membership for group ${groupIdentifier} to user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeGroupMembership(
|
||||||
|
userIdentifier: string | number,
|
||||||
|
groupIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const userParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const groupParam = buildIdentifierParam(groupIdentifier);
|
||||||
|
const url = `${
|
||||||
|
this.#USERS_API_PREFIX
|
||||||
|
}/${userParam}/groups/${groupParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remote group membership for group ${groupIdentifier} from user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remove group membership for group ${groupIdentifier} from user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRoles(
|
||||||
|
userIdentifier: string
|
||||||
|
): Promise<ListView<PartyRoleMembership>> {
|
||||||
|
const userParam: string = buildIdentifierParam(userIdentifier);
|
||||||
|
const url = `${this.#USERS_API_PREFIX}/${userParam}/roles`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.get(url);
|
||||||
|
if (response.ok) {
|
||||||
|
const result: Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
> = (await response.json()) as Record<string, unknown>;
|
||||||
|
const list: Record<string, unknown>[] = result.list as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const roles: PartyRoleMembership[] = list.map((record) =>
|
||||||
|
buildPartyRoleMembershipFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
list: roles,
|
||||||
|
count: result.count as number,
|
||||||
|
limit: result.limit as number,
|
||||||
|
offset: result.offset as number,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"get",
|
||||||
|
`Failed to get roles of user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to get roles of user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async addRole(
|
||||||
|
userIdentifier: string | number,
|
||||||
|
roleIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const userParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const roleParam = buildIdentifierParam(roleIdentifier);
|
||||||
|
const url = `${this.#USERS_API_PREFIX}/${userParam}/roles/${roleParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.put(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"put",
|
||||||
|
`Failed to add role ${roleIdentifier} to user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to add role ${roleIdentifier} to user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeRole(
|
||||||
|
userIdentifier: string | number,
|
||||||
|
roleIdentifier: string | number
|
||||||
|
): Promise<void> {
|
||||||
|
const userParam = buildIdentifierParam(userIdentifier);
|
||||||
|
const roleParam = buildIdentifierParam(roleIdentifier);
|
||||||
|
const url = `${this.#USERS_API_PREFIX}/${userParam}/roles/${roleParam}`;
|
||||||
|
try {
|
||||||
|
const response: ApiResponse = await this.#apiClient.delete(url);
|
||||||
|
if (response.ok) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new ApiError(
|
||||||
|
response.status,
|
||||||
|
response.statusText,
|
||||||
|
"delete",
|
||||||
|
`Failed to remove role ${roleIdentifier} from user ${userIdentifier}`,
|
||||||
|
url
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof ApiError) {
|
||||||
|
throw err;
|
||||||
|
} else {
|
||||||
|
throw new ApiClientError(
|
||||||
|
`Failed to remote role ${roleIdentifier} from user ${userIdentifier}: ${err}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
const ADMIN_API_PREFIX = "/api/admin";
|
||||||
|
|
||||||
|
export {
|
||||||
|
ADMIN_API_PREFIX
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
/**
|
||||||
|
* Entities used to manage application instances using the RESTful API.
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
LocalizedString,
|
||||||
|
assertProperties,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
import {
|
||||||
|
DomainOwnership,
|
||||||
|
buildDomainOwnershipFromRecord,
|
||||||
|
} from "./categorization";
|
||||||
|
import { SiteId } from "./site";
|
||||||
|
|
||||||
|
export interface CcmApplication {
|
||||||
|
applicationId: number;
|
||||||
|
uuid: string;
|
||||||
|
title: LocalizedString;
|
||||||
|
description: LocalizedString;
|
||||||
|
created: string;
|
||||||
|
applicationClassName: string;
|
||||||
|
applicationType: string;
|
||||||
|
primaryUrl: string;
|
||||||
|
domains: DomainOwnership[];
|
||||||
|
siteAware: boolean;
|
||||||
|
site: SiteId;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CcmApplicationId {
|
||||||
|
applicationType: string;
|
||||||
|
primaryUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildCcmApplicationFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): CcmApplication {
|
||||||
|
assertProperties(record, [
|
||||||
|
"applicationId",
|
||||||
|
"uuid",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"created",
|
||||||
|
"applicationClassName",
|
||||||
|
"applicationType",
|
||||||
|
"primaryUrl",
|
||||||
|
"domains",
|
||||||
|
"siteAware",
|
||||||
|
"site",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const domainRecords: Record<string, unknown>[] = record.domains as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const domains = domainRecords.map((record) =>
|
||||||
|
buildDomainOwnershipFromRecord(record)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
applicationId: record.applicationId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
title: record.title as LocalizedString,
|
||||||
|
description: record.description as LocalizedString,
|
||||||
|
created: record.created as string,
|
||||||
|
applicationClassName: record.applicationClassName as string,
|
||||||
|
applicationType: record.applicationType as string,
|
||||||
|
primaryUrl: record.primaryUrl as string,
|
||||||
|
domains,
|
||||||
|
siteAware: record.siteAware as boolean,
|
||||||
|
site: record.site as SiteId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildCcmApplicationIdFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): CcmApplicationId {
|
||||||
|
assertProperties(record, ["applicationType", "primaryUrl"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
applicationType: record.applicationType as string,
|
||||||
|
primaryUrl: record.primaryUrl as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,219 @@
|
||||||
|
/**
|
||||||
|
* Entities used the categorization API.
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
LocalizedString,
|
||||||
|
assertProperties,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
import { CcmObjectId } from "./core";
|
||||||
|
import { CcmApplicationId } from "./applications";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data required to identify a category.
|
||||||
|
*/
|
||||||
|
export interface CategoryId {
|
||||||
|
categoryId: number;
|
||||||
|
uuid: string;
|
||||||
|
uniqueId: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An entity providing the data of a category which is associated with
|
||||||
|
* another category.
|
||||||
|
*/
|
||||||
|
export interface AssociatedCategory extends CategoryId {
|
||||||
|
/**
|
||||||
|
* The name of the category
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The title of the category
|
||||||
|
*/
|
||||||
|
title: LocalizedString;
|
||||||
|
/**
|
||||||
|
* The description of the category
|
||||||
|
*/
|
||||||
|
description: LocalizedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A category.
|
||||||
|
*/
|
||||||
|
export interface Category {
|
||||||
|
/**
|
||||||
|
* The ID of the category.
|
||||||
|
*/
|
||||||
|
categoryId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the category.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The unique ID of the category.
|
||||||
|
*/
|
||||||
|
uniqueId: string;
|
||||||
|
/**
|
||||||
|
* The name of the category
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The title of the category.
|
||||||
|
*/
|
||||||
|
title: LocalizedString;
|
||||||
|
/**
|
||||||
|
* The description of the category.
|
||||||
|
*/
|
||||||
|
description: LocalizedString;
|
||||||
|
/**
|
||||||
|
* Is the category enabled?
|
||||||
|
*/
|
||||||
|
enabled: boolean;
|
||||||
|
/**
|
||||||
|
* Is the teh category an abstract category?
|
||||||
|
*/
|
||||||
|
abstractCategory: boolean;
|
||||||
|
/**
|
||||||
|
* The parent category of the category. Is null if the category is a root
|
||||||
|
* category.
|
||||||
|
*/
|
||||||
|
parentCategory: AssociatedCategory | null;
|
||||||
|
/**
|
||||||
|
* Used to determine the order of the category in the list of subcategories
|
||||||
|
* of the parent category.
|
||||||
|
*/
|
||||||
|
categoryOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data about a categorized object.
|
||||||
|
*/
|
||||||
|
export interface Categorization {
|
||||||
|
/**
|
||||||
|
* The ID of the categorization.
|
||||||
|
*/
|
||||||
|
categorizationId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the categorization
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The categorized object.
|
||||||
|
*/
|
||||||
|
categorizedObject: CcmObjectId;
|
||||||
|
/**
|
||||||
|
* Is the categorized object the index object of the category?
|
||||||
|
*/
|
||||||
|
indexObject: boolean;
|
||||||
|
/**
|
||||||
|
* Used to order the list of categories to which an object is assigned.
|
||||||
|
*/
|
||||||
|
categoryOrder: number;
|
||||||
|
/**
|
||||||
|
* Used to order the list of objects assigned to a category.
|
||||||
|
*/
|
||||||
|
objectOrder: number;
|
||||||
|
/**
|
||||||
|
* The type of the categorization.
|
||||||
|
*/
|
||||||
|
type: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DomainOwnership {
|
||||||
|
ownershipId: number;
|
||||||
|
uuid: string;
|
||||||
|
context: string;
|
||||||
|
owner: CcmApplicationId;
|
||||||
|
ownerOrder: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link Category} object from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as datasource.
|
||||||
|
*/
|
||||||
|
export function buildCategoryFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): Category {
|
||||||
|
assertProperties(record, [
|
||||||
|
"categoryId",
|
||||||
|
"uuid",
|
||||||
|
"uniqueId",
|
||||||
|
"name",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"enabled",
|
||||||
|
"abstractCategory",
|
||||||
|
"parentCategory",
|
||||||
|
"categoryOrder",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const parentCategory: AssociatedCategory | null = record.parentCategory
|
||||||
|
? (record.parentCategory as AssociatedCategory)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
categoryId: record.categoryId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
uniqueId: record.uniqueId as string,
|
||||||
|
name: record.name as string,
|
||||||
|
title: record.title as LocalizedString,
|
||||||
|
description: record.description as LocalizedString,
|
||||||
|
enabled: record.enable as boolean,
|
||||||
|
abstractCategory: record.abstractCategory as boolean,
|
||||||
|
parentCategory,
|
||||||
|
categoryOrder: record.categoryOrder as number,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for building a {@link Categorization} object from
|
||||||
|
* a record.
|
||||||
|
*
|
||||||
|
* @param record The record to use as datasource.
|
||||||
|
*/
|
||||||
|
export function buildCategorizationFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): Categorization {
|
||||||
|
assertProperties(record, [
|
||||||
|
"categorizationId",
|
||||||
|
"uuid",
|
||||||
|
"categorizedObject",
|
||||||
|
"indexObject",
|
||||||
|
"categoryOrder",
|
||||||
|
"objectOrder",
|
||||||
|
"type",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
categorizationId: record.categorizationId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
categorizedObject: record.categorizedObject as CcmObjectId,
|
||||||
|
indexObject: record.indexObject as boolean,
|
||||||
|
categoryOrder: record.categoryOrder as number,
|
||||||
|
objectOrder: record.objectOrder as number,
|
||||||
|
type: record.type as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildDomainOwnershipFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): DomainOwnership {
|
||||||
|
assertProperties(record, [
|
||||||
|
"ownershipId",
|
||||||
|
"uuid",
|
||||||
|
"context",
|
||||||
|
"owner",
|
||||||
|
"ownerOrder",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
ownershipId: record.ownershipId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
context: record.context as string,
|
||||||
|
owner: record.owner as CcmApplicationId,
|
||||||
|
ownerOrder: record.ownerOrder as number,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the configuration API.
|
||||||
|
*
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assertProperties } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informations about a configuration class.
|
||||||
|
*/
|
||||||
|
export interface ConfigurationInfo {
|
||||||
|
name: string;
|
||||||
|
descBundle: string;
|
||||||
|
titleKey: string;
|
||||||
|
descKey: string;
|
||||||
|
settings: Record<string, SettingInfo>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about a specific setting.
|
||||||
|
*/
|
||||||
|
export interface SettingInfo {
|
||||||
|
name: string;
|
||||||
|
valueType: string;
|
||||||
|
defaultValue: string;
|
||||||
|
confClass: string;
|
||||||
|
descBundle: string;
|
||||||
|
labelKey: string;
|
||||||
|
descKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for building a {@link ConfigurationInfo} object from a
|
||||||
|
* record.
|
||||||
|
* @param record The record used as datasource.
|
||||||
|
*/
|
||||||
|
export function buildConfigurationInfoFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): ConfigurationInfo {
|
||||||
|
assertProperties(record, [
|
||||||
|
"name",
|
||||||
|
"descBundle",
|
||||||
|
"titleKey",
|
||||||
|
"descKey",
|
||||||
|
"settings",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const settingRecords: Record<
|
||||||
|
string,
|
||||||
|
Record<string, unknown>
|
||||||
|
>[] = record.settings as Record<string, Record<string, unknown>>[];
|
||||||
|
|
||||||
|
const settings: Record<string, SettingInfo> = {};
|
||||||
|
|
||||||
|
for (const key in settingRecords) {
|
||||||
|
settings[key] = buildSettingInfoFromRecord(settingRecords[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: record.name as string,
|
||||||
|
descBundle: record.descBundle as string,
|
||||||
|
titleKey: record.titleKey as string,
|
||||||
|
descKey: record.descKey as string,
|
||||||
|
settings,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link SettingInfo} object from a record.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
export function buildSettingInfoFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): SettingInfo {
|
||||||
|
assertProperties(record, [
|
||||||
|
"name",
|
||||||
|
"valueType",
|
||||||
|
"defaultValue",
|
||||||
|
"confClass",
|
||||||
|
"descBundle",
|
||||||
|
"labelKey",
|
||||||
|
"descKey",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: record.name as string,
|
||||||
|
valueType: record.valueType as string,
|
||||||
|
defaultValue: record.defaultValue as string,
|
||||||
|
confClass: record.confClass as string,
|
||||||
|
descBundle: record.descBundle as string,
|
||||||
|
labelKey: record.labelKey as string,
|
||||||
|
descKey: record.descKey as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/**
|
||||||
|
* Some basic entities
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic data about an `CcmObject`.
|
||||||
|
*/
|
||||||
|
export interface CcmObjectId {
|
||||||
|
objectId: number,
|
||||||
|
uuid: string,
|
||||||
|
displayName: string
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,107 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the RESTful API for managing groups.
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assertProperties } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
PartyId,
|
||||||
|
PartyRoleMembership,
|
||||||
|
buildPartyRoleMembershipFromRecord,
|
||||||
|
buildPartyIdFromRecord
|
||||||
|
} from "./party";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A group is used to collect several users.
|
||||||
|
*/
|
||||||
|
export interface Group {
|
||||||
|
/**
|
||||||
|
* The ID of the group.
|
||||||
|
*/
|
||||||
|
partyId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the group.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The name of the group.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The members of the group.
|
||||||
|
*/
|
||||||
|
memberships: GroupUserMembership[];
|
||||||
|
/**
|
||||||
|
* The roles assigned to the group.
|
||||||
|
*/
|
||||||
|
roleMemberships: PartyRoleMembership[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data about a user which is a member of a group.
|
||||||
|
*/
|
||||||
|
export interface GroupUserMembership {
|
||||||
|
/**
|
||||||
|
* The ID of the membership.
|
||||||
|
*/
|
||||||
|
membershipId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the membership.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The user.
|
||||||
|
*/
|
||||||
|
user: PartyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link Group} object from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as datasource.
|
||||||
|
*/
|
||||||
|
export function buildGroupFromRecord(record: Record<string, unknown>): Group {
|
||||||
|
assertProperties(record, [
|
||||||
|
"partyId",
|
||||||
|
"uuid",
|
||||||
|
"name",
|
||||||
|
"memberships",
|
||||||
|
"roleMemberships",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const membershipRecords = record.memberships as Record<string, unknown>[];
|
||||||
|
const roleMembershipRecords = record.roleMemberships as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
partyId: record.partyId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
name: record.name as string,
|
||||||
|
memberships: membershipRecords.map((r) =>
|
||||||
|
buildGroupUserMembershipFromRecord(r)
|
||||||
|
),
|
||||||
|
roleMemberships: roleMembershipRecords.map((r) =>
|
||||||
|
buildPartyRoleMembershipFromRecord(r)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link GroupUserMembership} object from a record.
|
||||||
|
* @param record The record used as datasource.
|
||||||
|
*/
|
||||||
|
export function buildGroupUserMembershipFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): GroupUserMembership {
|
||||||
|
assertProperties(record, ["membershipId", "uuid", "user"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
membershipId: record.membershipId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
user: buildPartyIdFromRecord(record.user as Record<string, unknown>),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the RESTful API for managing imports and exports.
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assertProperties } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An import manifest contains data about an import.
|
||||||
|
*/
|
||||||
|
export interface ImportManifest {
|
||||||
|
/**
|
||||||
|
* The on which the import archive was created.
|
||||||
|
*/
|
||||||
|
created: Date;
|
||||||
|
/**
|
||||||
|
* The URL of the server on which the archive was created.
|
||||||
|
*/
|
||||||
|
onServer: string;
|
||||||
|
/**
|
||||||
|
* The object types included in the archive.
|
||||||
|
*/
|
||||||
|
types: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link ImportManifest} from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
export function buildImportManifestFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): ImportManifest {
|
||||||
|
assertProperties(record, ["created", "onServer", "types"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
created: new Date(record.created as string),
|
||||||
|
onServer: record.onServer as string,
|
||||||
|
types: record.types as string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the RESTful APIs for managing users, groups and roles.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assertProperties } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import { RoleId, buildRoleIdFromRecord } from "./role";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A party is either a user or a group.
|
||||||
|
*/
|
||||||
|
export interface PartyId {
|
||||||
|
/**
|
||||||
|
* The ID of the party.
|
||||||
|
*/
|
||||||
|
partyId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the party.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The name of the party.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A role assigned to a party.
|
||||||
|
*/
|
||||||
|
export interface PartyRoleMembership {
|
||||||
|
/**
|
||||||
|
* The membership ID.
|
||||||
|
*/
|
||||||
|
membershipId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the membership.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The assigned role.
|
||||||
|
*/
|
||||||
|
role: RoleId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link PartyRoleMembership} from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
export function buildPartyRoleMembershipFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): PartyRoleMembership {
|
||||||
|
assertProperties(record, ["membershipId", "uuid", "role"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
membershipId: record.membershipId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
role: buildRoleIdFromRecord(record.role as Record<string, unknown>),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link PartyId} from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as datasource.
|
||||||
|
*/
|
||||||
|
export function buildPartyIdFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): PartyId {
|
||||||
|
assertProperties(record, ["partyId", "uuid", "name"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
partyId: record.partyId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
name: record.name as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,227 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the RESTful API for managing roles.
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
assertProperties,
|
||||||
|
LocalizedString,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
import { CcmObjectId } from "./core";
|
||||||
|
import { TaskId } from "./workflow";
|
||||||
|
|
||||||
|
import { PartyId } from "./party";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tasks assigned to a role.
|
||||||
|
*/
|
||||||
|
export interface RoleAssignedTask {
|
||||||
|
/**
|
||||||
|
* The ID of the assignment.
|
||||||
|
*/
|
||||||
|
taskAssignmentId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the assignment.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The assigned task.
|
||||||
|
*/
|
||||||
|
task: TaskId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A role which can be assigned to a user or a group.
|
||||||
|
*/
|
||||||
|
export interface Role {
|
||||||
|
/**
|
||||||
|
* The ID of the role.
|
||||||
|
*/
|
||||||
|
roleId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the role.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The name of the role.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* Description of the role.
|
||||||
|
*/
|
||||||
|
description: LocalizedString;
|
||||||
|
/**
|
||||||
|
* Permissions granted to the role.
|
||||||
|
*/
|
||||||
|
permissions: RolePermission[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Basic information required to identify a role.
|
||||||
|
*/
|
||||||
|
export interface RoleId {
|
||||||
|
/**
|
||||||
|
* ID of the role.
|
||||||
|
*/
|
||||||
|
roleId: number;
|
||||||
|
/**
|
||||||
|
* UUID of the role.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* Name of the role.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A member of a role.
|
||||||
|
*/
|
||||||
|
export interface RolePartyMembership {
|
||||||
|
/**
|
||||||
|
* The membership ID.
|
||||||
|
*/
|
||||||
|
membershipId: number;
|
||||||
|
/**
|
||||||
|
* UUID of the membership.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The member.
|
||||||
|
*/
|
||||||
|
party: PartyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A permission granted to a role.
|
||||||
|
*/
|
||||||
|
export interface RolePermission {
|
||||||
|
/**
|
||||||
|
* The ID of the permission.
|
||||||
|
*/
|
||||||
|
permissionId: number;
|
||||||
|
/**
|
||||||
|
* UUID of the permission
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The privilege granted by the permission.
|
||||||
|
*/
|
||||||
|
grantedPrivilege: string;
|
||||||
|
/**
|
||||||
|
* Is the permission inherited?
|
||||||
|
*/
|
||||||
|
inherited: boolean;
|
||||||
|
/**
|
||||||
|
* The object for which the permission was granted. Might be null.
|
||||||
|
*/
|
||||||
|
object: CcmObjectId | null;
|
||||||
|
/**
|
||||||
|
* The user which created the permission.
|
||||||
|
*/
|
||||||
|
creationUser: PartyId;
|
||||||
|
/**
|
||||||
|
* Date on which the permission was created.
|
||||||
|
*/
|
||||||
|
creationDate: Date;
|
||||||
|
/**
|
||||||
|
* The IP of the creation user.
|
||||||
|
*/
|
||||||
|
creationIp: string;
|
||||||
|
/**
|
||||||
|
* If the permissions is inherited, this property will point to the
|
||||||
|
* object from which it was inherited. Might be null.
|
||||||
|
*/
|
||||||
|
inheritedFrom: CcmObjectId | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link Role} object from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
export function buildRoleFromRecord(record: Record<string, unknown>): Role {
|
||||||
|
assertProperties(record, [
|
||||||
|
"roleId",
|
||||||
|
"uuid",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"permissions",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const permissionRecords = record.permissions as Record<string, unknown>[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
roleId: record.roleId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
name: record.name as string,
|
||||||
|
description: record.description as LocalizedString,
|
||||||
|
permissions: permissionRecords.map((r) =>
|
||||||
|
buildRolePermissionFromRecord(r)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link RoleId} object from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
export function buildRoleIdFromRecord(record: Record<string, unknown>): RoleId {
|
||||||
|
assertProperties(record, ["roleId", "uuid", "name"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
roleId: record.roleId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
name: record.name as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link RolePartyMembership} object from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
export function buildRolePartyMembershipFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): RolePartyMembership {
|
||||||
|
assertProperties(record, ["membershipId", "uuid", "party"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
membershipId: record.membershipId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
party: record.party as PartyId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link RolePermission} object from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
export function buildRolePermissionFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): RolePermission {
|
||||||
|
assertProperties(record, [
|
||||||
|
"permissionId",
|
||||||
|
"uuid",
|
||||||
|
"grantedPrivilege",
|
||||||
|
"inherited",
|
||||||
|
"object",
|
||||||
|
"creationUser",
|
||||||
|
"creationIp",
|
||||||
|
"inheritedFrom",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
permissionId: record.permissionId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
grantedPrivilege: record.grantedPrivilege as string,
|
||||||
|
inherited: record.inherited as boolean,
|
||||||
|
object: record.object as CcmObjectId,
|
||||||
|
creationUser: record.creationUser as PartyId,
|
||||||
|
creationDate: record.creationDate as Date,
|
||||||
|
creationIp: record.creationIp as string,
|
||||||
|
inheritedFrom: record.inheritedFrom as CcmObjectId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the RESTful API for managing sites
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assertProperties } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
export interface Site {
|
||||||
|
siteId: number;
|
||||||
|
uuid: string;
|
||||||
|
domainOfSite: string;
|
||||||
|
defaultSite: string;
|
||||||
|
defaultTheme: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SiteId {
|
||||||
|
siteId: number;
|
||||||
|
uuid: string;
|
||||||
|
domainOfSite: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildSiteFromRecord(record: Record<string, unknown>): Site {
|
||||||
|
assertProperties(record, [
|
||||||
|
"siteId",
|
||||||
|
"uuid",
|
||||||
|
"domainOfSite",
|
||||||
|
"defaultSite",
|
||||||
|
"defaultTheme",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
siteId: record.siteId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
domainOfSite: record.domainOfSite as string,
|
||||||
|
defaultSite: record.defaultSite as string,
|
||||||
|
defaultTheme: record.defaultTheme as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the RESTful API for managing themes
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
LocalizedString,
|
||||||
|
assertProperties,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
export interface ThemeFileInfo {
|
||||||
|
name: string;
|
||||||
|
directory: boolean;
|
||||||
|
mimeType: string;
|
||||||
|
size: number;
|
||||||
|
writable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThemeInfo {
|
||||||
|
themeManifest: ThemeManifest;
|
||||||
|
version: ThemeVersion;
|
||||||
|
provider: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ThemeManifest {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
masterTheme: string;
|
||||||
|
title: LocalizedString;
|
||||||
|
description: LocalizedString;
|
||||||
|
defaultTemplate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum ThemeVersion {
|
||||||
|
DRAFT,
|
||||||
|
LIVE,
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildThemeInfoFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): ThemeInfo {
|
||||||
|
assertProperties(record, ["themeManifest", "version", "provider"]);
|
||||||
|
|
||||||
|
const themeManifest: ThemeManifest = buildThemeManifestFromRecord(
|
||||||
|
record.themeManifest as Record<string, unknown>
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
themeManifest,
|
||||||
|
version: record.version as ThemeVersion,
|
||||||
|
provider: record.provider as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildThemeManifestFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): ThemeManifest {
|
||||||
|
assertProperties(record, [
|
||||||
|
"name",
|
||||||
|
"type",
|
||||||
|
"masterTheme",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"defaultTemplate",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: record.name as string,
|
||||||
|
type: record.type as string,
|
||||||
|
masterTheme: record.masterTheme as string,
|
||||||
|
title: record.title as LocalizedString,
|
||||||
|
description: record.description as LocalizedString,
|
||||||
|
defaultTemplate: record.defaultTemplate as string,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildThemeFileInfoFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): ThemeFileInfo {
|
||||||
|
assertProperties(record, [
|
||||||
|
"name",
|
||||||
|
"directory",
|
||||||
|
"mimeType",
|
||||||
|
"size",
|
||||||
|
"writable",
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: record.name as string,
|
||||||
|
directory: record.directory as boolean,
|
||||||
|
mimeType: record.mimeType as string,
|
||||||
|
size: record.size as number,
|
||||||
|
writable: record.writeable as boolean
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,164 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the RESTful API for managing users.
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { assertProperties } from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
import {
|
||||||
|
PartyId,
|
||||||
|
PartyRoleMembership,
|
||||||
|
buildPartyRoleMembershipFromRecord,
|
||||||
|
} from "./party";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data about an email address.
|
||||||
|
*/
|
||||||
|
export interface EmailAddress {
|
||||||
|
/**
|
||||||
|
* The email address.
|
||||||
|
*/
|
||||||
|
address: string;
|
||||||
|
/**
|
||||||
|
* Is the address bouncing?
|
||||||
|
*/
|
||||||
|
bouncing: boolean;
|
||||||
|
/**
|
||||||
|
* Is the address verified?
|
||||||
|
*/
|
||||||
|
verified: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A user
|
||||||
|
*/
|
||||||
|
export interface User {
|
||||||
|
/**
|
||||||
|
* The ID of the user.
|
||||||
|
*/
|
||||||
|
partyId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the user.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The name of the user.
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
/**
|
||||||
|
* The given name of the user.
|
||||||
|
*/
|
||||||
|
givenName: string;
|
||||||
|
/**
|
||||||
|
* The family name of the user.
|
||||||
|
*/
|
||||||
|
familyName: string;
|
||||||
|
/**
|
||||||
|
* The primary email address of the user
|
||||||
|
*/
|
||||||
|
primaryEmailAddress: EmailAddress;
|
||||||
|
/**
|
||||||
|
* Additional email addresses of the user.
|
||||||
|
*/
|
||||||
|
emailAddresses: EmailAddress[];
|
||||||
|
/**
|
||||||
|
* Is the user banned?
|
||||||
|
*/
|
||||||
|
banned: boolean;
|
||||||
|
/**
|
||||||
|
* Is a password reset required for the user?
|
||||||
|
*/
|
||||||
|
passwordResetRequired: boolean;
|
||||||
|
/**
|
||||||
|
* The group memberships of the user.
|
||||||
|
*/
|
||||||
|
groupMemberships: UserGroupMembership[];
|
||||||
|
/**
|
||||||
|
* The role memberships of the user.
|
||||||
|
*/
|
||||||
|
roleMemberships: PartyRoleMembership[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Membership of a user in a group.
|
||||||
|
*/
|
||||||
|
export interface UserGroupMembership {
|
||||||
|
/**
|
||||||
|
* The ID of the membership.
|
||||||
|
*/
|
||||||
|
membershipId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the membership.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The group.
|
||||||
|
*/
|
||||||
|
group: PartyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link User} object from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
export function buildUserFromRecord(record: Record<string, unknown>): User {
|
||||||
|
assertProperties(record, [
|
||||||
|
"partyId",
|
||||||
|
"uuid",
|
||||||
|
"name",
|
||||||
|
"givenName",
|
||||||
|
"familyName",
|
||||||
|
"primaryEmailAddress",
|
||||||
|
"emailAddresses",
|
||||||
|
"banned",
|
||||||
|
"passwordResetRequired",
|
||||||
|
"groupMemberships",
|
||||||
|
"roleMemberships",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const groupMembershipRecords = record.groupMemberships as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
const roleMembershipRecords = record.roleMemberships as Record<
|
||||||
|
string,
|
||||||
|
unknown
|
||||||
|
>[];
|
||||||
|
|
||||||
|
return {
|
||||||
|
partyId: record.partyId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
name: record.name as string,
|
||||||
|
givenName: record.givenName as string,
|
||||||
|
familyName: record.familyName as string,
|
||||||
|
primaryEmailAddress: record.primaryEmailAddress as EmailAddress,
|
||||||
|
emailAddresses: record.emailAddresses as EmailAddress[],
|
||||||
|
banned: record.banned as boolean,
|
||||||
|
passwordResetRequired: record.passwordResetRequired as boolean,
|
||||||
|
groupMemberships: groupMembershipRecords.map((r) =>
|
||||||
|
buildUserGroupMembershipFromRecord(r)
|
||||||
|
),
|
||||||
|
roleMemberships: roleMembershipRecords.map((r) =>
|
||||||
|
buildPartyRoleMembershipFromRecord(r)
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a {@link UserGroupMembership} object from a `Record`.
|
||||||
|
*
|
||||||
|
* @param record The record used as data source.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function buildUserGroupMembershipFromRecord(
|
||||||
|
record: Record<string, unknown>
|
||||||
|
): UserGroupMembership {
|
||||||
|
assertProperties(record, ["membershipId", "uuid", "group"]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
membershipId: record.membershipId as number,
|
||||||
|
uuid: record.uuid as string,
|
||||||
|
group: record.party as PartyId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
/**
|
||||||
|
* Entities used by the RESTful API for managing workflows.
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
LocalizedString,
|
||||||
|
assertProperties,
|
||||||
|
} from "@libreccm/ccm-apiclient-commons";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task associated with another entity.
|
||||||
|
*/
|
||||||
|
export interface TaskId {
|
||||||
|
/**
|
||||||
|
* The id of the task.
|
||||||
|
*/
|
||||||
|
taskId: number;
|
||||||
|
/**
|
||||||
|
* The UUID of the task.
|
||||||
|
*/
|
||||||
|
uuid: string;
|
||||||
|
/**
|
||||||
|
* The label of the task.
|
||||||
|
*/
|
||||||
|
label: LocalizedString;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "es6",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"outDir": "target/dist",
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"strict": true,
|
||||||
|
"target": "es6"
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/main/typescript/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
161
ccm-core/pom.xml
161
ccm-core/pom.xml
|
|
@ -110,6 +110,21 @@
|
||||||
<artifactId>flyway-core</artifactId>
|
<artifactId>flyway-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!--OpenAPI/Swagger Annoations for documenting the RESTful API-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
|
<artifactId>swagger-jaxrs2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
|
<artifactId>swagger-jaxrs2-servlet-initializer-v2</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
|
<artifactId>swagger-annotations</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- Dependencies for log4j 2 -->
|
<!-- Dependencies for log4j 2 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.logging.log4j</groupId>
|
<groupId>org.apache.logging.log4j</groupId>
|
||||||
|
|
@ -292,6 +307,10 @@
|
||||||
<directory>src/main/resources</directory>
|
<directory>src/main/resources</directory>
|
||||||
<filtering>true</filtering>
|
<filtering>true</filtering>
|
||||||
</resource>
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>target/generated-resources/_admin</directory>
|
||||||
|
<targetPath>_admin</targetPath>
|
||||||
|
</resource>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<testResources>
|
<testResources>
|
||||||
|
|
@ -323,6 +342,10 @@
|
||||||
<trimStackTrace>false</trimStackTrace>
|
<trimStackTrace>false</trimStackTrace>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-site-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
<!--<plugin>
|
<!--<plugin>
|
||||||
<groupId>org.jacoco</groupId>
|
<groupId>org.jacoco</groupId>
|
||||||
<artifactId>jacoco-maven-plugin</artifactId>
|
<artifactId>jacoco-maven-plugin</artifactId>
|
||||||
|
|
@ -377,6 +400,143 @@
|
||||||
</execution>
|
</execution>
|
||||||
</executions>
|
</executions>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
|
<!-- <plugin>
|
||||||
|
<groupId>io.openapitools.swagger</groupId>
|
||||||
|
<artifactId>swagger-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<resourcePackages>
|
||||||
|
<resourcePackage>org.libreccm.api</resourcePackage>
|
||||||
|
<resourcePackage>org.libreccm.api.admin</resourcePackage>
|
||||||
|
<resourcePackage>org.libreccm.api.admin.categorization</resourcePackage>
|
||||||
|
<resourcePackage>org.libreccm.api.admin.categorization.dto</resourcePackage>
|
||||||
|
<resourcePackage>org.libreccm.api.admin.configuration</resourcePackage>
|
||||||
|
<resourcePackage>org.libreccm.api.admin.security</resourcePackage>
|
||||||
|
<resourcePackage>org.libreccm.api.admin.security.dto</resourcePackage>
|
||||||
|
</resourcePackages>
|
||||||
|
<outputDirectory>${project.basedir}/src/site/resources</outputDirectory>
|
||||||
|
<outputDirectory>${project.build.directory}/generated-resources/openapi</outputDirectory>
|
||||||
|
<outputFilename>ccm-core-api</outputFilename>
|
||||||
|
<outputFormats>JSON</outputFormats>
|
||||||
|
<prettyPrint>true</prettyPrint>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>generate-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>generate</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>-->
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>io.swagger.core.v3</groupId>
|
||||||
|
<artifactId>swagger-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<resourcePackages>
|
||||||
|
<package>org.libreccm.api</package>
|
||||||
|
<package>org.libreccm.api.admin</package>
|
||||||
|
<package>org.libreccm.api.admin.categorization</package>
|
||||||
|
<package>org.libreccm.api.admin.categorization.dto</package>
|
||||||
|
<package>org.libreccm.api.admin.configuration</package>
|
||||||
|
<package>org.libreccm.api.admin.security</package>
|
||||||
|
<package>org.libreccm.api.admin.security.dto</package>
|
||||||
|
</resourcePackages>
|
||||||
|
<!--<outputDirectory>${project.basedir}/src/site/resources</outputDirectory>-->
|
||||||
|
<outputPath>${project.build.directory}/generated-resources/openapi</outputPath>
|
||||||
|
<outputFileName>ccm-core-api</outputFileName>
|
||||||
|
<outputFormat>JSONANDYAML</outputFormat>
|
||||||
|
<prettyPrint>TRUE</prettyPrint>
|
||||||
|
</configuration>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>resolve</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.github.eirslett</groupId>
|
||||||
|
<artifactId>frontend-maven-plugin</artifactId>
|
||||||
|
|
||||||
|
<configuration>
|
||||||
|
<installDirectory>../node</installDirectory>
|
||||||
|
</configuration>
|
||||||
|
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>install-node-and-npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<nodeVersion>${nodeVersion}</nodeVersion>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>npm install ccm-admin-dashboard</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>install</arguments>
|
||||||
|
<workingDirectory>src/main/ccm-admin-dashboard</workingDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>build ccm-admin-dashboard</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>run build</arguments>
|
||||||
|
<workingDirectory>src/main/ccm-admin-dashboard</workingDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>npm install ccm-admin-systeminformation</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>install</arguments>
|
||||||
|
<workingDirectory>src/main/ccm-admin-systeminformation</workingDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>build ccm-admin-systeminformation</id>
|
||||||
|
<goals>
|
||||||
|
<goal>npm</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<arguments>run build</arguments>
|
||||||
|
<workingDirectory>src/main/ccm-admin-systeminformation</workingDirectory>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<!-- <plugin>
|
||||||
|
<groupId>io.swagger</groupId>
|
||||||
|
<artifactId>swagger-codegen-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>gen-api-doc</id>
|
||||||
|
<goals>
|
||||||
|
<goal>generate</goal>
|
||||||
|
</goals>
|
||||||
|
<phase>prepare-package</phase>
|
||||||
|
<configuration>
|
||||||
|
<inputSpec>${project.build.directory}/generated-resources/openapi/ccm-core-api.json</inputSpec>
|
||||||
|
<language>html</language>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>-->
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
@ -583,6 +743,7 @@
|
||||||
</reportSet>
|
</reportSet>
|
||||||
</reportSets>
|
</reportSets>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
|
||||||
</plugins>
|
</plugins>
|
||||||
</reporting>
|
</reporting>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
.cache
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# ccm-admin-commons
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/cli-plugin-babel/preset"]
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,75 @@
|
||||||
|
{
|
||||||
|
"name": "@libreccm/ccm-admin-commons",
|
||||||
|
"version": "7.0.0",
|
||||||
|
"private": true,
|
||||||
|
"types": "dist/main.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"bundle": "parcel build src/main.ts && tsc --emitDeclarationOnly --outDir dist",
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build --target lib --name ccm-admin-commons ./src/main.ts && tsc --emitDeclarationOnly --outDir dist",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^4.5.2",
|
||||||
|
"bootstrap-vue": "^2.16.0",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-class-component": "^7.2.3",
|
||||||
|
"vue-property-decorator": "^8.4.2",
|
||||||
|
"vue-router": "^3.2.0",
|
||||||
|
"vuex": "^3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||||
|
"@typescript-eslint/parser": "^2.33.0",
|
||||||
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-router": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||||
|
"@vue/cli-service": "~4.5.0",
|
||||||
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^5.0.2",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"node-sass": "^4.12.0",
|
||||||
|
"parcel-bundler": "^1.12.4",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"typescript": "~3.9.3",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"filenameHashing": false,
|
||||||
|
"publicPath": "./"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/typescript/recommended",
|
||||||
|
"@vue/prettier",
|
||||||
|
"@vue/prettier/@typescript-eslint"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2020
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
<template>
|
||||||
|
<b-navbar toggleable="lg" type="dark" variant="info">
|
||||||
|
<b-navbar-brand href="#">LibreCCM</b-navbar-brand>
|
||||||
|
|
||||||
|
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
||||||
|
|
||||||
|
<b-collapse id="nav-collapse" is-nav>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item href="#">Link 1</b-nav-item>
|
||||||
|
<b-nav-item href="#">Link 2</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-collapse>
|
||||||
|
</b-navbar>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class Navbar extends Vue {
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
|
||||||
|
import { BootstrapVue, IconsPlugin } from "bootstrap-vue";
|
||||||
|
import Navbar from "./components/Navbar.vue";
|
||||||
|
|
||||||
|
import "bootstrap/dist/css/bootstrap.css";
|
||||||
|
import "bootstrap-vue/dist/bootstrap-vue.css";
|
||||||
|
|
||||||
|
export { Navbar };
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Vue, { VNode } from "vue";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface Element extends VNode {}
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface ElementClass extends Vue {}
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[elem: string]: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
declare module "*.vue" {
|
||||||
|
import Vue from "vue";
|
||||||
|
export default Vue;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"declaration": true,
|
||||||
|
"importHelpers": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"types": [
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"tests/**/*.ts",
|
||||||
|
"tests/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# ccm-admin-dashboard
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/cli-plugin-babel/preset"]
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,76 @@
|
||||||
|
{
|
||||||
|
"name": "@libreccm/ccm-admin-dashboard",
|
||||||
|
"version": "7.0.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build --dest ../../../target/generated-resources/_admin/dashboard",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^4.5.2",
|
||||||
|
"bootstrap-vue": "^2.16.0",
|
||||||
|
"@libreccm/ccm-admin-commons": "^7.0.0",
|
||||||
|
"@libreccm/ccm-apiclient-commons": "^7.0.0",
|
||||||
|
"@libreccm/ccm-core-apiclient": "^7.0.0",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"jquery": "^3.5.1",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-class-component": "^7.2.3",
|
||||||
|
"vue-property-decorator": "^8.4.2",
|
||||||
|
"vue-router": "^3.2.0",
|
||||||
|
"vuex": "^3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||||
|
"@typescript-eslint/parser": "^2.33.0",
|
||||||
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-router": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||||
|
"@vue/cli-service": "~4.5.0",
|
||||||
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^5.0.2",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"node-sass": "^4.12.0",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"typescript": "~3.9.3",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"filenameHashing": false,
|
||||||
|
"publicPath": "./"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/typescript/recommended",
|
||||||
|
"@vue/prettier",
|
||||||
|
"@vue/prettier/@typescript-eslint"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2020
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div id="app" class="container">
|
||||||
|
<navbar></navbar>
|
||||||
|
<b-card-group deck>
|
||||||
|
<b-card v-for="app in apps" :key="app.slug" >
|
||||||
|
<!-- :title="app.label" -->
|
||||||
|
<b-card-title>
|
||||||
|
<b-icon :icon=app.iconName></b-icon>
|
||||||
|
{{app.label}}
|
||||||
|
</b-card-title>
|
||||||
|
<b-card-text>
|
||||||
|
{{ app.description }}
|
||||||
|
</b-card-text>
|
||||||
|
</b-card>
|
||||||
|
</b-card-group>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss"></style>
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
|
import { AdminApp } from "./admin-ui";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class App extends Vue {
|
||||||
|
get apps(): AdminApp[] {
|
||||||
|
const appsDataElem: Element | null = document.querySelector(
|
||||||
|
"#appsData"
|
||||||
|
);
|
||||||
|
if (appsDataElem && appsDataElem.textContent) {
|
||||||
|
const appsData: string = appsDataElem.textContent as string;
|
||||||
|
return JSON.parse(appsData);
|
||||||
|
} else {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
// const appElem: Element | null = document.querySelector("#app");
|
||||||
|
// if (appElem === null) {
|
||||||
|
// return [];
|
||||||
|
// }
|
||||||
|
|
||||||
|
// const appsValue: string | null = appElem.getAttribute("data-apps");
|
||||||
|
// if (appsValue === null) {
|
||||||
|
// return [];
|
||||||
|
// } else {
|
||||||
|
// return JSON.parse(appsValue);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
export interface AdminApp {
|
||||||
|
slug: String,
|
||||||
|
label: string,
|
||||||
|
description: string,
|
||||||
|
order: number,
|
||||||
|
jsFilesUrls: string[],
|
||||||
|
cssFilesUrls: string[],
|
||||||
|
iconName: string | undefined,
|
||||||
|
symbolUrl: string | undefined,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
import store from "./store";
|
||||||
|
|
||||||
|
import { BootstrapVue, IconsPlugin } from "bootstrap-vue";
|
||||||
|
|
||||||
|
import "bootstrap/dist/css/bootstrap.css";
|
||||||
|
import "bootstrap-vue/dist/bootstrap-vue.css";
|
||||||
|
|
||||||
|
import { SystemInformationClient } from "@libreccm/ccm-core-apiclient";
|
||||||
|
import Navbar from "@libreccm/ccm-admin-commons";
|
||||||
|
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
const foo: any = {};
|
||||||
|
const client: SystemInformationClient = new SystemInformationClient(foo);
|
||||||
|
|
||||||
|
Vue.use(BootstrapVue);
|
||||||
|
Vue.use(IconsPlugin);
|
||||||
|
Vue.use(Navbar);
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
render: h => h(App),
|
||||||
|
}).$mount("#app");
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import VueRouter, { RouteConfig } from "vue-router";
|
||||||
|
import Home from "../views/Home.vue";
|
||||||
|
|
||||||
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
const routes: Array<RouteConfig> = [
|
||||||
|
// {
|
||||||
|
// path: "/",
|
||||||
|
// name: "Home",
|
||||||
|
// component: Home
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// path: "/about",
|
||||||
|
// name: "About",
|
||||||
|
// // route level code-splitting
|
||||||
|
// // this generates a separate chunk (about.[hash].js) for this route
|
||||||
|
// // which is lazy-loaded when the route is visited.
|
||||||
|
// component: () =>
|
||||||
|
// import(/* webpackChunkName: "about" */ "../views/About.vue")
|
||||||
|
// }
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: "hash",
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Vue, { VNode } from "vue";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface Element extends VNode {}
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface ElementClass extends Vue {}
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[elem: string]: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
declare module "*.vue" {
|
||||||
|
import Vue from "vue";
|
||||||
|
export default Vue;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import Vuex from "vuex";
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
state: {},
|
||||||
|
mutations: {},
|
||||||
|
actions: {},
|
||||||
|
modules: {}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is an about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<img alt="Vue logo" src="../assets/logo.png" />
|
||||||
|
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
HelloWorld
|
||||||
|
}
|
||||||
|
})
|
||||||
|
export default class Home extends Vue {}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"importHelpers": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"types": [
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"tests/**/*.ts",
|
||||||
|
"tests/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
/dist
|
||||||
|
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
|
||||||
|
# Log files
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
# ccm-admin-systeminformation
|
||||||
|
|
||||||
|
## Project setup
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and hot-reloads for development
|
||||||
|
```
|
||||||
|
npm run serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### Compiles and minifies for production
|
||||||
|
```
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lints and fixes files
|
||||||
|
```
|
||||||
|
npm run lint
|
||||||
|
```
|
||||||
|
|
||||||
|
### Customize configuration
|
||||||
|
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
presets: ["@vue/cli-plugin-babel/preset"]
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"name": "ccm-admin-systeminformation",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"serve": "vue-cli-service serve",
|
||||||
|
"build": "vue-cli-service build --dest ../../../target/generated-resources/_admin/systeminformation",
|
||||||
|
"lint": "vue-cli-service lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"bootstrap": "^4.5.2",
|
||||||
|
"bootstrap-vue": "^2.16.0",
|
||||||
|
"core-js": "^3.6.5",
|
||||||
|
"vue": "^2.6.11",
|
||||||
|
"vue-class-component": "^7.2.3",
|
||||||
|
"vue-property-decorator": "^8.4.2",
|
||||||
|
"vue-router": "^3.2.0",
|
||||||
|
"vuex": "^3.4.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.33.0",
|
||||||
|
"@typescript-eslint/parser": "^2.33.0",
|
||||||
|
"@vue/cli-plugin-babel": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-router": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-typescript": "~4.5.0",
|
||||||
|
"@vue/cli-plugin-vuex": "~4.5.0",
|
||||||
|
"@vue/cli-service": "~4.5.0",
|
||||||
|
"@vue/eslint-config-prettier": "^6.0.0",
|
||||||
|
"@vue/eslint-config-typescript": "^5.0.2",
|
||||||
|
"eslint": "^6.7.2",
|
||||||
|
"eslint-plugin-prettier": "^3.1.3",
|
||||||
|
"eslint-plugin-vue": "^6.2.2",
|
||||||
|
"node-sass": "^4.12.0",
|
||||||
|
"prettier": "^1.19.1",
|
||||||
|
"sass-loader": "^8.0.2",
|
||||||
|
"typescript": "~3.9.3",
|
||||||
|
"vue-template-compiler": "^2.6.11"
|
||||||
|
},
|
||||||
|
"vue": {
|
||||||
|
"filenameHashing": false,
|
||||||
|
"publicPath": "./"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"plugin:vue/essential",
|
||||||
|
"eslint:recommended",
|
||||||
|
"@vue/typescript/recommended",
|
||||||
|
"@vue/prettier",
|
||||||
|
"@vue/prettier/@typescript-eslint"
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2020
|
||||||
|
},
|
||||||
|
"rules": {}
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
"> 1%",
|
||||||
|
"last 2 versions",
|
||||||
|
"not dead"
|
||||||
|
]
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
|
|
@ -0,0 +1,17 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||||
|
<title><%= htmlWebpackPlugin.options.title %></title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>
|
||||||
|
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
|
||||||
|
</noscript>
|
||||||
|
<div id="app"></div>
|
||||||
|
<!-- built files will be auto injected -->
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<div id="nav">
|
||||||
|
<router-link to="/">Home</router-link> |
|
||||||
|
<router-link to="/about">About</router-link>
|
||||||
|
</div>
|
||||||
|
<router-view />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
#app {
|
||||||
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
text-align: center;
|
||||||
|
color: #2c3e50;
|
||||||
|
}
|
||||||
|
|
||||||
|
#nav {
|
||||||
|
padding: 30px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-weight: bold;
|
||||||
|
color: #2c3e50;
|
||||||
|
|
||||||
|
&.router-link-exact-active {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
|
|
@ -0,0 +1,138 @@
|
||||||
|
<template>
|
||||||
|
<div class="hello">
|
||||||
|
<h1>{{ msg }}</h1>
|
||||||
|
<p>
|
||||||
|
For a guide and recipes on how to configure / customize this project,<br />
|
||||||
|
check out the
|
||||||
|
<a href="https://cli.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>vue-cli documentation</a
|
||||||
|
>.
|
||||||
|
</p>
|
||||||
|
<h3>Installed CLI Plugins</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-babel"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>babel</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-router"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>router</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-vuex"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>vuex</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-eslint"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>eslint</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-cli/tree/dev/packages/%40vue/cli-plugin-typescript"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>typescript</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Essential Links</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://vuejs.org" target="_blank" rel="noopener">Core Docs</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://forum.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>Forum</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://chat.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>Community Chat</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener"
|
||||||
|
>Twitter</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://news.vuejs.org" target="_blank" rel="noopener">News</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Ecosystem</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://router.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>vue-router</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://vuex.vuejs.org" target="_blank" rel="noopener">vuex</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/vue-devtools#vue-devtools"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>vue-devtools</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://vue-loader.vuejs.org" target="_blank" rel="noopener"
|
||||||
|
>vue-loader</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="https://github.com/vuejs/awesome-vue"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener"
|
||||||
|
>awesome-vue</a
|
||||||
|
>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||||
|
|
||||||
|
@Component
|
||||||
|
export default class HelloWorld extends Vue {
|
||||||
|
@Prop() private msg!: string;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||||
|
<style scoped lang="scss">
|
||||||
|
h3 {
|
||||||
|
margin: 40px 0 0;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 10px;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #42b983;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import App from "./App.vue";
|
||||||
|
import router from "./router";
|
||||||
|
import store from "./store";
|
||||||
|
|
||||||
|
import { BootstrapVue, IconsPlugin } from "bootstrap-vue";
|
||||||
|
|
||||||
|
import 'bootstrap/dist/css/bootstrap.css'
|
||||||
|
import 'bootstrap-vue/dist/bootstrap-vue.css'
|
||||||
|
|
||||||
|
Vue.config.productionTip = false;
|
||||||
|
|
||||||
|
Vue.use(BootstrapVue);
|
||||||
|
Vue.use(IconsPlugin);
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
router,
|
||||||
|
store,
|
||||||
|
render: h => h(App),
|
||||||
|
}).$mount("#app");
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import VueRouter, { RouteConfig } from "vue-router";
|
||||||
|
import Home from "../views/Home.vue";
|
||||||
|
import About from "../views/About.vue";
|
||||||
|
|
||||||
|
Vue.use(VueRouter);
|
||||||
|
|
||||||
|
const routes: Array<RouteConfig> = [
|
||||||
|
{
|
||||||
|
path: "/",
|
||||||
|
name: "Home",
|
||||||
|
component: Home
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/about",
|
||||||
|
name: "About",
|
||||||
|
component: About
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const router = new VueRouter({
|
||||||
|
mode: "hash",
|
||||||
|
routes
|
||||||
|
});
|
||||||
|
|
||||||
|
export default router;
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
import Vue, { VNode } from "vue";
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace JSX {
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface Element extends VNode {}
|
||||||
|
// tslint:disable no-empty-interface
|
||||||
|
interface ElementClass extends Vue {}
|
||||||
|
interface IntrinsicElements {
|
||||||
|
[elem: string]: any;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
declare module "*.vue" {
|
||||||
|
import Vue from "vue";
|
||||||
|
export default Vue;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import Vue from "vue";
|
||||||
|
import Vuex from "vuex";
|
||||||
|
|
||||||
|
Vue.use(Vuex);
|
||||||
|
|
||||||
|
export default new Vuex.Store({
|
||||||
|
state: {},
|
||||||
|
mutations: {},
|
||||||
|
actions: {},
|
||||||
|
modules: {}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
<template>
|
||||||
|
<div class="about">
|
||||||
|
<h1>This is an about page</h1>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<template>
|
||||||
|
<div class="home">
|
||||||
|
<b-navbar toggleable="lg" type="dark" variant="info">
|
||||||
|
<b-navbar-brand href="#">LibreCCM</b-navbar-brand>
|
||||||
|
|
||||||
|
<b-navbar-toggle target="nav-collapse"></b-navbar-toggle>
|
||||||
|
|
||||||
|
<b-collapse id="nav-collapse" is-nav>
|
||||||
|
<b-navbar-nav>
|
||||||
|
<b-nav-item href="#">Link 1</b-nav-item>
|
||||||
|
<b-nav-item href="#">Link 2</b-nav-item>
|
||||||
|
</b-navbar-nav>
|
||||||
|
</b-collapse>
|
||||||
|
</b-navbar>
|
||||||
|
<img alt="Vue logo" src="../assets/logo.png" />
|
||||||
|
<HelloWorld msg="Welcome to Your Vue.js + TypeScript App" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Vue } from "vue-property-decorator";
|
||||||
|
import HelloWorld from "@/components/HelloWorld.vue"; // @ is an alias to /src
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
HelloWorld,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class Home extends Vue {}
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"module": "esnext",
|
||||||
|
"strict": true,
|
||||||
|
"jsx": "preserve",
|
||||||
|
"importHelpers": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"types": [
|
||||||
|
"webpack-env"
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"src/*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"lib": [
|
||||||
|
"esnext",
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"scripthost"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.tsx",
|
||||||
|
"src/**/*.vue",
|
||||||
|
"tests/**/*.ts",
|
||||||
|
"tests/**/*.tsx"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -53,7 +53,6 @@ import java.util.stream.Stream;
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
* @author Jens Pelzetter
|
* @author Jens Pelzetter
|
||||||
* @version $Id$
|
|
||||||
*/
|
*/
|
||||||
public class SystemInformation {
|
public class SystemInformation {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
* MA 02110-1301 USA
|
* MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
package org.libreccm.core.api;
|
package org.libreccm.api;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
@ -28,6 +28,7 @@ public final class ApiConstants {
|
||||||
// Nothing
|
// Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static final String IDENTIFIER_PREFIX_ID = "ID-";
|
public static final String IDENTIFIER_PREFIX_ID = "ID-";
|
||||||
public static final String IDENTIFIER_PREFIX_UUID = "UUID-";
|
public static final String IDENTIFIER_PREFIX_UUID = "UUID-";
|
||||||
|
|
||||||
|
|
@ -16,19 +16,19 @@
|
||||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||||
* MA 02110-1301 USA
|
* MA 02110-1301 USA
|
||||||
*/
|
*/
|
||||||
package org.libreccm.core.api;
|
package org.libreccm.api;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
* @author <a href="mailto:jens.pelzetter@googlemail.com">Jens Pelzetter</a>
|
||||||
*/
|
*/
|
||||||
public class ExtractedIdentifier {
|
public class Identifier {
|
||||||
|
|
||||||
private final IdentifierType type;
|
private final IdentifierType type;
|
||||||
|
|
||||||
private final String identifier;
|
private final String identifier;
|
||||||
|
|
||||||
protected ExtractedIdentifier(
|
protected Identifier(
|
||||||
final IdentifierType type, final String identifier
|
final IdentifierType type, final String identifier
|
||||||
) {
|
) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue