Compare commits

...

137 Commits

Author SHA1 Message Date
Jens Pelzetter 43a9e73d33 Trying to get internal dependencies working
Former-commit-id: be89e4f360
2020-08-29 19:00:15 +02:00
Jens Pelzetter ee4618c67b Moved AdmimUi modules to /src/main, some small improvments
Former-commit-id: 4dbaa4fb83
2020-08-29 12:40:40 +02:00
Jens Pelzetter aefa2ae0d7 Some small fixes
Former-commit-id: fd2def3309
2020-08-28 21:25:54 +02:00
Jens Pelzetter 0cff7d753a Some bugfixes
Former-commit-id: 7c22396e28
2020-08-28 20:37:09 +02:00
Jens Pelzetter 02cb4ab656 Dashboard app for ccm-admin
Former-commit-id: 680e708e37
2020-08-28 14:49:38 +02:00
Jens Pelzetter 46ea2de206 Integration of Vue.js now working
Former-commit-id: 982f4f1fd5
2020-08-27 20:48:00 +02:00
Jens Pelzetter dfe36e2614 Redirect to login from new Admin UI app
Former-commit-id: a7e02d61b1
2020-08-26 15:17:00 +02:00
Jens Pelzetter 6041e355a5 Serving HTML and resources for Admin UI
Former-commit-id: fa7a74080d
2020-08-25 20:34:58 +02:00
Jens Pelzetter 1a81dccadd Java part of the Admin UI
Former-commit-id: a0ae1d1c64
2020-08-24 21:27:40 +02:00
Jens Pelzetter 8cd8f05054 Merge branch 'master' into restapi
Former-commit-id: c8138350b6
2020-08-21 17:23:55 +02:00
Jens Pelzetter 3a6da7c807 Rename, use Error subclasses instead of throwing strings
Former-commit-id: 83de873130
2020-08-14 20:00:31 +02:00
Jens Pelzetter 035bfa3731 Use Error subclasses instead of throwing strings
Former-commit-id: 2567bd1ee2
2020-08-14 18:47:21 +02:00
Jens Pelzetter 6fec8bbd1a Use Error subclasses instead of throwing strings
Former-commit-id: d0a881ec95
2020-08-14 17:28:27 +02:00
Jens Pelzetter c4d0ec6be0 Use Error subclasses instead of throwing strings
Former-commit-id: b92356b844
2020-08-14 17:16:06 +02:00
Jens Pelzetter 4452cb071d Use Error subclasses instead of throwing strings
Former-commit-id: 70568a65ff
2020-08-14 17:06:42 +02:00
Jens Pelzetter 734ca7623d typo
Former-commit-id: 5c0b0739ac
2020-08-14 16:43:40 +02:00
Jens Pelzetter aa7ae6520f Use Error subclasses instead of throwing strings
Former-commit-id: 6510eb65cb
2020-08-14 16:42:39 +02:00
Jens Pelzetter 554391e7fd Use Error subclasses instead of throwing strings
Former-commit-id: 676156d790
2020-08-14 11:52:24 +02:00
Jens Pelzetter 7e9242c005 Use Error subclasses instead of throwing strings
Former-commit-id: 0c659aa9a2
2020-08-14 07:14:48 +02:00
Jens Pelzetter 6ecf9993c7 Use Error subtypes instead of throwing strings
Former-commit-id: 66bc3d20bb
2020-08-13 14:43:34 +02:00
Jens Pelzetter 1149a20b89 Use Error instances for throw
Former-commit-id: 36755e9361
2020-08-12 19:11:06 +02:00
Jens Pelzetter fcd50df49d Use Error instances for throw
Former-commit-id: edcc000d57
2020-08-12 19:08:40 +02:00
Jens Pelzetter 93cb4ba102 ApiClientError class
Former-commit-id: 68e4208dc1
2020-08-12 13:53:38 +02:00
Jens Pelzetter 2f53cb4b67 Use Error for throw
Former-commit-id: 6dbc592026
2020-08-12 13:46:49 +02:00
Jens Pelzetter 259aacca67 Client for themes API finished
Former-commit-id: 344f8aa370
2020-08-11 17:27:34 +02:00
Jens Pelzetter 62faaf6384 More functions for the themes API
Former-commit-id: 37b2512ef2
2020-08-11 15:43:09 +02:00
Jens Pelzetter 910b61cd81 Additional endpoint for themes API for getting file info about a file in a theme
Former-commit-id: 09f020329e
2020-08-11 15:42:39 +02:00
Jens Pelzetter cdf82f4d06 Entity for TheneFileInfo
Former-commit-id: c436331e67
2020-08-10 20:57:21 +02:00
Jens Pelzetter 2e6c97b775 Implemented client functions for some of the API endpoints for theme management
Former-commit-id: 324574f314
2020-08-10 20:56:43 +02:00
Jens Pelzetter 34303af9fc JavaDoc
Former-commit-id: 39621f5811
2020-08-09 12:46:17 +02:00
Jens Pelzetter 3a72c79cd8 Update a theme by uploading a ZIP file
Former-commit-id: 413a6ab150
2020-08-09 12:33:58 +02:00
Jens Pelzetter 99a189534b Entities used by the RESTful API for managing themes
Former-commit-id: 589ca1a2c6
2020-08-08 19:27:57 +02:00
Jens Pelzetter 600a010ee9 Fixed wrong import
Former-commit-id: 3551dd4b6d
2020-08-08 13:34:58 +02:00
Jens Pelzetter de29bb6a3a Extended RESTful API for theme management
Former-commit-id: 3ef46aaf51
2020-08-07 17:33:41 +02:00
Jens Pelzetter 30cd9bae25 Optimizations for RESTful API for managing containers
Former-commit-id: 96dae5876b
2020-08-06 16:38:13 +02:00
Jens Pelzetter 429ceeea82 Optmiziations for the RESTful API managing containers of page models
Former-commit-id: 8472cb7e45
2020-08-06 16:21:30 +02:00
Jens Pelzetter e5d05e998d Started to refactor PageModelsApi to same structure used for other endpoints
Former-commit-id: 7819bd4146
2020-08-05 17:46:55 +02:00
Jens Pelzetter 35b86a224a Added methods with limit and offset parameter to page models repo
Former-commit-id: 1c34927e92
2020-08-05 17:46:24 +02:00
Jens Pelzetter 8b0e00dca8 Typos
Former-commit-id: 65eefb9f2f
2020-08-05 16:42:14 +02:00
Jens Pelzetter ac27d5ac61 Client for RESTful API for managing application instances
Former-commit-id: 96256d60ff
2020-08-05 16:41:38 +02:00
Jens Pelzetter d30cbc5ec7 Fixed a typo in a path param
Former-commit-id: 3738ff91a1
2020-08-05 16:37:05 +02:00
Jens Pelzetter 93e9063959 Methods renamed
Former-commit-id: 6b47fe0804
2020-08-05 16:36:15 +02:00
Jens Pelzetter 2ec5507c1a Fixed a typo in the a path of an endpoint
Former-commit-id: 781633c416
2020-08-05 16:32:50 +02:00
Jens Pelzetter 78b5eb4721 First part of API client for managing application instances
Former-commit-id: 102b984b5f
2020-08-04 18:08:37 +02:00
Jens Pelzetter 37cbfa0cf2 Removed unused import
Former-commit-id: 4b3126c56a
2020-08-04 16:42:29 +02:00
Jens Pelzetter 52ca67b4e3 Entities for RESTful API for managing application instances
Former-commit-id: 360afd6bdb
2020-08-04 16:41:54 +02:00
Jens Pelzetter cd34cdc597 Client for managing sites
Former-commit-id: d5901a160b
2020-08-03 17:36:49 +02:00
Jens Pelzetter fd5b69feed Rename to fit name pattern
Former-commit-id: dccf466e4d
2020-08-03 17:36:35 +02:00
Jens Pelzetter 5ff6a49819 Client for managing users with the RESTful API
Former-commit-id: d091c31d55
2020-08-03 17:04:55 +02:00
Jens Pelzetter 958f556307 Client for RESTful API for managing roles
Former-commit-id: 280af3f6e7
2020-08-02 13:11:49 +02:00
Jens Pelzetter dc928d32c9 Client for managing groups using the RESTful API
Former-commit-id: a6c4fbe02c
2020-08-02 11:45:09 +02:00
Jens Pelzetter c4cf6e8741 API Doc
Former-commit-id: 3a3de5dac7
2020-08-02 11:45:02 +02:00
Jens Pelzetter b2b8c62389 Small enhancements, including a method to build an identififer param based on the type of identifier
Former-commit-id: e72f901119
2020-08-02 11:42:11 +02:00
Jens Pelzetter ea1750ecd8 Fixed typo in path for endpoints
Former-commit-id: bffe5ca549
2020-08-02 11:40:39 +02:00
Jens Pelzetter 5867b26997 Fixed some minor problems
Former-commit-id: d51a0d04fe
2020-07-31 18:30:15 +02:00
Jens Pelzetter eb1948d817 Code documentation
Former-commit-id: 38d76fbcda
2020-07-31 18:30:00 +02:00
Jens Pelzetter cb0d700a24 First part of API client for managing groups
Former-commit-id: c9f879df94
2020-07-31 17:23:03 +02:00
Jens Pelzetter 1c5961a75c Split entities/security.ts into smaller files
Former-commit-id: e01f10c2aa
2020-07-31 17:22:41 +02:00
Jens Pelzetter d91b01fee7 Typo
Former-commit-id: 5741a04ac5
2020-07-30 18:54:55 +02:00
Jens Pelzetter 705210a5cc Build functions for entities for RESTful security API
Former-commit-id: 88fb1fb895
2020-07-30 18:54:20 +02:00
Jens Pelzetter bc6ddd90a2 Entitites for security RESTful API
Former-commit-id: 8e1dd0ae99
2020-07-29 20:42:13 +02:00
Jens Pelzetter d8c02b8917 Typos
Former-commit-id: 4f034241a2
2020-07-29 20:42:04 +02:00
Jens Pelzetter 42fb2eab80 Fixed a typo in the path of a endpoint
Former-commit-id: d20e3b91cd
2020-07-29 18:13:14 +02:00
Jens Pelzetter 026576eeed Client for RESTful for Im/Export
Former-commit-id: 1b8f00175c
2020-07-29 18:12:56 +02:00
Jens Pelzetter c8f1e568d8 Client for RESTful for managing the configuration of LibreCCM
Former-commit-id: 9b044a7e4a
2020-07-28 20:24:43 +02:00
Jens Pelzetter cc02865318 Formatting
Former-commit-id: f63b048826
2020-07-28 20:24:33 +02:00
Jens Pelzetter 9424f35c2d Configuration API, some general improvments for API client
Former-commit-id: 0b621bf7b4
2020-07-27 21:05:38 +02:00
Jens Pelzetter 53839265b6 Entities for configuration API
Former-commit-id: 4dc4e9855d
2020-07-27 17:53:05 +02:00
Jens Pelzetter 558d075931 Removed star import
Former-commit-id: 833faafbb6
2020-07-27 17:52:48 +02:00
Jens Pelzetter ea04e3ba19 RESTful API doc generation with swagger-codegen-maven-plugin
Former-commit-id: ae5a9d80aa
2020-07-27 17:52:16 +02:00
Jens Pelzetter 816c0649b9 Client for System Information RESTful API
Former-commit-id: b565805057
2020-07-26 12:52:16 +02:00
Jens Pelzetter 7ec1355a3b Client for categories API implemented
Former-commit-id: d05a72e9af
2020-07-26 11:28:43 +02:00
Jens Pelzetter 8865a4ef8a Better paths for CategoriesApi
Former-commit-id: c96a47990f
2020-07-26 11:28:21 +02:00
Jens Pelzetter 1f81207902 Migrated to offical OpenAPI Maven plugin
Former-commit-id: 79c6c7d812
2020-07-26 10:56:01 +02:00
Jens Pelzetter 52663e5b14 Implemented some more functions for the Core RESTful API
Former-commit-id: 79af35336c
2020-07-24 20:42:49 +02:00
Jens Pelzetter aae365f4f3 Fixed some typos
Former-commit-id: 445db26f33
2020-07-24 20:42:15 +02:00
Jens Pelzetter 78d9f512f6 Clients will be put into separate dir
Former-commit-id: 06d1e5b07c
2020-07-24 17:40:33 +02:00
Jens Pelzetter 77f7a76cd6 typo
Former-commit-id: 2c98dad058
2020-07-24 17:38:02 +02:00
Jens Pelzetter 529d7d0bde updateCategory, deleteCategory
Former-commit-id: af67ac1a97
2020-07-24 17:36:46 +02:00
Jens Pelzetter 8633bf3e53 Typo
Former-commit-id: 3e33913689
2020-07-24 17:36:38 +02:00
Jens Pelzetter 703e05e33d Better error handling
Former-commit-id: 0b440d649e
2020-07-24 17:14:38 +02:00
Jens Pelzetter 3add883c25 getCategories implemented for API client
Former-commit-id: b58517802d
2020-07-23 20:45:55 +02:00
Jens Pelzetter 2542eb4968 Utility functions for checking if an record has all required properties
Former-commit-id: af3c2b3da4
2020-07-23 20:25:23 +02:00
Jens Pelzetter 1b916fa7e9 Added a ok property to ApiResponse
Former-commit-id: 58fb0937d2
2020-07-23 19:10:17 +02:00
Jens Pelzetter 901375c4cc Current state of API client
Former-commit-id: aca84af322
2020-07-22 21:25:21 +02:00
Jens Pelzetter f1adb7f0ae Fixed some bugs
Former-commit-id: 736facc691
2020-07-21 21:05:11 +02:00
Jens Pelzetter 2e298bf267 Post impl for Node based API client
Former-commit-id: d90dd3eafa
2020-07-21 16:55:57 +02:00
Jens Pelzetter 3cb28232a6 Started implemention of ApiClient using the Node.js HTTP API
Former-commit-id: 5543dbbf39
2020-07-20 21:15:30 +02:00
Jens Pelzetter 4e046647c7 Fetch based client implemented
Former-commit-id: cabdae0c9e
2020-07-13 19:19:27 +02:00
Jens Pelzetter 5e4d73b44d Some more doc
Former-commit-id: df9b53680c
2020-07-12 12:16:59 +02:00
Jens Pelzetter 82a982ebf9 Cleanup of structure, documentation
Former-commit-id: 917961a570
2020-07-12 12:04:59 +02:00
Jens Pelzetter 1fdb60b045 Current status of ccm-apiclient-commons. Trying to figure best way for zero dependencies and supporting browsers and node environments
Former-commit-id: cdde8ce67a
2020-07-11 18:14:09 +02:00
Jens Pelzetter 6366cd60f3 Started implementation of API client for ccm-core
Former-commit-id: 01444aa09e
2020-07-10 20:48:01 +02:00
Jens Pelzetter 5423933d3e No longer using npm link for internal NPM modules, instead npm install ${filePath} is used.
Former-commit-id: 097412293f
2020-07-10 20:47:09 +02:00
Jens Pelzetter 61362d9448 RESTful API for Import and Export
Former-commit-id: 9aea37c68b
2020-07-07 16:49:38 +02:00
Jens Pelzetter ebf9d0d520 CcmApplicationsApi finished
Former-commit-id: bebe653cce
2020-07-03 17:53:39 +02:00
Jens Pelzetter d6758f478f RESTful endpoint for creating applications
Former-commit-id: 14f084eef7
2020-07-02 09:41:59 +02:00
Jens Pelzetter 5978b40989 Get methods for CcmApplicationsApi
Former-commit-id: 84c125128b
2020-07-01 21:21:10 +02:00
Jens Pelzetter 8dd3a112f7 Optimizations
Former-commit-id: 8a9ec963f4
2020-06-30 21:20:49 +02:00
Jens Pelzetter 40d1c34e86 Several optimizations
Former-commit-id: 88675a786a
2020-06-30 21:05:28 +02:00
Jens Pelzetter ecd4192aed Started implementation of RESTful API for application management
Former-commit-id: 98b082ca94
2020-06-30 21:04:55 +02:00
Jens Pelzetter af609f5c10 RESTful API for managing sites
Former-commit-id: f7571263da
2020-06-29 21:16:40 +02:00
Jens Pelzetter 4cc48686d5 RESTful API for PageModels
Former-commit-id: e7e5afab5c
2020-06-28 12:50:43 +02:00
Jens Pelzetter f7721dd41b Integrated SwaggerUI for docuementing RESTful API into Maven site
Former-commit-id: 477201b7e0
2020-06-21 13:12:20 +02:00
Jens Pelzetter 5d891c2c6d Improved paths for Configuration RESTful API
Former-commit-id: 11867e6fa2
2020-06-21 10:44:00 +02:00
Jens Pelzetter 4a64d03056 @Produces, @Consumes, @RequiresProvilege, @AuthorizedRequired and @Transactional annotations for ConfigurationApi
Former-commit-id: 31aa92f6ee
2020-06-20 16:49:43 +02:00
Jens Pelzetter 5f31e4ff45 RESTful API for configuration
Former-commit-id: 98f5d3201b
2020-06-20 16:47:18 +02:00
Jens Pelzetter 43b547e044 SystemInformation API
Former-commit-id: 0b9c2e9b27
2020-06-08 20:10:46 +02:00
Jens Pelzetter 264736422f RESTful API for Categories
Former-commit-id: 3b6f069895
2020-06-07 11:15:48 +02:00
Jens Pelzetter 813ff58daa Methods for deleting categories
Former-commit-id: 16bd34b7d7
2020-06-07 08:01:23 +02:00
Jens Pelzetter 8c6f68eb2b Update methods for CategoriesApi
Former-commit-id: 0c5c70e002
2020-06-06 18:09:43 +02:00
Jens Pelzetter a549db6035 Use Objects.equals where possible
Former-commit-id: 68f5b47da6
2020-06-06 17:59:58 +02:00
Jens Pelzetter acfa6b7b5b Some methods for the CategoriesApi
Former-commit-id: 4e688e4fcf
2020-06-06 17:04:33 +02:00
Jens Pelzetter ae8e504eaa Use UriInfo and URIBuilder for generating URI for created responses
Former-commit-id: a72ec4e8d9
2020-06-06 16:53:37 +02:00
Jens Pelzetter 407384a75a RESTful API for managing Domains
Former-commit-id: 10da2f6ef6
2020-06-06 16:15:15 +02:00
Jens Pelzetter 4a4e4beb78 Improvements for RESTful API
Former-commit-id: fe6e680733
2020-06-05 20:32:27 +02:00
Jens Pelzetter 2d15639124 All method definitions for the CategoriesApi are now in place
Former-commit-id: 8b3f843531
2020-06-05 07:21:35 +02:00
Jens Pelzetter 08cebebd02 More methods definitions for CategoriesApi
Former-commit-id: 6388ca7c62
2020-06-04 20:27:02 +02:00
Jens Pelzetter 9ceb480c23 DTOs for categories api
Former-commit-id: 39a5da0c04
2020-06-04 17:54:46 +02:00
Jens Pelzetter 94704d9e4a API for Category domains (only method definitions, no implementation yet)
Former-commit-id: 8fe5c38cb9
2020-06-04 12:01:44 +02:00
Jens Pelzetter ac6d3b3d65 Renaming
Former-commit-id: a93d0a66ce
2020-06-03 20:30:30 +02:00
Jens Pelzetter 568cfe243e Prepared API for domains and categories
Former-commit-id: 7f10899d45
2020-06-03 20:20:45 +02:00
Jens Pelzetter a467bcebb7 Replace obsolete import
Former-commit-id: ecd984f560
2020-06-03 20:20:29 +02:00
Jens Pelzetter b164d6e00b Replaced obsolete import
Former-commit-id: bfb5b1a44f
2020-06-03 20:20:09 +02:00
Jens Pelzetter 97c2ed46bf Fixed imports
Former-commit-id: 3550564f6e
2020-06-03 20:19:30 +02:00
Jens Pelzetter ba001cc04b API for roles
Former-commit-id: 7d787f98bd
2020-06-03 17:56:10 +02:00
Jens Pelzetter d2b702cb11 Moved duplicated methods into separate class, more implementations for RolesAPI
Former-commit-id: 632805d9ac
2020-05-31 16:52:56 +02:00
Jens Pelzetter f4fd2bab9a Reorg and more methods for RolesApi
Former-commit-id: 8fead43ead
2020-05-29 20:30:49 +02:00
Jens Pelzetter 76f510e84d Use DTO objects for API results instead of building JSON objects
Former-commit-id: 211467eb4e
2020-05-28 21:13:23 +02:00
Jens Pelzetter 73c0a45ce7 Started implementation of RESTful API endpoints for Roles
Former-commit-id: 7d6424dcd9
2020-05-27 21:37:34 +02:00
Jens Pelzetter a954ced804 Added JSON generate methods to some entities
Former-commit-id: 1d7f88829b
2020-05-27 21:37:09 +02:00
Jens Pelzetter 1a2c3087e3 Include overall number of results and other data in entpoints for retrieving all entities
Former-commit-id: c4dee6362c
2020-05-27 21:35:49 +02:00
Jens Pelzetter 38d541c18d More RESTful API
Former-commit-id: 81232cd19a
2020-05-26 15:18:17 +02:00
Jens Pelzetter 422f37747d Removed star import
Former-commit-id: ca2e2c3f97
2020-05-25 20:30:18 +02:00
Jens Pelzetter 141ce9f5a8 Formatting
Former-commit-id: 223555f416
2020-05-25 20:29:25 +02:00
Jens Pelzetter ec09fd98e4 RESTful API for managing users completed
Former-commit-id: b89c2a7076
2020-05-25 20:25:37 +02:00
Jens Pelzetter c2c50e5899 REST API for managing users
Former-commit-id: ce2147315d
2020-05-25 19:59:04 +02:00
247 changed files with 84177 additions and 5579 deletions

View File

@ -0,0 +1,11 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
};

View File

@ -0,0 +1,3 @@
dist
node_modules
target

View File

@ -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

View File

@ -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
}
}

View File

@ -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>

View File

@ -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);
}
}
}

View File

@ -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";
}
}

View File

@ -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>
}

View File

@ -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

View File

@ -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": {

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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"
}, },

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,11 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: [
'@typescript-eslint',
],
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
],
};

4
ccm-core-apiclient/.gitignore vendored 100644
View File

@ -0,0 +1,4 @@
dist
node_modules
target

2141
ccm-core-apiclient/package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

View File

@ -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
}
}

View File

@ -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>

View File

@ -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";

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -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}`
);
}
}
}
}

View File

@ -0,0 +1,5 @@
const ADMIN_API_PREFIX = "/api/admin";
export {
ADMIN_API_PREFIX
}

View File

@ -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,
};
}

View File

@ -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,
};
}

View File

@ -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,
};
}

View File

@ -0,0 +1,13 @@
/**
* Some basic entities
* @packageDocumentation
*/
/**
* Basic data about an `CcmObject`.
*/
export interface CcmObjectId {
objectId: number,
uuid: string,
displayName: string
}

View File

@ -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>),
};
}

View File

@ -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[]
}
}

View File

@ -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,
};
}

View File

@ -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,
};
}

View File

@ -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,
};
}

View File

@ -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
};
}

View File

@ -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,
};
}

View File

@ -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;
}

View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"module": "es6",
"moduleResolution": "node",
"outDir": "target/dist",
"declaration": true,
"sourceMap": true,
"strict": true,
"target": "es6"
},
"include": [
"src/main/typescript/**/*"
]
}

View File

@ -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>

View File

@ -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?

View File

@ -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/).

View File

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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 };

View File

@ -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;
}
}
}

View File

@ -0,0 +1,4 @@
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}

View File

@ -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"
]
}

View File

@ -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?

View File

@ -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/).

View File

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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,
}

View File

@ -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");

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -0,0 +1,4 @@
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}

View File

@ -0,0 +1,11 @@
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {}
});

View File

@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -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>

View File

@ -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"
]
}

View File

@ -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?

View File

@ -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/).

View File

@ -0,0 +1,3 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"]
};

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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");

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -0,0 +1,4 @@
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}

View File

@ -0,0 +1,11 @@
import Vue from "vue";
import Vuex from "vuex";
Vue.use(Vuex);
export default new Vuex.Store({
state: {},
mutations: {},
actions: {},
modules: {}
});

View File

@ -0,0 +1,5 @@
<template>
<div class="about">
<h1>This is an about page</h1>
</div>
</template>

View File

@ -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>

View File

@ -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"
]
}

View File

@ -53,7 +53,6 @@ import java.util.stream.Stream;
* *
* *
* @author Jens Pelzetter * @author Jens Pelzetter
* @version $Id$
*/ */
public class SystemInformation { public class SystemInformation {

View File

@ -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-";

View File

@ -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