diff --git a/back/.project b/back/.project index 182e0e3..bdecf66 100644 --- a/back/.project +++ b/back/.project @@ -15,10 +15,16 @@ + + edu.umd.cs.findbugs.plugin.eclipse.findbugsBuilder + + + org.eclipse.jdt.core.javanature org.eclipse.m2e.core.maven2Nature + edu.umd.cs.findbugs.plugin.eclipse.findbugsNature diff --git a/back/pom.xml b/back/pom.xml index bc40d14..e93e74b 100644 --- a/back/pom.xml +++ b/back/pom.xml @@ -20,7 +20,7 @@ kangaroo-and-rabbit archidata - 0.10.2 + 0.11.0 org.slf4j @@ -40,13 +40,13 @@ org.junit.jupiter junit-jupiter-api - 5.11.0-M1 + 5.11.0-M2 test org.junit.jupiter junit-jupiter-engine - 5.11.0-M1 + 5.11.0-M2 test diff --git a/back/src/org/kar/karso/WebLauncher.java b/back/src/org/kar/karso/WebLauncher.java index 7cea58c..0a1633e 100755 --- a/back/src/org/kar/karso/WebLauncher.java +++ b/back/src/org/kar/karso/WebLauncher.java @@ -12,11 +12,7 @@ import org.kar.archidata.UpdateJwtPublicKey; import org.kar.archidata.api.DataResource; import org.kar.archidata.backup.BackupEngine; import org.kar.archidata.backup.BackupEngine.StoreMode; -import org.kar.archidata.catcher.ExceptionCatcher; -import org.kar.archidata.catcher.FailException404API; -import org.kar.archidata.catcher.FailExceptionCatcher; -import org.kar.archidata.catcher.InputExceptionCatcher; -import org.kar.archidata.catcher.SystemExceptionCatcher; +import org.kar.archidata.catcher.GenericCatcher; import org.kar.archidata.db.DBConfig; import org.kar.archidata.filter.CORSFilter; import org.kar.archidata.filter.OptionFilter; @@ -121,11 +117,7 @@ public class WebLauncher { // global authentication system rc.register(KarsoAuthenticationFilter.class); // register exception catcher - rc.register(InputExceptionCatcher.class); - rc.register(SystemExceptionCatcher.class); - rc.register(FailExceptionCatcher.class); - rc.register(FailException404API.class); - rc.register(ExceptionCatcher.class); + GenericCatcher.addAll(rc); // add default resource: rc.register(DataResource.class); rc.register(ApplicationResource.class); diff --git a/back/src/org/kar/karso/api/UserResource.java b/back/src/org/kar/karso/api/UserResource.java index 619bb02..1810680 100755 --- a/back/src/org/kar/karso/api/UserResource.java +++ b/back/src/org/kar/karso/api/UserResource.java @@ -52,7 +52,7 @@ public class UserResource { private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class); @JsonInclude(JsonInclude.Include.NON_NULL) - public class UserOut { + public static class UserOut { public long id; public String login; @@ -276,8 +276,9 @@ public class UserResource { "FAIL Authentiocate-wrong email/login '" + login + "')"); } // Check the password: - final String passwodCheck = getSHA512("login='" + login + "';pass='" + user.password + "';date='" + time + "'"); - if (!passwodCheck.contentEquals(password)) { + final String passwordCheck = getSHA512( + "login='" + login + "';pass='" + user.password + "';date='" + time + "'"); + if (!passwordCheck.contentEquals(password)) { throw new FailException(Response.Status.PRECONDITION_FAILED, "Password error ..."); } LOGGER.debug(" ==> pass nearly all test : admin={} blocked={} removed={}", user.admin, user.blocked, diff --git a/back/src/org/kar/karso/migration/Initialization.java b/back/src/org/kar/karso/migration/Initialization.java index ced1e88..7202ebd 100644 --- a/back/src/org/kar/karso/migration/Initialization.java +++ b/back/src/org/kar/karso/migration/Initialization.java @@ -2,20 +2,25 @@ package org.kar.karso.migration; import java.util.List; +import org.kar.archidata.dataAccess.DataAccess; import org.kar.archidata.migration.MigrationSqlStep; +import org.kar.archidata.tools.UuidUtils; import org.kar.karso.model.Application; import org.kar.karso.model.ApplicationToken; import org.kar.karso.model.Right; import org.kar.karso.model.RightDescription; import org.kar.karso.model.Settings; import org.kar.karso.model.UserAuth; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public class Initialization extends MigrationSqlStep { + private static final Logger LOGGER = LoggerFactory.getLogger(Initialization.class); public static final int KARSO_INITIALISATION_ID = 1; - public static final List> CLASSES_BASE = List.of(Settings.class, UserAuth.class, Application.class, - ApplicationToken.class, RightDescription.class, Right.class); + public static final List> LIST_OF_COMMON_CLASSES = List.of(Settings.class, UserAuth.class, + Application.class, ApplicationToken.class, RightDescription.class, Right.class); @Override public String getName() { @@ -28,7 +33,7 @@ public class Initialization extends MigrationSqlStep { @Override public void generateStep() throws Exception { - for (final Class clazz : CLASSES_BASE) { + for (final Class clazz : LIST_OF_COMMON_CLASSES) { addClass(clazz); } @@ -44,10 +49,15 @@ public class Initialization extends MigrationSqlStep { (1, 'karadmin', '0ddcac5ede3f1300a1ce5948ab15112f2810130531d578ab8bc4dc131652d7cf7a3ff6e827eb957bff43bc2c65a6a1d46722e5b3a2343ac3176a33ea7250080b', 'admin@admin.ZZZ', 1); """); + final String data = UuidUtils.nextUUID().toString(); addAction(""" - INSERT INTO `user_link_application` (`object1Id`, `object2Id`) - VALUES ('1', '1'); - """); + INSERT INTO `user_link_application` (`uuid`, `object1Id`, `object2Id`) + VALUES (UUID_TO_BIN('%s'), '1', '1'); + """.formatted(data), "mysql"); + addAction(""" + INSERT INTO `user_link_application` (`uuid`, `object1Id`, `object2Id`) + VALUES ('%s', '1', '1'); + """.formatted(data), "sqlite"); addAction(""" INSERT INTO `settings` (`key`, `right`, `type`, `value`) VALUES ('SIGN_UP_ENABLE', 'rwr-r-', 'BOOLEAN', 'false'), @@ -83,4 +93,25 @@ public class Initialization extends MigrationSqlStep { display(); } + public static void dropAll() { + for (final Class element : LIST_OF_COMMON_CLASSES) { + try { + DataAccess.drop(element); + } catch (final Exception ex) { + LOGGER.error("Fail to drop table !!!!!!"); + ex.printStackTrace(); + } + } + } + + public static void cleanAll() { + for (final Class element : LIST_OF_COMMON_CLASSES) { + try { + DataAccess.cleanAll(element); + } catch (final Exception ex) { + LOGGER.error("Fail to clean table !!!!!!"); + ex.printStackTrace(); + } + } + } } diff --git a/back/src/org/kar/karso/migration/Migration20231015.java b/back/src/org/kar/karso/migration/Migration20231015.java index 2c244a8..0813335 100644 --- a/back/src/org/kar/karso/migration/Migration20231015.java +++ b/back/src/org/kar/karso/migration/Migration20231015.java @@ -18,7 +18,7 @@ public class Migration20231015 extends MigrationSqlStep { } @Override - public void generateStep() throws Exception { + public void generateStep() { for (final String elem : List.of("application", "applicationToken", "right", "rightDescription", "settings", "user", "user_link_application")) { //, "user_link_cover")) { @@ -30,7 +30,6 @@ public class Migration20231015 extends MigrationSqlStep { RENAME COLUMN `modify_date` TO `updatedAt`; """); } - display(); } } diff --git a/back/src/org/kar/karso/migration/Migration20231126.java b/back/src/org/kar/karso/migration/Migration20231126.java index 44a0e2c..e4cf1db 100644 --- a/back/src/org/kar/karso/migration/Migration20231126.java +++ b/back/src/org/kar/karso/migration/Migration20231126.java @@ -16,7 +16,7 @@ public class Migration20231126 extends MigrationSqlStep { } @Override - public void generateStep() throws Exception { + public void generateStep() { // update migration update (last one) addAction( """ diff --git a/back/src/org/kar/karso/migration/Migration20240515.java b/back/src/org/kar/karso/migration/Migration20240515.java index 5397ff3..156b6d4 100644 --- a/back/src/org/kar/karso/migration/Migration20240515.java +++ b/back/src/org/kar/karso/migration/Migration20240515.java @@ -23,7 +23,7 @@ public class Migration20240515 extends MigrationSqlStep { } @Override - public void generateStep() throws Exception { + public void generateStep() { // update migration update (last one) addAction(""" ALTER TABLE `user_link_application` ADD `uuid` binary(16) AFTER `id`; @@ -35,9 +35,13 @@ public class Migration20240515 extends MigrationSqlStep { elem.uuid = UuidUtils.nextUUID(); } for (final UUIDConversion elem : datas) { - DataAccess.update(elem, elem.id, List.of("uuid"), new OverrideTableName("data")); + DataAccess.update(elem, elem.id, List.of("uuid"), new OverrideTableName("user_link_application")); } }); + addAction(""" + ALTER TABLE `user_link_application` + CHANGE `id` `id` bigint; + """); addAction(""" ALTER TABLE `user_link_application` DROP PRIMARY KEY; """); @@ -52,7 +56,7 @@ public class Migration20240515 extends MigrationSqlStep { addAction(""" ALTER TABLE `user_link_application` - ADD `deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT 'Deleted state' AFTER `uuid`; + DROP `id`; """); addAction(""" diff --git a/back/test/src/test/kar/karso/TestUsers.java b/back/test/src/test/kar/karso/TestUsers.java new file mode 100644 index 0000000..afbc19f --- /dev/null +++ b/back/test/src/test/kar/karso/TestUsers.java @@ -0,0 +1,89 @@ +package test.kar.karso; + +import java.io.IOException; +import java.util.List; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.kar.archidata.db.DBEntry; +import org.kar.archidata.exception.RESTErrorResponseExeption; +import org.kar.archidata.model.GetToken; +import org.kar.archidata.tools.ConfigBaseVariable; +import org.kar.archidata.tools.RESTApi; +import org.kar.karso.migration.Initialization; +import org.kar.karso.model.DataGetToken; +import org.kar.karso.model.UserAuthGet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ExtendWith(StepwiseExtension.class) +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class TestUsers { + private final static Logger LOGGER = LoggerFactory.getLogger(TestUsers.class); + public final static String ENDPOINT_NAME = "users/"; + + static WebLauncherTest webInterface = null; + static RESTApi api = null; + + private static long idTest; + + @BeforeAll + public static void configureWebServer() throws InterruptedException, RESTErrorResponseExeption, IOException { + LOGGER.info("configure server ..."); + webInterface = new WebLauncherTest(); + LOGGER.info("Clean previous table"); + + try { + Initialization.dropAll(); + } catch (final Exception ex) { + ex.printStackTrace(); + LOGGER.error("plop: {}", ex.getLocalizedMessage()); + throw ex; + } + + LOGGER.info("Create DB"); + try { + webInterface.migrateDB(); + } catch (final Exception ex) { + ex.printStackTrace(); + LOGGER.error("Detect an error: {}", ex.getMessage()); + } + LOGGER.info("Start REST (BEGIN)"); + webInterface.process(); + LOGGER.info("Start REST (DONE)"); + api = new RESTApi(ConfigBaseVariable.apiAdress); + final GetToken result = api.post(GetToken.class, "users/get_token", + DataGetToken.generate("karadmin", "v1", "202515252", "adminA@666")); + api.setToken(result.jwt); + } + + @AfterAll + public static void stopWebServer() throws IOException { + LOGGER.info("Kill the web server"); + webInterface.stop(); + webInterface = null; + LOGGER.info("Remove the test db"); + DBEntry.closeAllForceMode(); + ConfigBaseVariable.clearAllValue(); + } + + @Order(1) + @Test + public void getsValue() throws RESTErrorResponseExeption, IOException, InterruptedException { + final List listUsers = api.gets(UserAuthGet.class, TestUsers.ENDPOINT_NAME); + Assertions.assertNotNull(listUsers); + Assertions.assertEquals(1, listUsers.size()); + Assertions.assertEquals(1, listUsers.get(0).id); + Assertions.assertEquals(true, listUsers.get(0).admin); + Assertions.assertEquals(false, listUsers.get(0).blocked); + Assertions.assertEquals(false, listUsers.get(0).avatar); + Assertions.assertEquals("karadmin", listUsers.get(0).login); + Assertions.assertEquals("admin@admin.ZZZ", listUsers.get(0).email); + } +} diff --git a/front/package.json b/front/package.json index 94804a1..188ce5c 100644 --- a/front/package.json +++ b/front/package.json @@ -21,31 +21,31 @@ }, "private": true, "dependencies": { - "@angular/animations": "^18.0.0", - "@angular/cdk": "^18.0.0", - "@angular/common": "^18.0.0", - "@angular/compiler": "^18.0.0", - "@angular/core": "^18.0.0", - "@angular/forms": "^18.0.0", - "@angular/material": "^18.0.0", - "@angular/platform-browser": "^18.0.0", - "@angular/platform-browser-dynamic": "^18.0.0", - "@angular/router": "^18.0.0", + "@angular/animations": "^18.0.1", + "@angular/cdk": "^18.0.1", + "@angular/common": "^18.0.1", + "@angular/compiler": "^18.0.1", + "@angular/core": "^18.0.1", + "@angular/forms": "^18.0.1", + "@angular/material": "^18.0.1", + "@angular/platform-browser": "^18.0.1", + "@angular/platform-browser-dynamic": "^18.0.1", + "@angular/router": "^18.0.1", "rxjs": "^7.8.1", "zone.js": "^0.14.6", "zod": "3.23.8", "@kangaroo-and-rabbit/kar-cw": "^0.4.0" }, "devDependencies": { - "@angular-devkit/build-angular": "^18.0.1", - "@angular-eslint/builder": "17.5.2", - "@angular-eslint/eslint-plugin": "17.5.2", - "@angular-eslint/eslint-plugin-template": "17.5.2", - "@angular-eslint/schematics": "17.5.2", - "@angular-eslint/template-parser": "17.5.2", - "@angular/cli": "^18.0.1", - "@angular/compiler-cli": "^18.0.0", - "@angular/language-service": "^18.0.0", + "@angular-devkit/build-angular": "^18.0.2", + "@angular-eslint/builder": "18.0.1", + "@angular-eslint/eslint-plugin": "18.0.1", + "@angular-eslint/eslint-plugin-template": "18.0.1", + "@angular-eslint/schematics": "18.0.1", + "@angular-eslint/template-parser": "18.0.1", + "@angular/cli": "^18.0.2", + "@angular/compiler-cli": "^18.0.1", + "@angular/language-service": "^18.0.1", "@playwright/test": "^1.44.1", "@types/jest": "^29.5.12", "jasmine": "^5.1.0", @@ -57,7 +57,7 @@ "karma-jasmine": "^5.1.0", "karma-jasmine-html-reporter": "^2.1.0", "karma-spec-reporter": "^0.0.36", - "prettier": "^3.2.5", + "prettier": "^3.3.0", "npm-check-updates": "^16.14.20", "tslib": "^2.6.2" } diff --git a/front/src/assets/images/ikon_red.svg b/front/src/assets/images/ikon_red.svg index cc0fb69..0b1243e 100644 --- a/front/src/assets/images/ikon_red.svg +++ b/front/src/assets/images/ikon_red.svg @@ -6,7 +6,7 @@ version="1.1" id="svg18" sodipodi:docname="ikon_red.svg" - inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, custom)" + inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -72,9 +72,9 @@ borderopacity="1.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" - inkscape:zoom="5.6" - inkscape:cx="42.410714" - inkscape:cy="110.625" + inkscape:zoom="7.9195959" + inkscape:cx="89.966711" + inkscape:cy="177.91312" inkscape:document-units="mm" inkscape:current-layer="layer1" showgrid="true" @@ -85,10 +85,18 @@ inkscape:window-x="0" inkscape:window-y="20" inkscape:window-maximized="1" - inkscape:pagecheckerboard="0"> + inkscape:pagecheckerboard="0" + inkscape:showpageshadow="2" + inkscape:deskcolor="#d1d1d1"> + id="grid4504" + originx="0" + originy="0" + spacingy="1" + spacingx="1" + units="px" + visible="true" /> @@ -114,11 +122,9 @@ transform="matrix(0.8407653,0,0,0.83753055,-37.28971,3.4402954)" aria-label="K"> + d="M 65.200545 279.95309 L 65.200545 341.55532 L 74.14964 341.55532 L 74.14964 321.02125 L 80.541851 314.17676 L 99.718483 341.55532 L 106.11069 336.07998 L 85.655619 308.7008 L 106.11069 286.7982 L 99.718483 279.95309 L 74.14964 307.33227 L 74.14964 279.95309 L 65.200545 279.95309 z M 69.586585 307.95792 C 71.270821 307.95521 72.163105 308.76519 72.164982 309.94037 C 72.166858 311.11555 71.276967 311.92813 69.592731 311.93085 C 67.946259 311.9335 67.061695 311.12357 67.059818 309.94839 C 67.057941 308.77322 67.940113 307.96057 69.586585 307.95792 z M 69.588429 309.10309 C 68.568824 309.10473 68.017826 309.4316 68.01865 309.94716 C 68.019473 310.46272 68.571283 310.78793 69.590887 310.78629 C 70.655808 310.78458 71.206973 310.45717 71.20615 309.94161 C 71.205327 309.42604 70.653349 309.10138 69.588429 309.10309 z M 70.651134 317.13779 C 71.466818 317.13648 72.177771 317.82535 72.179733 319.0536 C 72.180798 319.7208 71.940317 320.4034 71.472902 320.93487 L 70.70891 320.29194 C 71.010421 319.91995 71.221049 319.45722 71.220287 318.97956 C 71.219524 318.50191 71.038049 318.28194 70.788812 318.28234 C 70.441392 318.2829 70.351355 318.5789 70.155738 319.04928 L 69.884683 319.68666 C 69.681646 320.24045 69.259204 320.7412 68.541704 320.74236 C 67.726021 320.74367 67.075662 320.0017 67.073955 318.93267 C 67.072998 318.33371 67.298319 317.73449 67.720551 317.28649 L 68.424309 317.85414 C 68.175617 318.19572 68.032626 318.50667 68.033401 318.9919 C 68.033994 319.36341 68.192626 319.61369 68.479626 319.61323 C 68.774179 319.61276 68.886895 319.27856 69.059842 318.80063 L 69.315531 318.20151 C 69.556173 317.54909 69.956292 317.13891 70.651134 317.13779 z M 72.09983 325.98324 L 72.102289 327.23453 L 70.32845 328.18534 L 70.32968 328.76904 L 72.104747 328.76595 L 72.106591 329.88027 L 67.18213 329.88829 L 67.179057 328.13722 C 67.177386 327.09093 67.538843 326.22575 68.7095 326.22387 C 69.434552 326.2227 69.895571 326.57849 70.130538 327.10126 L 72.09983 325.98324 z M 68.711344 327.32338 C 68.227976 327.32416 68.061959 327.6353 68.062903 328.22668 L 68.064133 328.77274 L 69.445833 328.77027 L 69.445219 328.22422 C 69.444274 327.63284 69.194712 327.3226 68.711344 327.32338 z M 72.114581 335.0286 L 72.116425 336.2114 L 70.946159 336.5088 L 70.948618 338.00258 L 72.119499 338.30368 L 72.121342 339.44145 L 67.195038 337.91003 L 67.192579 336.57544 L 72.114581 335.0286 z M 70.078294 336.73771 L 69.625307 336.85248 C 69.134592 336.98216 68.560497 337.13471 68.039547 337.24921 L 68.039547 337.27945 C 68.560872 337.39992 69.13541 337.53513 69.626536 337.66323 L 70.079523 337.77614 L 70.078294 336.73771 z " /> void; export interface RESTAbort { - abort?: () => boolean; + abort?: () => boolean; } // Rest generic callback have a basic model to upload and download advancement. export interface RESTCallbacks { - progressUpload?: ProgressCallback; - progressDownload?: ProgressCallback; - abortHandle?: RESTAbort; + progressUpload?: ProgressCallback; + progressDownload?: ProgressCallback; + abortHandle?: RESTAbort; } export interface RESTRequestType { - restModel: RESTModel; - restConfig: RESTConfig; - data?: any; - params?: object; - queries?: object; - callback?: RESTCallbacks; + restModel: RESTModel; + restConfig: RESTConfig; + data?: any; + params?: object; + queries?: object; + callback?: RESTCallbacks; } function replaceAll(input, searchValue, replaceValue) { - return input.split(searchValue).join(replaceValue); + return input.split(searchValue).join(replaceValue); } function removeTrailingSlashes(input: string): string { - if (isNullOrUndefined(input)) { - return "undefined"; - } - return input.replace(/\/+$/, ""); + if (isNullOrUndefined(input)) { + return "undefined"; + } + return input.replace(/\/+$/, ""); } function removeLeadingSlashes(input: string): string { - if (isNullOrUndefined(input)) { - return ""; - } - return input.replace(/^\/+/, ""); + if (isNullOrUndefined(input)) { + return ""; + } + return input.replace(/^\/+/, ""); } export function RESTUrl({ - restModel, - restConfig, - params, - queries, + restModel, + restConfig, + params, + queries, }: RESTRequestType): string { - // Create the URL PATH: - let generateUrl = `${removeTrailingSlashes( - restConfig.server - )}/${removeLeadingSlashes(restModel.endPoint)}`; - if (params !== undefined) { - for (let key of Object.keys(params)) { - generateUrl = replaceAll(generateUrl, `{${key}}`, `${params[key]}`); + // Create the URL PATH: + let generateUrl = `${removeTrailingSlashes( + restConfig.server + )}/${removeLeadingSlashes(restModel.endPoint)}`; + if (params !== undefined) { + for (let key of Object.keys(params)) { + generateUrl = replaceAll(generateUrl, `{${key}}`, `${params[key]}`); + } + } + if ( + queries === undefined && + (restConfig.token === undefined || restModel.tokenInUrl !== true) + ) { + return generateUrl; + } + const searchParams = new URLSearchParams(); + if (queries !== undefined) { + for (let key of Object.keys(queries)) { + const value = queries[key]; + if (Array.isArray(value)) { + for (const element of value) { + searchParams.append(`${key}`, `${element}`); } + } else { + searchParams.append(`${key}`, `${value}`); + } } - if ( - queries === undefined && - (restConfig.token === undefined || restModel.tokenInUrl !== true) - ) { - return generateUrl; - } - const searchParams = new URLSearchParams(); - if (queries !== undefined) { - for (let key of Object.keys(queries)) { - const value = queries[key]; - if (Array.isArray(value)) { - for (const element of value) { - searchParams.append(`${key}`, `${element}`); - } - } else { - searchParams.append(`${key}`, `${value}`); - } - } - } - if (restConfig.token !== undefined && restModel.tokenInUrl === true) { - searchParams.append("Authorization", `Bearer ${restConfig.token}`); - } - return generateUrl + "?" + searchParams.toString(); + } + if (restConfig.token !== undefined && restModel.tokenInUrl === true) { + searchParams.append("Authorization", `Bearer ${restConfig.token}`); + } + return generateUrl + "?" + searchParams.toString(); } export function fetchProgress( - generateUrl: string, - { - method, - headers, - body, - }: { - method: HTTPRequestModel; - headers: any; - body: any; - }, - { progressUpload, progressDownload, abortHandle }: RESTCallbacks + generateUrl: string, + { + method, + headers, + body, + }: { + method: HTTPRequestModel; + headers: any; + body: any; + }, + { progressUpload, progressDownload, abortHandle }: RESTCallbacks ): Promise { - const xhr: { - io?: XMLHttpRequest; - } = { - io: new XMLHttpRequest(), - }; - return new Promise((resolve, reject) => { - // Stream the upload progress - if (progressUpload) { - xhr.io?.upload.addEventListener("progress", (dataEvent) => { - if (dataEvent.lengthComputable) { - progressUpload(dataEvent.loaded, dataEvent.total); - } - }); + const xhr: { + io?: XMLHttpRequest; + } = { + io: new XMLHttpRequest(), + }; + return new Promise((resolve, reject) => { + // Stream the upload progress + if (progressUpload) { + xhr.io?.upload.addEventListener("progress", (dataEvent) => { + if (dataEvent.lengthComputable) { + progressUpload(dataEvent.loaded, dataEvent.total); } - // Stream the download progress - if (progressDownload) { - xhr.io?.addEventListener("progress", (dataEvent) => { - if (dataEvent.lengthComputable) { - progressDownload(dataEvent.loaded, dataEvent.total); - } - }); + }); + } + // Stream the download progress + if (progressDownload) { + xhr.io?.addEventListener("progress", (dataEvent) => { + if (dataEvent.lengthComputable) { + progressDownload(dataEvent.loaded, dataEvent.total); } - if (abortHandle) { - abortHandle.abort = () => { - if (xhr.io) { - console.log(`Request abort on the XMLHttpRequest: ${generateUrl}`); - xhr.io.abort(); - return true; - } - console.log( - `Request abort (FAIL) on the XMLHttpRequest: ${generateUrl}` - ); - return false; - }; + }); + } + if (abortHandle) { + abortHandle.abort = () => { + if (xhr.io) { + console.log(`Request abort on the XMLHttpRequest: ${generateUrl}`); + xhr.io.abort(); + return true; } - // Check if we have an internal Fail: - xhr.io?.addEventListener("error", () => { - xhr.io = undefined; - reject(new TypeError("Failed to fetch")); - }); - - // Capture the end of the stream - xhr.io?.addEventListener("loadend", () => { - if (xhr.io?.readyState !== XMLHttpRequest.DONE) { - return; - } - if (xhr.io?.status === 0) { - //the stream has been aborted - reject(new TypeError("Fetch has been aborted")); - return; - } - // Stream is ended, transform in a generic response: - const response = new Response(xhr.io.response, { - status: xhr.io.status, - statusText: xhr.io.statusText, - }); - const headersArray = replaceAll( - xhr.io.getAllResponseHeaders().trim(), - "\r\n", - "\n" - ).split("\n"); - headersArray.forEach(function (header) { - const firstColonIndex = header.indexOf(":"); - if (firstColonIndex !== -1) { - const key = header.substring(0, firstColonIndex).trim(); - const value = header.substring(firstColonIndex + 1).trim(); - response.headers.set(key, value); - } else { - response.headers.set(header, ""); - } - }); - xhr.io = undefined; - resolve(response); - }); - xhr.io?.open(method, generateUrl, true); - if (!isNullOrUndefined(headers)) { - for (const [key, value] of Object.entries(headers)) { - xhr.io?.setRequestHeader(key, value as string); - } - } - xhr.io?.send(body); + console.log( + `Request abort (FAIL) on the XMLHttpRequest: ${generateUrl}` + ); + return false; + }; + } + // Check if we have an internal Fail: + xhr.io?.addEventListener("error", () => { + xhr.io = undefined; + reject(new TypeError("Failed to fetch")); }); + + // Capture the end of the stream + xhr.io?.addEventListener("loadend", () => { + if (xhr.io?.readyState !== XMLHttpRequest.DONE) { + return; + } + if (xhr.io?.status === 0) { + //the stream has been aborted + reject(new TypeError("Fetch has been aborted")); + return; + } + // Stream is ended, transform in a generic response: + const response = new Response(xhr.io.response, { + status: xhr.io.status, + statusText: xhr.io.statusText, + }); + const headersArray = replaceAll( + xhr.io.getAllResponseHeaders().trim(), + "\r\n", + "\n" + ).split("\n"); + headersArray.forEach(function (header) { + const firstColonIndex = header.indexOf(":"); + if (firstColonIndex !== -1) { + const key = header.substring(0, firstColonIndex).trim(); + const value = header.substring(firstColonIndex + 1).trim(); + response.headers.set(key, value); + } else { + response.headers.set(header, ""); + } + }); + xhr.io = undefined; + resolve(response); + }); + xhr.io?.open(method, generateUrl, true); + if (!isNullOrUndefined(headers)) { + for (const [key, value] of Object.entries(headers)) { + xhr.io?.setRequestHeader(key, value as string); + } + } + xhr.io?.send(body); + }); } export function RESTRequest({ - restModel, - restConfig, - data, - params, - queries, - callback, + restModel, + restConfig, + data, + params, + queries, + callback, }: RESTRequestType): Promise { - // Create the URL PATH: - let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries }); - let headers: any = {}; - if (restConfig.token !== undefined && restModel.tokenInUrl !== true) { - headers["Authorization"] = `Bearer ${restConfig.token}`; + // Create the URL PATH: + let generateUrl = RESTUrl({ restModel, restConfig, data, params, queries }); + let headers: any = {}; + if (restConfig.token !== undefined && restModel.tokenInUrl !== true) { + headers["Authorization"] = `Bearer ${restConfig.token}`; + } + if (restModel.accept !== undefined) { + headers["Accept"] = restModel.accept; + } + if (restModel.requestType !== HTTPRequestModel.GET) { + // if Get we have not a content type, the body is empty + if (restModel.contentType !== HTTPMimeType.MULTIPART) { + // special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****" + headers["Content-Type"] = restModel.contentType; } - if (restModel.accept !== undefined) { - headers["Accept"] = restModel.accept; + } + let body = data; + if (restModel.contentType === HTTPMimeType.JSON) { + body = JSON.stringify(data); + } else if (restModel.contentType === HTTPMimeType.MULTIPART) { + const formData = new FormData(); + for (const name in data) { + formData.append(name, data[name]); } - if (restModel.requestType !== HTTPRequestModel.GET) { - // if Get we have not a content type, the body is empty - if (restModel.contentType !== HTTPMimeType.MULTIPART) { - // special case of multi-part ==> no content type otherwise the browser does not set the ";bundary=--****" - headers["Content-Type"] = restModel.contentType; - } + body = formData; + } + return new Promise((resolve, reject) => { + let action: undefined | Promise = undefined; + if ( + isNullOrUndefined(callback) || + (isNullOrUndefined(callback.progressDownload) && + isNullOrUndefined(callback.progressUpload) && + isNullOrUndefined(callback.abortHandle)) + ) { + // No information needed: call the generic fetch interface + action = fetch(generateUrl, { + method: restModel.requestType, + headers, + body, + }); + } else { + // need progression information: call old fetch model (XMLHttpRequest) that permit to keep % upload and % download for HTTP1.x + action = fetchProgress( + generateUrl, + { + method: restModel.requestType ?? HTTPRequestModel.GET, + headers, + body, + }, + callback + ); } - let body = data; - if (restModel.contentType === HTTPMimeType.JSON) { - body = JSON.stringify(data); - } else if (restModel.contentType === HTTPMimeType.MULTIPART) { - const formData = new FormData(); - for (const name in data) { - formData.append(name, data[name]); - } - body = formData; - } - return new Promise((resolve, reject) => { - let action: undefined | Promise = undefined; - if ( - isNullOrUndefined(callback) || - (isNullOrUndefined(callback.progressDownload) && - isNullOrUndefined(callback.progressUpload) && - isNullOrUndefined(callback.abortHandle)) - ) { - // No information needed: call the generic fetch interface - action = fetch(generateUrl, { - method: restModel.requestType, - headers, - body, - }); - } else { - // need progression information: call old fetch model (XMLHttpRequest) that permit to keep % upload and % download for HTTP1.x - action = fetchProgress( - generateUrl, - { - method: restModel.requestType ?? HTTPRequestModel.GET, - headers, - body, - }, - callback - ); - } - action - .then((response: Response) => { - if (response.status >= 200 && response.status <= 299) { - const contentType = response.headers.get("Content-Type"); - if ( - !isNullOrUndefined(restModel.accept) && - restModel.accept !== contentType - ) { - reject({ - name: "Model accept type incompatible", - time: Date().toString(), - status: 901, - error: `REST check wrong type: ${restModel.accept} != ${contentType}`, - statusMessage: "Fetch error", - message: "rest-tools.ts Wrong type in the message return type", - } as RestErrorResponse); - } else if (contentType === HTTPMimeType.JSON) { - response - .json() - .then((value: any) => { - resolve({ status: response.status, data: value }); - }) - .catch((reason: any) => { - reject({ - name: "API serialization error", - time: Date().toString(), - status: 902, - error: `REST parse json fail: ${reason}`, - statusMessage: "Fetch parse error", - message: "rest-tools.ts Wrong message model to parse", - } as RestErrorResponse); - }); - } else { - resolve({ status: response.status, data: response.body }); - } - } else { - reject({ - name: "REST return no OK status", - time: Date().toString(), - status: response.status, - error: `${response.body}`, - statusMessage: "Fetch code error", - message: "rest-tools.ts Wrong return code", - } as RestErrorResponse); - } - }) - .catch((error: any) => { + action + .then((response: Response) => { + if (response.status >= 200 && response.status <= 299) { + const contentType = response.headers.get("Content-Type"); + if ( + !isNullOrUndefined(restModel.accept) && + restModel.accept !== contentType + ) { + reject({ + name: "Model accept type incompatible", + time: Date().toString(), + status: 901, + message: `REST Content type are not compatible: ${restModel.accept} != ${contentType}`, + statusMessage: "Fetch error", + error: "rest-tools.ts Wrong type in the message return type", + } as RestErrorResponse); + } else if (contentType === HTTPMimeType.JSON) { + response + .json() + .then((value: any) => { + resolve({ status: response.status, data: value }); + }) + .catch((reason: Error) => { reject({ - name: "Request fail", - time: Date(), - status: 999, - error: error, - statusMessage: "Fetch catch error", - message: "rest-tools.ts detect an error in the fetch request", + name: "API serialization error", + time: Date().toString(), + status: 902, + message: `REST parse json fail: ${reason}`, + statusMessage: "Fetch parse error", + error: "rest-tools.ts Wrong message model to parse", + } as RestErrorResponse); + }); + } else { + resolve({ status: response.status, data: response.body }); + } + } else { + // the answer is not correct not a 2XX + // clone the response to keep the raw data if case of error: + response + .clone() + .json() + .then((value: any) => { + if (isRestErrorResponse(value)) { + reject(value); + } else { + response + .text() + .then((dataError: string) => { + reject({ + name: "API serialization error", + time: Date().toString(), + status: 903, + message: `REST parse error json with wrong type fail. ${dataError}`, + statusMessage: "Fetch parse error", + error: "rest-tools.ts Wrong message model to parse", + } as RestErrorResponse); + }) + .catch((reason: any) => { + reject({ + name: "API serialization error", + time: Date().toString(), + status: response.status, + message: `unmanaged error model: ??? with error: ${reason}`, + statusMessage: "Fetch ERROR parse error", + error: "rest-tools.ts Wrong message model to parse", + } as RestErrorResponse); + }); + } + }) + .catch((reason: Error) => { + response + .text() + .then((dataError: string) => { + reject({ + name: "API serialization error", + time: Date().toString(), + status: response.status, + message: `unmanaged error model: ${dataError} with error: ${reason}`, + statusMessage: "Fetch ERROR TEXT parse error", + error: "rest-tools.ts Wrong message model to parse", + } as RestErrorResponse); + }) + .catch((reason: any) => { + reject({ + name: "API serialization error", + time: Date().toString(), + status: response.status, + message: `unmanaged error model: ??? with error: ${reason}`, + statusMessage: "Fetch ERROR TEXT FAIL", + error: "rest-tools.ts Wrong message model to parse", + } as RestErrorResponse); }); }); - }); + } + }) + .catch((error: Error) => { + if (isRestErrorResponse(error)) { + reject(error); + } else { + reject({ + name: "Request fail", + time: Date(), + status: 999, + message: error, + statusMessage: "Fetch catch error", + error: "rest-tools.ts detect an error in the fetch request", + }); + } + }); + }); } export function RESTRequestJson( - request: RESTRequestType, - checker?: (data: any) => data is TYPE + request: RESTRequestType, + checker?: (data: any) => data is TYPE ): Promise { - return new Promise((resolve, reject) => { - RESTRequest(request) - .then((value: ModelResponseHttp) => { - if (isNullOrUndefined(checker)) { - console.log(`Have no check of MODEL in API: ${RESTUrl(request)}`); - resolve(value.data); - } else if (checker === undefined || checker(value.data)) { - resolve(value.data); - } else { - reject({ - name: "Model check fail", - time: Date().toString(), - status: 950, - error: "REST Fail to verify the data", - statusMessage: "API cast ERROR", - message: "api.ts Check type as fail", - } as RestErrorResponse); - } - }) - .catch((reason: RestErrorResponse) => { - reject(reason); - }); - }); + return new Promise((resolve, reject) => { + RESTRequest(request) + .then((value: ModelResponseHttp) => { + if (isNullOrUndefined(checker)) { + console.log(`Have no check of MODEL in API: ${RESTUrl(request)}`); + resolve(value.data); + } else if (checker === undefined || checker(value.data)) { + resolve(value.data); + } else { + reject({ + name: "Model check fail", + time: Date().toString(), + status: 950, + error: "REST Fail to verify the data", + statusMessage: "API cast ERROR", + message: "api.ts Check type as fail", + } as RestErrorResponse); + } + }) + .catch((reason: RestErrorResponse) => { + reject(reason); + }); + }); } export function RESTRequestVoid(request: RESTRequestType): Promise { - return new Promise((resolve, reject) => { - RESTRequest(request) - .then((value: ModelResponseHttp) => { - resolve(); - }) - .catch((reason: RestErrorResponse) => { - reject(reason); - }); - }); + return new Promise((resolve, reject) => { + RESTRequest(request) + .then((value: ModelResponseHttp) => { + resolve(); + }) + .catch((reason: RestErrorResponse) => { + reject(reason); + }); + }); } diff --git a/front/src/styles.less b/front/src/styles.less index 8337a90..f32235d 100644 --- a/front/src/styles.less +++ b/front/src/styles.less @@ -2,12 +2,15 @@ .xdesktop { display: block; } + .xtablette { display: none; } + .xphone { display: none; } + .xmobile { display: none; } @@ -17,12 +20,15 @@ .xdesktop { display: none; } + .xtablette { display: block; } + .xphone { display: none; } + .xmobile { display: block; } @@ -32,12 +38,15 @@ .xdesktop { display: none; } + .xtablette { display: none; } + .xphone { display: block; } + .xmobile { display: block; } @@ -206,6 +215,7 @@ html { vertical-align: middle; } } + .square-button { // background: #b3d4fc; text-shadow: none; @@ -264,12 +274,15 @@ html { text-align: center; line-height: 36px; vertical-align: middle; + &:disabled { cursor: not-allowed; } + .material-icons { vertical-align: middle; } + background: rgba(0, 0, 0, 0); } @@ -302,6 +315,7 @@ label { height: 100%; } + .full-back { position: absolute; width: 100%; @@ -312,12 +326,9 @@ label { background-image: url('assets/images/ikon_red.svg'); background-repeat: no-repeat; - /* background-size: contain; - */ - background-size: 80%; background-attachment: fixed; background-position: 50% 50%; z-index: -1; -} +} \ No newline at end of file