[DEV] first vesion of karisic in react + chakra-ui

This commit is contained in:
Edouard DUPIN 2024-08-22 11:29:57 +02:00
parent 21d53b77f2
commit 1d4c547d89
123 changed files with 20545 additions and 8 deletions

View File

@ -1,4 +0,0 @@

View File

@ -29,7 +29,7 @@ public class WebLauncherLocal extends WebLauncher {
TrackResource.class, DataResource.class);
final AnalyzeApi api = new AnalyzeApi();
api.addAllApi(listOfResources);
TsGenerateApi.generateApi(api, "../front/src/app/back-api/");
TsGenerateApi.generateApi(api, "../front2/src/back-api/");
LOGGER.info("Generate APIs (DONE)");
}
@ -49,6 +49,7 @@ public class WebLauncherLocal extends WebLauncher {
ConfigBaseVariable.apiAdress = "http://0.0.0.0:19080/karusic/api/";
//ConfigBaseVariable.ssoAdress = "https://atria-soft.org/karso/api/";
ConfigBaseVariable.dbPort = "3906";
ConfigBaseVariable.testMode = "true";
}
try {
super.migrateDB();

View File

@ -1,9 +1,12 @@
package org.kar.karusic.api;
import java.util.List;
import java.util.Map;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.filter.GenericContext;
import org.kar.karusic.api.UserResourceModel.PartRight;
import org.kar.karusic.api.UserResourceModel.UserMe;
import org.kar.karusic.model.UserKarusic;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -20,7 +23,7 @@ import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
@Path("/users")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces(MediaType.APPLICATION_JSON)
public class UserResource {
private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class);
@ -74,10 +77,15 @@ public class UserResource {
@GET
@Path("me")
@RolesAllowed("USER")
public UserOut getMe(@Context final SecurityContext sc) {
public UserMe getMe(@Context final SecurityContext sc) {
LOGGER.debug("getMe()");
final GenericContext gc = (GenericContext) sc.getUserPrincipal();
LOGGER.debug("== USER ? {}", gc.userByToken);
return new UserOut(gc.userByToken.id, gc.userByToken.name);
return new UserMe(gc.userByToken.id, gc.userByToken.name, //
Map.of(gc.userByToken.name, //
Map.of("admin", PartRight.READ_WRITE, //
"user", PartRight.READ_WRITE), //
"karusic", //
Map.of("user", PartRight.READ)));
}
}

View File

@ -0,0 +1,12 @@
package org.kar.karusic.api.UserResourceModel;
import java.util.HashMap;
import org.kar.archidata.annotation.NoWriteSpecificMode;
@NoWriteSpecificMode
public class ModuleAuthorizations extends HashMap<String, PartRight> {
private static final long serialVersionUID = 1L;
public ModuleAuthorizations() {}
}

View File

@ -0,0 +1,29 @@
package org.kar.karusic.api.UserResourceModel;
import com.fasterxml.jackson.annotation.JsonValue;
public enum PartRight {
READ(1), //
WRITE(2), //
READ_WRITE(3);
private final int value;
PartRight(final int value) {
this.value = value;
}
@JsonValue
public int getValue() {
return this.value;
}
public static PartRight fromValue(final int value) {
for (final PartRight species : PartRight.values()) {
if (species.getValue() == value) {
return species;
}
}
throw new IllegalArgumentException("PartRight: Unknown value: " + value);
}
}

View File

@ -0,0 +1,24 @@
package org.kar.karusic.api.UserResourceModel;
import java.util.Map;
import org.kar.archidata.annotation.NoWriteSpecificMode;
import io.swagger.v3.oas.annotations.media.Schema;
@NoWriteSpecificMode
public class UserMe {
public long id;
public String login;
@Schema(description = "Map<EntityName, Map<PartName, Right>>")
public Map<String, Map<String, PartRight>> rights;
public UserMe() {}
public UserMe(final long id, final String login, final Map<String, Map<String, PartRight>> rights) {
this.id = id;
this.login = login;
this.rights = rights;
}
}

View File

@ -0,0 +1,31 @@
services:
kar_db_service:
image: mysql:latest
restart: always
environment:
- MYSQL_ROOT_PASSWORD=base_db_password
volumes:
- ./data:/var/lib/mysql
mem_limit: 300m
ports:
- 3906:3306
healthcheck:
test: ["CMD", "mysqladmin" ,"ping", "-h", "localhost"]
timeout: 10s
retries: 5
# perform a 1 minute grace to let the DB to perform the initialization
start_period: 1m
start_interval: 1m
kar_adminer_service:
image: adminer:latest
restart: always
depends_on:
kar_db_service:
condition: service_healthy
links:
- kar_db_service:db
ports:
- 4079:8080
mem_limit: 50m

27
front2/.storybook/main.ts Normal file
View File

@ -0,0 +1,27 @@
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
framework: {
name: '@storybook/react-vite',
options: {},
},
core: {
disableTelemetry: true,
builder: '@storybook/builder-vite',
},
stories: ['../src/**/*.@(mdx|stories.@(js|jsx|ts|tsx))'],
addons: ['@storybook/addon-links', '@storybook/addon-essentials'],
staticDirs: ['../public'],
typescript: {
reactDocgen: false,
},
docs: {},
};
export default config;

View File

@ -0,0 +1,16 @@
<style>
html {
background: transparent !important;
}
.docs-story > :first-child {
padding: 0;
}
.docs-story > * {
background: transparent !important;
}
#root #start-ui-storybook-wrapper {
min-height: 100vh;
}
</style>

View File

@ -0,0 +1,43 @@
import React from 'react';
import { Box } from '@chakra-ui/react';
import { ChakraProvider } from '@chakra-ui/react';
import { MemoryRouter } from 'react-router-dom';
import theme from '../src/theme';
// .storybook/preview.js
export const parameters = {
options: {
storySort: {
order: ['StyleGuide', 'Components', 'Fields', 'App Layout'],
},
},
actions: {},
layout: 'fullscreen',
backgrounds: { disable: true, grid: { disable: true } },
chakra: {
theme,
},
};
const DocumentationWrapper = ({ children }) => {
return (
<Box id="start-ui-storybook-wrapper" p="4" pb="8" flex="1">
{children}
</Box>
);
};
export const decorators = [
(Story, context) => (
<ChakraProvider theme={theme}>
{/* Using MemoryRouter to avoid route clashing with Storybook */}
<MemoryRouter>
<DocumentationWrapper>
<Story {...context} />
</DocumentationWrapper>
</MemoryRouter>
</ChakraProvider>
),
];

2
front2/LICENSE Normal file
View File

@ -0,0 +1,2 @@
Proprietary
@copyright Edouard Dupin 2024

6
front2/app-build.json Normal file
View File

@ -0,0 +1,6 @@
{
"display": "__DEVELOPMENT__",
"version": "__VERSION__",
"commit": "__COMMIT__",
"date": "__DATE__"
}

25
front2/build.js Normal file
View File

@ -0,0 +1,25 @@
const dayjs = require('dayjs');
const fs = require('fs');
const generateAppBuild = () => {
const getVersion = () => fs.readFileSync('version.txt', 'utf8');
const commit = process.env.VERCEL_GIT_COMMIT_SHA
? process.env.VERCEL_GIT_COMMIT_SHA
: getVersion();
const appBuildContent = {
display: `${dayjs().format('YYYY-MM-DD')}`,
version: `${commit} - ${dayjs().format()}`,
commit,
date: dayjs().format(),
};
fs.writeFileSync(
'./app-build.json',
JSON.stringify(appBuildContent, null, 2)
);
};
generateAppBuild();

0
front2/doc/.keep Normal file
View File

View File

@ -0,0 +1,2 @@
# URL for database connection
VITE_API_BASE_URL=api/

View File

@ -0,0 +1,103 @@
###############################################################
## Install dependency:
###############################################################
FROM node:latest AS dependency
# For pnpm
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
# copy the credential
COPY npmrc /root/.npmrc
COPY package.json pnpm-lock.yaml ./
COPY src/theme ./src/theme
# TODO: install only the production environment:
RUN pnpm install --prod=false
###############################################################
## Install sources
###############################################################
FROM dependency AS load_sources
# JUST to get the vertion of the application and his sha...
COPY build.js \
version.txt \
tsconfig.json \
tsconfig.node.json \
vite.config.mts \
.env.validator.js \
index.html \
./
COPY public public
COPY src src
#We are not in prod mode ==> we need to overwrite the production env.
ARG env=docker/.env.production
COPY ${env} .env
###############################################################
## Run the linter
###############################################################
FROM load_sources AS check
COPY .eslintrc.json app-build.json ./
# Run linter
RUN pnpm lint .
RUN pnpm tsc --noEmit
###############################################################
## Run the Unit test
###############################################################
FROM load_sources AS unittest
COPY vitest.config.mts app-build.json ./
# Run unit test
RUN pnpm test
###############################################################
## Build the story-book
###############################################################
FROM load_sources AS builder_storybook
COPY app-build.json ./app-build.json
COPY .storybook ./.storybook/
# build the storybook in static
RUN SKIP_ENV_VALIDATIONS=1 pnpm storybook:build
###############################################################
## Build the sources
###############################################################
FROM load_sources AS builder
# build in bundle mode all the application
RUN pnpm static:build
###############################################################
## Runner environment:
###############################################################
FROM httpd:latest AS runner
WORKDIR /app
# configure HTTP server (add a redirection on the index.html to manage new app model to re-find the generic page):
RUN sed -e '/DocumentRoot/,/Directory>/d' -i /usr/local/apache2/conf/httpd.conf
RUN sed -r 's|#LoadModule rewrite_module|LoadModule rewrite_module|' -i /usr/local/apache2/conf/httpd.conf
RUN echo '<VirtualHost *:80> \n\
ServerName my-app \n\
DocumentRoot "/usr/local/apache2/htdocs" \n\
<Directory "/usr/local/apache2/htdocs"> \n\
Options Indexes FollowSymLinks \n\
AllowOverride None \n\
Require all granted \n\
RewriteEngine on \n\
# Do not rewrite files or directories \n\
RewriteCond %{REQUEST_FILENAME} -f [OR] \n\
RewriteCond %{REQUEST_FILENAME} -d \n\
RewriteRule ^ - [L] \n\
# Rewrite everything else to index.html to allow HTML5 state links \n\
RewriteRule ^ app/index.html [L] \n\
</Directory> \n\
</VirtualHost> \n\
' >> /usr/local/apache2/conf/httpd.conf
# copy artifact build from the 'build environment'
COPY --from=builder /app/dist /usr/local/apache2/htdocs/app

78
front2/ikon.svg Normal file
View File

@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="256"
height="256"
viewBox="0 0 67.733333 67.733333"
version="1.1"
id="svg8"
inkscape:version="0.92.4 5da689c313, 2019-01-14"
sodipodi:docname="ikon.svg"
inkscape:export-filename="/home/heero/dev/perso/appl_pro/NoKomment/plugin/chrome/ikon.png"
inkscape:export-xdpi="7.1250005"
inkscape:export-ydpi="7.1250005">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="52.480467"
inkscape:cy="138.73493"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:snap-text-baseline="false"
inkscape:window-width="1918"
inkscape:window-height="1038"
inkscape:window-x="0"
inkscape:window-y="20"
inkscape:window-maximized="1">
<inkscape:grid
type="xygrid"
id="grid4504" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-229.26668)">
<g
aria-label="K"
transform="scale(1.0347881,0.96638145)"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;line-height:1.25;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:2.11376619"
id="text821">
<path
d="m 12.784421,241.62303 h 8.949095 v 27.37877 l 25.568842,-27.37877 6.39221,6.84469 -20.455074,21.90302 20.455074,27.37876 -6.39221,5.47576 -19.176632,-27.37877 -6.39221,6.84469 0,20.53408 h -8.949095 z"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:84.55024719px;font-family:'DejaVu Sans Mono';-inkscape-font-specification:'DejaVu Sans Mono, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;stroke-width:2.11376619;fill:#ff0000;fill-opacity:1"
id="path823"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccccc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

13
front2/index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Karusic</title>
<link rel="icon" href="/favicon.ico" />
</head>
<body className="flex flex-col">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

18
front2/knip.ts Normal file
View File

@ -0,0 +1,18 @@
import type { KnipConfig } from 'knip';
const config: KnipConfig = {
// Ignoring mostly shell binaries
ignoreBinaries: ['export', 'sleep'],
ignore: [
// Related to tests
'tests/**',
'**.conf.js',
'steps.d.ts',
'steps_file.js',
'env_ci/codecept.conf.js',
// Generic components are useful.
'src/components/**',
],
};
export default config;

108
front2/package.json Normal file
View File

@ -0,0 +1,108 @@
{
"name": "karusic",
"private": true,
"version": "0.0.1",
"description": "KAR web music application",
"author": {
"name": "Edouard DUPIN",
"email": "yui.heero@gmail.farm"
},
"license": "PROPRIETARY",
"engines": {
"node": ">=20"
},
"scripts": {
"update_packages": "ncu --upgrade",
"install_dependency": "pnpm install",
"test": "vitest run",
"test:watch": "vitest watch",
"build": "tsc && vite build",
"dev": "vite",
"pretty": "prettier -w .",
"lint": "pnpm tsc --noEmit",
"storybook": "storybook dev -p 3001",
"storybook:build": "storybook build && mv ./storybook-static ./public/storybook"
},
"lint-staged": {
"*.{ts,tsx,js,jsx,json}": "prettier --write"
},
"dependencies": {
"@chakra-ui/anatomy": "2.2.2",
"@chakra-ui/cli": "2.4.1",
"@chakra-ui/react": "2.8.2",
"@chakra-ui/theme-tools": "2.1.2",
"@dnd-kit/core": "6.1.0",
"@dnd-kit/modifiers": "7.0.0",
"@dnd-kit/sortable": "8.0.0",
"@dnd-kit/utilities": "3.2.2",
"@emotion/react": "11.13.0",
"@emotion/styled": "11.13.0",
"allotment": "1.20.2",
"css-mediaquery": "0.1.2",
"dayjs": "1.11.12",
"history": "5.3.0",
"react": "18.3.1",
"react-color-palette": "7.2.2",
"react-currency-input-field": "3.8.0",
"react-custom-scrollbars": "4.2.1",
"react-day-picker": "9.0.8",
"react-dom": "18.3.1",
"react-error-boundary": "4.0.13",
"react-focus-lock": "2.12.1",
"react-icons": "5.3.0",
"react-popper": "2.3.0",
"react-router-dom": "6.26.1",
"react-select": "5.8.0",
"react-simple-keyboard": "3.7.144",
"react-sticky-el": "2.1.0",
"react-use": "17.5.1",
"react-use-draggable-scroll": "0.4.7",
"react-virtuoso": "4.10.1",
"ts-pattern": "5.3.1",
"uuid": "10.0.0",
"zod": "3.23.8",
"zustand": "4.5.5"
},
"devDependencies": {
"@chakra-ui/styled-system": "2.9.2",
"@playwright/test": "1.46.0",
"@storybook/addon-actions": "8.2.9",
"@storybook/addon-essentials": "8.2.9",
"@storybook/addon-links": "8.2.9",
"@storybook/addon-mdx-gfm": "8.2.9",
"@storybook/react": "8.2.9",
"@storybook/react-vite": "8.2.9",
"@storybook/theming": "8.2.9",
"@testing-library/jest-dom": "6.4.8",
"@testing-library/react": "16.0.0",
"@testing-library/user-event": "14.5.2",
"@trivago/prettier-plugin-sort-imports": "4.3.0",
"@types/jest": "29.5.12",
"@types/node": "22.3.0",
"@types/react": "18.3.3",
"@types/react-dom": "18.3.0",
"@types/react-sticky-el": "1.0.7",
"@typescript-eslint/eslint-plugin": "8.1.0",
"@typescript-eslint/parser": "8.1.0",
"@vitejs/plugin-react": "4.3.1",
"eslint": "9.9.0",
"eslint-plugin-codeceptjs": "1.3.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-react": "7.35.0",
"eslint-plugin-react-hooks": "4.6.2",
"eslint-plugin-storybook": "0.8.0",
"jest": "29.7.0",
"jest-environment-jsdom": "29.7.0",
"knip": "5.27.2",
"lint-staged": "15.2.9",
"prettier": "3.3.3",
"puppeteer": "23.1.0",
"react-is": "18.3.1",
"storybook": "8.2.9",
"ts-node": "10.9.2",
"typescript": "5.5.4",
"vite": "5.4.1",
"vitest": "2.0.5",
"npm-check-updates": "^17.0.6"
}
}

View File

14157
front2/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

16
front2/prettier.config.js Normal file
View File

@ -0,0 +1,16 @@
// Using a JS file, allowing us to add comments
module.exports = {
// This plugins line is mandatory for the plugin to work with pnpm.
// https://github.com/trivago/prettier-plugin-sort-imports/blob/61d069711008c530f5a41ca4e254781abc5de358/README.md?plain=1#L89-L96
plugins: ['@trivago/prettier-plugin-sort-imports'],
endOfLine: 'lf',
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
arrowParens: 'always',
importOrder: ['^react$', '^(?!^react$|^@/|^[./]).*', '^@/(.*)$', '^[./]'],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
importOrderParserPlugins: ['jsx', 'typescript'],
};

BIN
front2/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

118
front2/src/App.tsx Normal file
View File

@ -0,0 +1,118 @@
import { useState } from 'react';
import { ChakraProvider, Select } from '@chakra-ui/react';
import {
Box,
Button,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Stack,
Text,
useDisclosure,
} from '@chakra-ui/react';
import { environment } from '@/environment';
import { App as SpaApp } from '@/scene/App';
import { USERS } from '@/service/session';
import theme from '@/theme';
import { hashLocalData } from '@/utils/sso';
const AppEnvHint = () => {
const modal = useDisclosure();
const [selectUserTest, setSelectUserTest] = useState<string>('NO_USER');
//const setUser = useRightsStore((store) => store.setUser);
const buildEnv =
process.env.NODE_ENV === 'development'
? 'Development'
: import.meta.env.VITE_DEV_ENV_NAME;
const envName: Array<string> = [];
!!buildEnv && envName.push(buildEnv);
if (!envName.length) {
return null;
}
const handleChange = (selectedOption) => {
console.log(`SELECT: [${selectedOption.target.value}]`);
setSelectUserTest(selectedOption.target.value);
};
const onClose = () => {
modal.onClose();
if (selectUserTest == 'NO_USER') {
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/false/__LOGOUT__`;
} else {
window.location.href = `/${environment.applName}/sso/${hashLocalData()}/true/${USERS[selectUserTest]}`;
}
};
return (
<>
<Box
zIndex="100000"
position="fixed"
top="0"
insetStart="0"
insetEnd="0"
h="2px"
bg="warning.400"
as="button"
cursor="pointer"
data-test-id="devtools"
onClick={modal.onOpen}
>
<Text
position="fixed"
top="0"
insetStart="4"
bg="warning.400"
color="warning.900"
fontSize="0.6rem"
fontWeight="bold"
px="10px"
marginLeft="25%"
borderBottomStartRadius="sm"
borderBottomEndRadius="sm"
textTransform="uppercase"
>
{envName.join(' : ')}
</Text>
</Box>
<Modal isOpen={modal.isOpen} onClose={modal.onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>Outils développeurs</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Stack>
<Text>Utilisateur</Text>
<Select placeholder="Select test user" onChange={handleChange}>
{Object.keys(USERS).map((key) => (
<option value={key} key={key}>
{key}
</option>
))}
</Select>
</Stack>
</ModalBody>
<ModalFooter>
<Button onClick={onClose}>Apply</Button>
</ModalFooter>
</ModalContent>
</Modal>
</>
);
};
const App = () => {
return (
<ChakraProvider theme={theme}>
<AppEnvHint />
<SpaApp />
</ChakraProvider>
);
};
export default App;

View File

@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
width="499"
height="498"
viewBox="0 0 499 498"
version="1.1"
id="svg10"
sodipodi:docname="avatar_generic.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04, 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"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs14" />
<sodipodi:namedview
id="namedview12"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
showgrid="false"
inkscape:zoom="3.5060241"
inkscape:cx="344.97766"
inkscape:cy="288.78866"
inkscape:window-width="3838"
inkscape:window-height="2118"
inkscape:window-x="0"
inkscape:window-y="20"
inkscape:window-maximized="1"
inkscape:current-layer="svg10" />
<rect
style="fill:#4c83e5;fill-opacity:1;stroke:none;stroke-width:8.82841587;stroke-opacity:1"
width="500"
height="500"
x="0"
y="-1.9999847"
id="rect2" />
<path
style="fill:#4e4e4d;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1"
d="m 198.78572,292.40268 -11.97951,28.10434 -7.03868,-39.62542 -32.77188,20.51375 14.30166,-59.1095 -47.70972,20.8692 30.95257,-58.44261 -40.08325,-11.9709 50.99682,-31.08004 -30.32488,-33.92052 41.38608,6.88643 c 0,0 -27.42157,-58.130582 -26.08007,-58.130582 1.34149,0 54.85161,37.962212 54.85161,37.962212 l 6.10019,-52.959427 22.55992,46.026947 33.20732,-51.718401 8.75817,54.113371 53.78031,-55.134502 -14.88381,76.635492 112.00146,-17.67965 -84.26404,54.10353 65.61018,10.26713 -53.91421,37.51917 40.05564,55.00796 -51.48529,-23.57551 7.49544,56.99322 -27.79947,-24.64556 -3.80452,36.62241 -23.37869,-22.14564 -5.89389,23.82189 -20.64297,-29.48769 -15.46209,46.92367 -7.2608,-54.46889 -21.32424,51.07849 z"
id="path3162"
sodipodi:nodetypes="cccccccccccsccccccccccccccccccccccc" />
<path
style="fill:#fbd0a6;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 126.90172,351.92657 55.90379,145.23242 168.77912,0.48055 8.10502,-147.83696 -56.19339,-10.73582 -9.91368,3.09728 -8.25753,-48.27446 -8.77147,-41.82541 -73.97306,-0.86753 -4.65072,84.85557 z"
id="path3467"
sodipodi:nodetypes="ccccccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 45.319305,524.24985 50.878125,-168.64351 91.02866,-21.85078 124.81002,189.43887 15.4976,-12.33208 -130.63444,-191.36234 -9.67318,14.25555 124.81002,189.43887"
id="path275"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="M 443.88287,524.02559 393.00474,355.38208 301.97608,333.5313 177.16606,522.97017 161.66846,510.63809 292.3029,319.27575 301.97608,333.5313 177.16606,522.97017"
id="path275-6"
sodipodi:nodetypes="cccccccc" />
<path
style="fill:#fbd0a6;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 187.52213,139.10279 14.34593,25.22547 9.57434,-32.06638 13.85516,33.79118 18.44245,-38.89028 4.92331,44.11511 20.28515,-38.50102 -2.21466,39.35905 31.27764,-28.90273 -5.47875,45.83312 23.27252,-10.25342 -8.67174,30.59353 c 24.86464,-33.77835 23.21015,12.27629 3.94365,35.3922 l -12.95127,26.98572 -12.80079,22.10524 -26.61623,18.28625 -53.44338,-20.79546 c -21.13665,-20.42844 -23.443,-25.48798 -31.95313,-51.61993 -19.36867,-32.18928 -17.20493,-59.66994 4.27858,-38.00437 l -2.30548,-27.96686 11.07502,10.22035 -14.60569,-33.15484 22.1057,18.70679 z"
id="path9531"
sodipodi:nodetypes="cccccccccccccccccccccccc" />
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,250 @@
/**
* Interface of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
RESTCallbacks,
RESTConfig,
RESTRequestJson,
RESTRequestVoid,
} from "../rest-tools";
import { z as zod } from "zod"
import {
Album,
AlbumWrite,
Long,
UUID,
ZodAlbum,
isAlbum,
} from "../model";
export namespace AlbumResource {
/**
* Add a Track on a specific album
*/
export function addTrack({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
trackId: Long,
id: Long,
},
}): Promise<Album> {
return RESTRequestJson({
restModel: {
endPoint: "/album/{id}/track/{trackId}",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isAlbum);
};
/**
* Get a specific Album with his ID
*/
export function get({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<Album> {
return RESTRequestJson({
restModel: {
endPoint: "/album/{id}",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isAlbum);
};
export const ZodGetsTypeReturn = zod.array(ZodAlbum);
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
try {
ZodGetsTypeReturn.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
return false;
}
}
/**
* Get all the available Albums
*/
export function gets({
restConfig,
}: {
restConfig: RESTConfig,
}): Promise<GetsTypeReturn> {
return RESTRequestJson({
restModel: {
endPoint: "/album/",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
}, isGetsTypeReturn);
};
/**
* Update a specific album
*/
export function patch({
restConfig,
params,
data,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: AlbumWrite,
}): Promise<Album> {
return RESTRequestJson({
restModel: {
endPoint: "/album/{id}",
requestType: HTTPRequestModel.PATCH,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
}, isAlbum);
};
/**
* Add an album (when all the data already exist)
*/
export function post({
restConfig,
data,
}: {
restConfig: RESTConfig,
data: AlbumWrite,
}): Promise<Album> {
return RESTRequestJson({
restModel: {
endPoint: "/album/",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
data,
}, isAlbum);
};
/**
* Remove a specific album
*/
export function remove({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<void> {
return RESTRequestVoid({
restModel: {
endPoint: "/album/{id}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
},
restConfig,
params,
});
};
/**
* Remove a cover on a specific album
*/
export function removeCover({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
coverId: UUID,
id: Long,
},
}): Promise<Album> {
return RESTRequestJson({
restModel: {
endPoint: "/album/{id}/cover/{coverId}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isAlbum);
};
/**
* Remove a Track on a specific album
*/
export function removeTrack({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
trackId: Long,
id: Long,
},
}): Promise<Album> {
return RESTRequestJson({
restModel: {
endPoint: "/album/{id}/track/{trackId}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isAlbum);
};
/**
* Add a cover on a specific album
*/
export function uploadCover({
restConfig,
params,
data,
callbacks,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: {
file: File,
},
callbacks?: RESTCallbacks,
}): Promise<Album> {
return RESTRequestJson({
restModel: {
endPoint: "/album/{id}/cover",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
callbacks,
}, isAlbum);
};
}

View File

@ -0,0 +1,181 @@
/**
* Interface of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
RESTCallbacks,
RESTConfig,
RESTRequestJson,
RESTRequestVoid,
} from "../rest-tools";
import { z as zod } from "zod"
import {
Artist,
ArtistWrite,
Long,
UUID,
ZodArtist,
isArtist,
} from "../model";
export namespace ArtistResource {
export function get({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<Artist> {
return RESTRequestJson({
restModel: {
endPoint: "/artist/{id}",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isArtist);
};
export const ZodGetsTypeReturn = zod.array(ZodArtist);
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
try {
ZodGetsTypeReturn.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
return false;
}
}
export function gets({
restConfig,
}: {
restConfig: RESTConfig,
}): Promise<GetsTypeReturn> {
return RESTRequestJson({
restModel: {
endPoint: "/artist/",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
}, isGetsTypeReturn);
};
export function patch({
restConfig,
params,
data,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: ArtistWrite,
}): Promise<Artist> {
return RESTRequestJson({
restModel: {
endPoint: "/artist/{id}",
requestType: HTTPRequestModel.PATCH,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
}, isArtist);
};
export function post({
restConfig,
data,
}: {
restConfig: RESTConfig,
data: ArtistWrite,
}): Promise<Artist> {
return RESTRequestJson({
restModel: {
endPoint: "/artist/",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
data,
}, isArtist);
};
export function remove({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<void> {
return RESTRequestVoid({
restModel: {
endPoint: "/artist/{id}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
},
restConfig,
params,
});
};
export function removeCover({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
coverId: UUID,
id: Long,
},
}): Promise<Artist> {
return RESTRequestJson({
restModel: {
endPoint: "/artist/{id}/cover/{coverId}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isArtist);
};
export function uploadCover({
restConfig,
params,
data,
callbacks,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: {
file: File,
},
callbacks?: RESTCallbacks,
}): Promise<Artist> {
return RESTRequestJson({
restModel: {
endPoint: "/artist/{id}/cover",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
callbacks,
}, isArtist);
};
}

View File

@ -0,0 +1,128 @@
/**
* Interface of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
RESTConfig,
RESTRequestJson,
RESTRequestVoid,
} from "../rest-tools";
import {
UUID,
} from "../model";
export namespace DataResource {
/**
* Get back some data from the data environment (with a beautiful name (permit download with basic name)
*/
export function retrieveDataFull({
restConfig,
queries,
params,
data,
}: {
restConfig: RESTConfig,
queries: {
Authorization?: string,
},
params: {
name: string,
uuid: UUID,
},
data: string,
}): Promise<object> {
return RESTRequestJson({
restModel: {
endPoint: "/data/{uuid}/{name}",
requestType: HTTPRequestModel.GET,
},
restConfig,
params,
queries,
data,
});
};
/**
* Get back some data from the data environment
*/
export function retrieveDataId({
restConfig,
queries,
params,
data,
}: {
restConfig: RESTConfig,
queries: {
Authorization?: string,
},
params: {
uuid: UUID,
},
data: string,
}): Promise<object> {
return RESTRequestJson({
restModel: {
endPoint: "/data/{uuid}",
requestType: HTTPRequestModel.GET,
},
restConfig,
params,
queries,
data,
});
};
/**
* Get a thumbnail of from the data environment (if resize is possible)
*/
export function retrieveDataThumbnailId({
restConfig,
queries,
params,
data,
}: {
restConfig: RESTConfig,
queries: {
Authorization?: string,
},
params: {
uuid: UUID,
},
data: string,
}): Promise<object> {
return RESTRequestJson({
restModel: {
endPoint: "/data/thumbnail/{uuid}",
requestType: HTTPRequestModel.GET,
},
restConfig,
params,
queries,
data,
});
};
/**
* Insert a new data in the data environment
*/
export function uploadFile({
restConfig,
data,
}: {
restConfig: RESTConfig,
data: {
file: File,
},
}): Promise<void> {
return RESTRequestVoid({
restModel: {
endPoint: "/data//upload/",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
},
restConfig,
data,
});
};
}

View File

@ -0,0 +1,6 @@
/**
* Interface of the server (auto-generated code)
*/
export namespace Front {
}

View File

@ -0,0 +1,181 @@
/**
* Interface of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
RESTCallbacks,
RESTConfig,
RESTRequestJson,
RESTRequestVoid,
} from "../rest-tools";
import { z as zod } from "zod"
import {
Gender,
GenderWrite,
Long,
UUID,
ZodGender,
isGender,
} from "../model";
export namespace GenderResource {
export function get({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<Gender> {
return RESTRequestJson({
restModel: {
endPoint: "/gender/{id}",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isGender);
};
export const ZodGetsTypeReturn = zod.array(ZodGender);
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
try {
ZodGetsTypeReturn.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
return false;
}
}
export function gets({
restConfig,
}: {
restConfig: RESTConfig,
}): Promise<GetsTypeReturn> {
return RESTRequestJson({
restModel: {
endPoint: "/gender/",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
}, isGetsTypeReturn);
};
export function patch({
restConfig,
params,
data,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: GenderWrite,
}): Promise<Gender> {
return RESTRequestJson({
restModel: {
endPoint: "/gender/{id}",
requestType: HTTPRequestModel.PATCH,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
}, isGender);
};
export function post({
restConfig,
data,
}: {
restConfig: RESTConfig,
data: GenderWrite,
}): Promise<Gender> {
return RESTRequestJson({
restModel: {
endPoint: "/gender/",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
data,
}, isGender);
};
export function remove({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<void> {
return RESTRequestVoid({
restModel: {
endPoint: "/gender/{id}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
},
restConfig,
params,
});
};
export function removeCover({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
coverId: UUID,
id: Long,
},
}): Promise<Gender> {
return RESTRequestJson({
restModel: {
endPoint: "/gender/{id}/cover/{coverId}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isGender);
};
export function uploadCover({
restConfig,
params,
data,
callbacks,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: {
file: File,
},
callbacks?: RESTCallbacks,
}): Promise<Gender> {
return RESTRequestJson({
restModel: {
endPoint: "/gender/{id}/cover",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
callbacks,
}, isGender);
};
}

View File

@ -0,0 +1,32 @@
/**
* Interface of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
RESTConfig,
RESTRequestJson,
} from "../rest-tools";
import {
HealthResult,
isHealthResult,
} from "../model";
export namespace HealthCheck {
export function getHealth({
restConfig,
}: {
restConfig: RESTConfig,
}): Promise<HealthResult> {
return RESTRequestJson({
restModel: {
endPoint: "/health_check/",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
}, isHealthResult);
};
}

View File

@ -0,0 +1,12 @@
/**
* Interface of the server (auto-generated code)
*/
export * from "./album-resource"
export * from "./artist-resource"
export * from "./data-resource"
export * from "./front"
export * from "./gender-resource"
export * from "./health-check"
export * from "./playlist-resource"
export * from "./track-resource"
export * from "./user-resource"

View File

@ -0,0 +1,219 @@
/**
* Interface of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
RESTConfig,
RESTRequestJson,
RESTRequestVoid,
} from "../rest-tools";
import { z as zod } from "zod"
import {
Long,
Playlist,
PlaylistWrite,
UUID,
ZodPlaylist,
isPlaylist,
} from "../model";
export namespace PlaylistResource {
export function addTrack({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
trackId: Long,
id: Long,
},
}): Promise<Playlist> {
return RESTRequestJson({
restModel: {
endPoint: "/playlist/{id}/track/{trackId}",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isPlaylist);
};
export function get({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<Playlist> {
return RESTRequestJson({
restModel: {
endPoint: "/playlist/{id}",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isPlaylist);
};
export const ZodGetsTypeReturn = zod.array(ZodPlaylist);
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
try {
ZodGetsTypeReturn.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
return false;
}
}
export function gets({
restConfig,
}: {
restConfig: RESTConfig,
}): Promise<GetsTypeReturn> {
return RESTRequestJson({
restModel: {
endPoint: "/playlist/",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
}, isGetsTypeReturn);
};
export function patch({
restConfig,
params,
data,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: PlaylistWrite,
}): Promise<Playlist> {
return RESTRequestJson({
restModel: {
endPoint: "/playlist/{id}",
requestType: HTTPRequestModel.PATCH,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
}, isPlaylist);
};
export function post({
restConfig,
data,
}: {
restConfig: RESTConfig,
data: PlaylistWrite,
}): Promise<Playlist> {
return RESTRequestJson({
restModel: {
endPoint: "/playlist/",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
data,
}, isPlaylist);
};
export function remove({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<void> {
return RESTRequestVoid({
restModel: {
endPoint: "/playlist/{id}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
},
restConfig,
params,
});
};
export function removeCover({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
coverId: UUID,
id: Long,
},
}): Promise<Playlist> {
return RESTRequestJson({
restModel: {
endPoint: "/playlist/{id}/cover/{coverId}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isPlaylist);
};
export function removeTrack({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
trackId: Long,
id: Long,
},
}): Promise<Playlist> {
return RESTRequestJson({
restModel: {
endPoint: "/playlist/{id}/track/{trackId}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isPlaylist);
};
export function uploadCover({
restConfig,
params,
data,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: {
file: File,
},
}): Promise<Playlist> {
return RESTRequestJson({
restModel: {
endPoint: "/playlist/{id}/cover",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
}, isPlaylist);
};
}

View File

@ -0,0 +1,252 @@
/**
* Interface of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
RESTCallbacks,
RESTConfig,
RESTRequestJson,
RESTRequestVoid,
} from "../rest-tools";
import { z as zod } from "zod"
import {
Long,
Track,
TrackWrite,
UUID,
ZodTrack,
isTrack,
} from "../model";
export namespace TrackResource {
export function addTrack({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
artistId: Long,
id: Long,
},
}): Promise<Track> {
return RESTRequestJson({
restModel: {
endPoint: "/track/{id}/artist/{artistId}",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isTrack);
};
export function get({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<Track> {
return RESTRequestJson({
restModel: {
endPoint: "/track/{id}",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isTrack);
};
export const ZodGetsTypeReturn = zod.array(ZodTrack);
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
try {
ZodGetsTypeReturn.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
return false;
}
}
export function gets({
restConfig,
}: {
restConfig: RESTConfig,
}): Promise<GetsTypeReturn> {
return RESTRequestJson({
restModel: {
endPoint: "/track/",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
}, isGetsTypeReturn);
};
export function patch({
restConfig,
params,
data,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: TrackWrite,
}): Promise<Track> {
return RESTRequestJson({
restModel: {
endPoint: "/track/{id}",
requestType: HTTPRequestModel.PATCH,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
}, isTrack);
};
export function post({
restConfig,
data,
}: {
restConfig: RESTConfig,
data: TrackWrite,
}): Promise<Track> {
return RESTRequestJson({
restModel: {
endPoint: "/track/",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.JSON,
accept: HTTPMimeType.JSON,
},
restConfig,
data,
}, isTrack);
};
export function remove({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<void> {
return RESTRequestVoid({
restModel: {
endPoint: "/track/{id}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
},
restConfig,
params,
});
};
export function removeCover({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
coverId: UUID,
id: Long,
},
}): Promise<Track> {
return RESTRequestJson({
restModel: {
endPoint: "/track/{id}/cover/{coverId}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isTrack);
};
export function removeTrack({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
artistId: Long,
id: Long,
},
}): Promise<Track> {
return RESTRequestJson({
restModel: {
endPoint: "/track/{id}/artist/{trackId}",
requestType: HTTPRequestModel.DELETE,
contentType: HTTPMimeType.TEXT_PLAIN,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isTrack);
};
export function uploadCover({
restConfig,
params,
data,
callbacks,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
data: {
file: File,
},
callbacks?: RESTCallbacks,
}): Promise<Track> {
return RESTRequestJson({
restModel: {
endPoint: "/track/{id}/cover",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
data,
callbacks,
}, isTrack);
};
export function uploadTrack({
restConfig,
data,
callbacks,
}: {
restConfig: RESTConfig,
data: {
fileName: string,
file: File,
gender: string,
artist: string,
album: string,
trackId: Long,
title: string,
},
callbacks?: RESTCallbacks,
}): Promise<Track> {
return RESTRequestJson({
restModel: {
endPoint: "/track/upload/",
requestType: HTTPRequestModel.POST,
contentType: HTTPMimeType.MULTIPART,
accept: HTTPMimeType.JSON,
},
restConfig,
data,
callbacks,
}, isTrack);
};
}

View File

@ -0,0 +1,84 @@
/**
* Interface of the server (auto-generated code)
*/
import {
HTTPMimeType,
HTTPRequestModel,
RESTConfig,
RESTRequestJson,
} from "../rest-tools";
import { z as zod } from "zod"
import {
Long,
UserKarusic,
UserMe,
ZodUserKarusic,
isUserKarusic,
isUserMe,
} from "../model";
export namespace UserResource {
export function get({
restConfig,
params,
}: {
restConfig: RESTConfig,
params: {
id: Long,
},
}): Promise<UserKarusic> {
return RESTRequestJson({
restModel: {
endPoint: "/users/{id}",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
params,
}, isUserKarusic);
};
export function getMe({
restConfig,
}: {
restConfig: RESTConfig,
}): Promise<UserMe> {
return RESTRequestJson({
restModel: {
endPoint: "/users/me",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
}, isUserMe);
};
export const ZodGetsTypeReturn = zod.array(ZodUserKarusic);
export type GetsTypeReturn = zod.infer<typeof ZodGetsTypeReturn>;
export function isGetsTypeReturn(data: any): data is GetsTypeReturn {
try {
ZodGetsTypeReturn.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGetsTypeReturn' error=${e}`);
return false;
}
}
export function gets({
restConfig,
}: {
restConfig: RESTConfig,
}): Promise<GetsTypeReturn> {
return RESTRequestJson({
restModel: {
endPoint: "/users/",
requestType: HTTPRequestModel.GET,
accept: HTTPMimeType.JSON,
},
restConfig,
}, isGetsTypeReturn);
};
}

View File

@ -0,0 +1,7 @@
/**
* Interface of the server (auto-generated code)
*/
export * from "./model";
export * from "./api";
export * from "./rest-tools";

View File

@ -0,0 +1,53 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodUUID} from "./uuid";
import {ZodLocalDate} from "./local-date";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodAlbum = ZodGenericDataSoftDelete.extend({
name: zod.string().max(256).optional(),
description: zod.string().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).optional(),
publication: ZodLocalDate.optional(),
});
export type Album = zod.infer<typeof ZodAlbum>;
export function isAlbum(data: any): data is Album {
try {
ZodAlbum.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodAlbum' error=${e}`);
return false;
}
}
export const ZodAlbumWrite = ZodGenericDataSoftDeleteWrite.extend({
name: zod.string().max(256).nullable().optional(),
description: zod.string().nullable().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).nullable().optional(),
publication: ZodLocalDate.nullable().optional(),
});
export type AlbumWrite = zod.infer<typeof ZodAlbumWrite>;
export function isAlbumWrite(data: any): data is AlbumWrite {
try {
ZodAlbumWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodAlbumWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,59 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodUUID} from "./uuid";
import {ZodLocalDate} from "./local-date";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodArtist = ZodGenericDataSoftDelete.extend({
name: zod.string().max(256).optional(),
description: zod.string().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).optional(),
firstName: zod.string().max(256).optional(),
surname: zod.string().max(256).optional(),
birth: ZodLocalDate.optional(),
death: ZodLocalDate.optional(),
});
export type Artist = zod.infer<typeof ZodArtist>;
export function isArtist(data: any): data is Artist {
try {
ZodArtist.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodArtist' error=${e}`);
return false;
}
}
export const ZodArtistWrite = ZodGenericDataSoftDeleteWrite.extend({
name: zod.string().max(256).nullable().optional(),
description: zod.string().nullable().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).nullable().optional(),
firstName: zod.string().max(256).nullable().optional(),
surname: zod.string().max(256).nullable().optional(),
birth: ZodLocalDate.nullable().optional(),
death: ZodLocalDate.nullable().optional(),
});
export type ArtistWrite = zod.infer<typeof ZodArtistWrite>;
export function isArtistWrite(data: any): data is ArtistWrite {
try {
ZodArtistWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodArtistWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,50 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodUUID} from "./uuid";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodGender = ZodGenericDataSoftDelete.extend({
name: zod.string().max(256).optional(),
description: zod.string().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).optional(),
});
export type Gender = zod.infer<typeof ZodGender>;
export function isGender(data: any): data is Gender {
try {
ZodGender.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGender' error=${e}`);
return false;
}
}
export const ZodGenderWrite = ZodGenericDataSoftDeleteWrite.extend({
name: zod.string().max(256).nullable().optional(),
description: zod.string().nullable().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).nullable().optional(),
});
export type GenderWrite = zod.infer<typeof ZodGenderWrite>;
export function isGenderWrite(data: any): data is GenderWrite {
try {
ZodGenderWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenderWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,41 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodGenericData, ZodGenericDataWrite } from "./generic-data";
export const ZodGenericDataSoftDelete = ZodGenericData.extend({
/**
* Deleted state
*/
deleted: zod.boolean().readonly().optional(),
});
export type GenericDataSoftDelete = zod.infer<typeof ZodGenericDataSoftDelete>;
export function isGenericDataSoftDelete(data: any): data is GenericDataSoftDelete {
try {
ZodGenericDataSoftDelete.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericDataSoftDelete' error=${e}`);
return false;
}
}
export const ZodGenericDataSoftDeleteWrite = ZodGenericDataWrite.extend({
});
export type GenericDataSoftDeleteWrite = zod.infer<typeof ZodGenericDataSoftDeleteWrite>;
export function isGenericDataSoftDeleteWrite(data: any): data is GenericDataSoftDeleteWrite {
try {
ZodGenericDataSoftDeleteWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericDataSoftDeleteWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,42 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodLong} from "./long";
import {ZodGenericTiming, ZodGenericTimingWrite } from "./generic-timing";
export const ZodGenericData = ZodGenericTiming.extend({
/**
* Unique Id of the object
*/
id: ZodLong.readonly(),
});
export type GenericData = zod.infer<typeof ZodGenericData>;
export function isGenericData(data: any): data is GenericData {
try {
ZodGenericData.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericData' error=${e}`);
return false;
}
}
export const ZodGenericDataWrite = ZodGenericTimingWrite.extend({
});
export type GenericDataWrite = zod.infer<typeof ZodGenericDataWrite>;
export function isGenericDataWrite(data: any): data is GenericDataWrite {
try {
ZodGenericDataWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericDataWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,45 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodIsoDate} from "./iso-date";
export const ZodGenericTiming = zod.object({
/**
* Create time of the object
*/
createdAt: ZodIsoDate.readonly().optional(),
/**
* When update the object
*/
updatedAt: ZodIsoDate.readonly().optional(),
});
export type GenericTiming = zod.infer<typeof ZodGenericTiming>;
export function isGenericTiming(data: any): data is GenericTiming {
try {
ZodGenericTiming.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericTiming' error=${e}`);
return false;
}
}
export const ZodGenericTimingWrite = zod.object({
});
export type GenericTimingWrite = zod.infer<typeof ZodGenericTimingWrite>;
export function isGenericTimingWrite(data: any): data is GenericTimingWrite {
try {
ZodGenericTimingWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodGenericTimingWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,36 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodHealthResult = zod.object({
});
export type HealthResult = zod.infer<typeof ZodHealthResult>;
export function isHealthResult(data: any): data is HealthResult {
try {
ZodHealthResult.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodHealthResult' error=${e}`);
return false;
}
}
export const ZodHealthResultWrite = zod.object({
});
export type HealthResultWrite = zod.infer<typeof ZodHealthResultWrite>;
export function isHealthResultWrite(data: any): data is HealthResultWrite {
try {
ZodHealthResultWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodHealthResultWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,23 @@
/**
* Interface of the server (auto-generated code)
*/
export * from "./album"
export * from "./artist"
export * from "./gender"
export * from "./generic-data"
export * from "./generic-data-soft-delete"
export * from "./generic-timing"
export * from "./health-result"
export * from "./int"
export * from "./iso-date"
export * from "./local-date"
export * from "./long"
export * from "./part-right"
export * from "./playlist"
export * from "./rest-error-response"
export * from "./timestamp"
export * from "./track"
export * from "./user"
export * from "./user-karusic"
export * from "./user-me"
export * from "./uuid"

View File

@ -0,0 +1,36 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const Zodint = zod.object({
});
export type int = zod.infer<typeof Zodint>;
export function isint(data: any): data is int {
try {
Zodint.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='Zodint' error=${e}`);
return false;
}
}
export const ZodintWrite = zod.object({
});
export type intWrite = zod.infer<typeof ZodintWrite>;
export function isintWrite(data: any): data is intWrite {
try {
ZodintWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodintWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,8 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodIsoDate = zod.string().datetime({ precision: 3 });
export type IsoDate = zod.infer<typeof ZodIsoDate>;

View File

@ -0,0 +1,8 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodLocalDate = zod.string().date();
export type LocalDate = zod.infer<typeof ZodLocalDate>;

View File

@ -0,0 +1,8 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodLong = zod.number();
export type Long = zod.infer<typeof ZodLong>;

View File

@ -0,0 +1,23 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export enum PartRight {
READ = 1,
WRITE = 2,
READ_WRITE = 3,
};
export const ZodPartRight = zod.nativeEnum(PartRight);
export function isPartRight(data: any): data is PartRight {
try {
ZodPartRight.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodPartRight' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,53 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodUUID} from "./uuid";
import {ZodLong} from "./long";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodPlaylist = ZodGenericDataSoftDelete.extend({
name: zod.string().max(256).optional(),
description: zod.string().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).optional(),
tracks: zod.array(ZodLong),
});
export type Playlist = zod.infer<typeof ZodPlaylist>;
export function isPlaylist(data: any): data is Playlist {
try {
ZodPlaylist.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodPlaylist' error=${e}`);
return false;
}
}
export const ZodPlaylistWrite = ZodGenericDataSoftDeleteWrite.extend({
name: zod.string().max(256).nullable().optional(),
description: zod.string().nullable().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).nullable().optional(),
tracks: zod.array(ZodLong).optional(),
});
export type PlaylistWrite = zod.infer<typeof ZodPlaylistWrite>;
export function isPlaylistWrite(data: any): data is PlaylistWrite {
try {
ZodPlaylistWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodPlaylistWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,29 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodUUID} from "./uuid";
import {Zodint, ZodintWrite } from "./int";
export const ZodRestErrorResponse = zod.object({
uuid: ZodUUID.optional(),
name: zod.string(),
message: zod.string(),
time: zod.string(),
status: Zodint,
statusMessage: zod.string(),
});
export type RestErrorResponse = zod.infer<typeof ZodRestErrorResponse>;
export function isRestErrorResponse(data: any): data is RestErrorResponse {
try {
ZodRestErrorResponse.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodRestErrorResponse' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,8 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodTimestamp = zod.string().datetime({ precision: 3 });
export type Timestamp = zod.infer<typeof ZodTimestamp>;

View File

@ -0,0 +1,61 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodUUID} from "./uuid";
import {ZodLong} from "./long";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodTrack = ZodGenericDataSoftDelete.extend({
name: zod.string().max(256).optional(),
description: zod.string().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).optional(),
genderId: ZodLong.optional(),
albumId: ZodLong.optional(),
track: ZodLong.optional(),
dataId: ZodUUID.optional(),
artists: zod.array(ZodLong),
});
export type Track = zod.infer<typeof ZodTrack>;
export function isTrack(data: any): data is Track {
try {
ZodTrack.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodTrack' error=${e}`);
return false;
}
}
export const ZodTrackWrite = ZodGenericDataSoftDeleteWrite.extend({
name: zod.string().max(256).nullable().optional(),
description: zod.string().nullable().optional(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).nullable().optional(),
genderId: ZodLong.nullable().optional(),
albumId: ZodLong.nullable().optional(),
track: ZodLong.nullable().optional(),
dataId: ZodUUID.nullable().optional(),
artists: zod.array(ZodLong).optional(),
});
export type TrackWrite = zod.infer<typeof ZodTrackWrite>;
export function isTrackWrite(data: any): data is TrackWrite {
try {
ZodTrackWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodTrackWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,37 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodUser, ZodUserWrite } from "./user";
export const ZodUserKarusic = ZodUser.extend({
});
export type UserKarusic = zod.infer<typeof ZodUserKarusic>;
export function isUserKarusic(data: any): data is UserKarusic {
try {
ZodUserKarusic.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserKarusic' error=${e}`);
return false;
}
}
export const ZodUserKarusicWrite = ZodUserWrite.extend({
});
export type UserKarusicWrite = zod.infer<typeof ZodUserKarusicWrite>;
export function isUserKarusicWrite(data: any): data is UserKarusicWrite {
try {
ZodUserKarusicWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserKarusicWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,29 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodLong} from "./long";
import {ZodPartRight} from "./part-right";
export const ZodUserMe = zod.object({
id: ZodLong,
login: zod.string().max(255).optional(),
/**
* Map<EntityName, Map<PartName, Right>>
*/
rights: zod.record(zod.string(), zod.record(zod.string(), ZodPartRight)),
});
export type UserMe = zod.infer<typeof ZodUserMe>;
export function isUserMe(data: any): data is UserMe {
try {
ZodUserMe.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserMe' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,41 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodLong} from "./long";
export const ZodUserOut = zod.object({
id: ZodLong,
login: zod.string().max(255).optional(),
});
export type UserOut = zod.infer<typeof ZodUserOut>;
export function isUserOut(data: any): data is UserOut {
try {
ZodUserOut.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserOut' error=${e}`);
return false;
}
}
export const ZodUserOutWrite = zod.object({
id: ZodLong,
login: zod.string().max(255).nullable().optional(),
});
export type UserOutWrite = zod.infer<typeof ZodUserOutWrite>;
export function isUserOutWrite(data: any): data is UserOutWrite {
try {
ZodUserOutWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserOutWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,57 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
import {ZodTimestamp} from "./timestamp";
import {ZodUUID} from "./uuid";
import {ZodGenericDataSoftDelete, ZodGenericDataSoftDeleteWrite } from "./generic-data-soft-delete";
export const ZodUser = ZodGenericDataSoftDelete.extend({
login: zod.string().max(128).optional(),
lastConnection: ZodTimestamp.optional(),
admin: zod.boolean(),
blocked: zod.boolean(),
removed: zod.boolean(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).optional(),
});
export type User = zod.infer<typeof ZodUser>;
export function isUser(data: any): data is User {
try {
ZodUser.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUser' error=${e}`);
return false;
}
}
export const ZodUserWrite = ZodGenericDataSoftDeleteWrite.extend({
login: zod.string().max(128).nullable().optional(),
lastConnection: ZodTimestamp.nullable().optional(),
admin: zod.boolean(),
blocked: zod.boolean(),
removed: zod.boolean(),
/**
* List of Id of the specific covers
*/
covers: zod.array(ZodUUID).nullable().optional(),
});
export type UserWrite = zod.infer<typeof ZodUserWrite>;
export function isUserWrite(data: any): data is UserWrite {
try {
ZodUserWrite.parse(data);
return true;
} catch (e: any) {
console.log(`Fail to parse data type='ZodUserWrite' error=${e}`);
return false;
}
}

View File

@ -0,0 +1,8 @@
/**
* Interface of the server (auto-generated code)
*/
import { z as zod } from "zod";
export const ZodUUID = zod.string().uuid();
export type UUID = zod.infer<typeof ZodUUID>;

View File

@ -0,0 +1,445 @@
/** @file
* @author Edouard DUPIN
* @copyright 2024, Edouard DUPIN, all right reserved
* @license MPL-2
*/
import { RestErrorResponse, isRestErrorResponse } from "./model";
export enum HTTPRequestModel {
DELETE = "DELETE",
GET = "GET",
PATCH = "PATCH",
POST = "POST",
PUT = "PUT",
}
export enum HTTPMimeType {
ALL = "*/*",
CSV = "text/csv",
IMAGE = "image/*",
IMAGE_JPEG = "image/jpeg",
IMAGE_PNG = "image/png",
JSON = "application/json",
MULTIPART = "multipart/form-data",
OCTET_STREAM = "application/octet-stream",
TEXT_PLAIN = "text/plain",
}
export interface RESTConfig {
// base of the server: http(s)://my.server.org/plop/api/
server: string;
// Token to access of the data.
token?: string;
}
export interface RESTModel {
// base of the local API request: "sheep/{id}".
endPoint: string;
// Type of the request.
requestType?: HTTPRequestModel;
// Input type requested.
accept?: HTTPMimeType;
// Content of the local data.
contentType?: HTTPMimeType;
// Mode of the TOKEN in URL or Header (?token:${tokenInUrl})
tokenInUrl?: boolean;
}
export interface ModelResponseHttp {
status: number;
data: any;
}
function isNullOrUndefined(data: any): data is undefined | null {
return data === undefined || data === null;
}
// generic progression callback
export type ProgressCallback = (count: number, total: number) => void;
export interface RESTAbort {
abort?: () => boolean;
}
// Rest generic callback have a basic model to upload and download advancement.
export interface RESTCallbacks {
progressUpload?: ProgressCallback;
progressDownload?: ProgressCallback;
abortHandle?: RESTAbort;
}
export interface RESTRequestType {
restModel: RESTModel;
restConfig: RESTConfig;
data?: any;
params?: object;
queries?: object;
callbacks?: RESTCallbacks;
}
function replaceAll(input, searchValue, replaceValue) {
return input.split(searchValue).join(replaceValue);
}
function removeTrailingSlashes(input: string): string {
if (isNullOrUndefined(input)) {
return "undefined";
}
return input.replace(/\/+$/, "");
}
function removeLeadingSlashes(input: string): string {
if (isNullOrUndefined(input)) {
return "";
}
return input.replace(/^\/+/, "");
}
export function RESTUrl({
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]}`);
}
}
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();
}
export function fetchProgress(
generateUrl: string,
{
method,
headers,
body,
}: {
method: HTTPRequestModel;
headers: any;
body: any;
},
{ progressUpload, progressDownload, abortHandle }: RESTCallbacks
): Promise<Response> {
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);
}
});
}
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;
};
}
// 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,
callbacks,
}: RESTRequestType): Promise<ModelResponseHttp> {
// 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;
}
}
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<Response> = undefined;
if (
isNullOrUndefined(callbacks) ||
(isNullOrUndefined(callbacks.progressDownload) &&
isNullOrUndefined(callbacks.progressUpload) &&
isNullOrUndefined(callbacks.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,
},
callbacks
);
}
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: "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<TYPE>(
request: RESTRequestType,
checker?: (data: any) => data is TYPE
): Promise<TYPE> {
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<void> {
return new Promise((resolve, reject) => {
RESTRequest(request)
.then((value: ModelResponseHttp) => {
resolve();
})
.catch((reason: RestErrorResponse) => {
reject(reason);
});
});
}

View File

@ -0,0 +1,35 @@
import React, { ReactNode, useEffect } from 'react';
import { Flex } from '@chakra-ui/react';
import { useLocation } from 'react-router-dom';
import { TOP_BAR_HEIGHT } from '@/components/TopBar/TopBar';
export type LayoutProps = React.PropsWithChildren<unknown> & {
topBar?: ReactNode;
};
export const PageLayout = ({ children }: LayoutProps) => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return (
<Flex
direction="column"
overflowX="auto"
overflowY="auto"
minH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
maxH={`calc(100vh - ${TOP_BAR_HEIGHT})`}
position="absolute"
top={TOP_BAR_HEIGHT}
bottom={0}
left={0}
right={0}
>
{children}
</Flex>
);
};

View File

@ -0,0 +1,41 @@
import React, { ReactNode, useEffect } from 'react';
import { Flex, FlexProps } from '@chakra-ui/react';
import { useLocation } from 'react-router-dom';
import { PageLayout } from '@/components/Layout/PageLayout';
import { colors } from '@/theme/foundations/colors';
export type LayoutProps = FlexProps & {
children: ReactNode;
};
export const PageLayoutInfoCenter = ({
children,
width = '25%',
...rest
}: LayoutProps) => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return (
<PageLayout>
<Flex
direction="column"
margin="auto"
minWidth={width}
border="back.900"
borderWidth="1px"
borderRadius="8px"
padding="10px"
boxShadow={'0px 0px 16px ' + colors.back[900]}
{...rest}
>
{children}
</Flex>
</PageLayout>
);
};

View File

@ -0,0 +1,180 @@
import { ReactNode } from 'react';
import {
Button,
Drawer,
DrawerBody,
DrawerContent,
DrawerHeader,
DrawerOverlay,
Flex,
HStack,
IconButton,
Menu,
MenuButton,
MenuItem,
MenuList,
Text,
useDisclosure,
} from '@chakra-ui/react';
import {
LuAlignJustify,
LuArrowBigLeft,
LuArrowRightSquare,
LuArrowUpSquare,
LuHelpCircle,
LuHome,
LuLogIn,
LuLogOut,
LuMoon,
LuPlusCircle,
LuSettings,
LuSun,
LuUserCircle,
} from 'react-icons/lu';
import { useNavigate } from 'react-router-dom';
import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState';
import { colors } from '@/theme/foundations/colors';
import { requestSignIn, requestSignOut, requestSignUp } from '@/utils/sso';
import { useThemeMode } from '@/utils/theme-tools';
export const TOP_BAR_HEIGHT = '50px';
export type TopBarProps = {
children?: ReactNode;
};
export const TopBar = ({ children }: TopBarProps) => {
const { mode, colorMode, toggleColorMode } = useThemeMode();
const buttonProperty = {
variant: '@menu',
height: TOP_BAR_HEIGHT,
};
const { session } = useServiceContext();
const backColor = mode('back.100', 'back.800');
const drawerDisclose = useDisclosure();
const onChangeTheme = () => {
drawerDisclose.onOpen();
};
const navigate = useNavigate();
const onSignIn = (): void => {
requestSignIn();
};
const onSignUp = (): void => {
requestSignUp();
};
const onSignOut = (): void => {
requestSignOut();
};
const onSelectHome = () => {
navigate('/');
};
return (
<Flex
position="absolute"
top={0}
left={0}
right={0}
height={TOP_BAR_HEIGHT}
alignItems="center"
justifyContent="space-between"
backgroundColor={backColor}
gap="2"
px="2"
boxShadow={'0px 2px 4px ' + colors.back[900]}
zIndex={200}
>
<Button {...buttonProperty} onClick={onChangeTheme} marginRight="auto">
<LuAlignJustify />
<Text paddingLeft="3px" fontWeight="bold">
Menu
</Text>
</Button>
{children}
{session?.state !== SessionState.CONNECTED && (
<>
<Button {...buttonProperty} onClick={onSignIn}>
<LuLogIn />
<Text paddingLeft="3px" fontWeight="bold">
Sign-in
</Text>
</Button>
<Button {...buttonProperty} onClick={onSignUp} disabled={true}>
<LuPlusCircle />
<Text paddingLeft="3px" fontWeight="bold">
Sign-up
</Text>
</Button>
</>
)}
{session?.state === SessionState.CONNECTED && (
<Menu>
<MenuButton
as={IconButton}
aria-label="Options"
icon={<LuUserCircle />}
{...buttonProperty}
width={TOP_BAR_HEIGHT}
/>
<MenuList>
<MenuItem _hover={{}} color={mode('brand.800', 'brand.200')}>
Sign in as {session?.login ?? 'Fail'}
</MenuItem>
<MenuItem icon={<LuArrowUpSquare />}>Add Media</MenuItem>
<MenuItem icon={<LuSettings />}>Settings</MenuItem>
<MenuItem icon={<LuHelpCircle />}>Help</MenuItem>
<MenuItem icon={<LuLogOut onClick={onSignOut} />}>
Sign-out
</MenuItem>
{colorMode === 'light' ? (
<MenuItem icon={<LuMoon />} onClick={toggleColorMode}>
Set dark mode
</MenuItem>
) : (
<MenuItem icon={<LuSun />} onClick={toggleColorMode}>
Set light mode
</MenuItem>
)}
</MenuList>
</Menu>
)}
<Drawer
placement="left"
onClose={drawerDisclose.onClose}
isOpen={drawerDisclose.isOpen}
>
<DrawerOverlay />
<DrawerContent>
<DrawerHeader
paddingY="auto"
onClick={drawerDisclose.onClose}
boxShadow={'0px 2px 4px ' + colors.back[900]}
backgroundColor={backColor}
color={mode('brand.900', 'brand.50')}
textTransform="uppercase"
>
<HStack height={TOP_BAR_HEIGHT}>
<LuArrowBigLeft />
<Text as="span" paddingLeft="3px">
Karusic
</Text>
</HStack>
</DrawerHeader>
<DrawerBody>
<Button {...buttonProperty} onClick={onSelectHome} width="fill">
<LuHome />
<Text paddingLeft="3px" fontWeight="bold">
Home
</Text>
</Button>
<p>Some contents...</p>
<p>Some contents...</p>
<p>Some contents...</p>
</DrawerBody>
</DrawerContent>
</Drawer>
</Flex>
);
};

View File

@ -0,0 +1,3 @@
export * from './Icons';

View File

@ -0,0 +1,28 @@
import dayjs from 'dayjs';
import 'dayjs/locale/fr';
import advancedFormat from 'dayjs/plugin/advancedFormat';
import customParseFormat from 'dayjs/plugin/customParseFormat';
import dayOfYear from 'dayjs/plugin/dayOfYear';
import duration from 'dayjs/plugin/duration';
import isBetween from 'dayjs/plugin/isBetween';
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
import isToday from 'dayjs/plugin/isToday';
import isTomorrow from 'dayjs/plugin/isTomorrow';
import isYesterday from 'dayjs/plugin/isYesterday';
import quarterOfYear from 'dayjs/plugin/quarterOfYear';
import relativeTime from 'dayjs/plugin/relativeTime';
import weekOfYear from 'dayjs/plugin/weekOfYear';
dayjs.locale('fr');
dayjs.extend(relativeTime);
dayjs.extend(customParseFormat);
dayjs.extend(weekOfYear);
dayjs.extend(isSameOrAfter);
dayjs.extend(isToday);
dayjs.extend(isTomorrow);
dayjs.extend(isYesterday);
dayjs.extend(dayOfYear);
dayjs.extend(isBetween);
dayjs.extend(advancedFormat);
dayjs.extend(quarterOfYear);
dayjs.extend(duration);

View File

@ -0,0 +1,2 @@
import './axios';
import './dayjs';

View File

@ -0,0 +1,2 @@
export const DATE_FORMAT = 'YYYY-MM-DD';
export const DATE_FORMAT_FULL = 'dddd DD MMMM HH:mm';

View File

@ -0,0 +1 @@
export * from './date'

107
front2/src/environment.ts Normal file
View File

@ -0,0 +1,107 @@
export interface Environment {
production: boolean;
applName: string;
defaultServer: string;
server: {
[key: string]: string;
};
ssoSite: string;
ssoSignIn: string;
ssoSignUp: string;
ssoSignOut: string;
tokenStoredInPermanentStorage: boolean;
replaceDataToRealServer?: boolean;
}
const serverSSOAddress = 'http://atria-soft.org';
const environment_back_prod: Environment = {
production: false,
// URL of development API
applName: 'karusic',
defaultServer: 'karusic',
server: {
karusic: `${serverSSOAddress}/karusic/api`,
karso: `${serverSSOAddress}/karso/api`,
},
ssoSite: `${serverSSOAddress}/karso/`,
ssoSignIn: `${serverSSOAddress}/karso/signin/karusic-dev/`,
ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`,
ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`,
tokenStoredInPermanentStorage: false,
};
const environment_local: Environment = {
production: false,
// URL of development API
applName: 'karusic',
defaultServer: 'karusic',
server: {
karusic: 'http://localhost:19080/karusic/api',
karso: `${serverSSOAddress}/karso/api`,
},
ssoSite: `${serverSSOAddress}/karso/`,
ssoSignIn: `${serverSSOAddress}/karso/signin/karusic-dev/`,
ssoSignUp: `${serverSSOAddress}/karso/signup/karusic-dev/`,
ssoSignOut: `${serverSSOAddress}/karso/signout/karusic-dev/`,
tokenStoredInPermanentStorage: false,
replaceDataToRealServer: true,
};
const environment_full_local: Environment = {
production: false,
// URL of development API
applName: 'karusic',
defaultServer: 'karusic',
server: {
karusic: 'http://localhost:19080/karusic/api',
karso: 'http://localhost:15080/karso/api',
},
ssoSite: `${serverSSOAddress}/karso/`,
ssoSignIn: 'http://localhost:4200/signin/karusic-dev/',
ssoSignUp: 'http://localhost:4200/signup/karusic-dev/',
ssoSignOut: 'http://localhost:4200/signout/karusic-dev/',
tokenStoredInPermanentStorage: false,
};
const environment_hybrid: Environment = {
production: false,
// URL of development API
applName: 'karusic',
defaultServer: 'karusic',
server: {
karusic: `${serverSSOAddress}/karusic/api`,
karso: `${serverSSOAddress}/karso/api`,
},
ssoSite: `${serverSSOAddress}/karso/`,
ssoSignIn: 'http://localhost:4200/signin/karusic-dev/',
ssoSignUp: 'http://localhost:4200/signup/karusic-dev/',
ssoSignOut: 'http://localhost:4200/signout/karusic-dev/',
tokenStoredInPermanentStorage: false,
};
export const environment = environment_local;
/**
* Check if the current environment is for development
* @returns true if development is active.
*/
export const isDevelopmentEnvironment = () => {
return import.meta.env.MODE === 'development';
};
/**
* get the current REST api URL. Depend on the VITE_API_BASE_URL env variable.
* @returns The URL with http(s)://***
*/
export const getApiUrl = () => {
const baseUrl: string | undefined = import.meta.env.VITE_API_BASE_URL;
if (baseUrl === undefined || baseUrl === null) {
//return `${window.location.protocol}//${window.location.host}/api`;
return environment.server.karusic;
}
if (baseUrl.startsWith('http')) {
return baseUrl;
}
return `${window.location.protocol}//${window.location.host}/${baseUrl}`;
};

View File

@ -0,0 +1,133 @@
import {
Box,
Button,
Center,
Heading,
Stack,
Text,
useTheme,
} from '@chakra-ui/react';
import { environment } from '@/environment';
const Illustration = ({ colorScheme = 'gray', ...rest }) => {
const theme = useTheme();
const color = theme?.colors?.[colorScheme] ?? {};
return (
<Box
as="svg"
width={400}
height={300}
maxW="full"
viewBox="0 0 400 300"
fill="none"
{...rest}
>
<path
// Left Hand
d="M65.013 104.416s-12.773-.562-13.719 11.938c-.946 12.5 16.13 8.397 13.719-11.938z"
fill={color['300']}
/>
<path
// Left Arm
d="M182.326 67.705s-35.463-20.529-67.804-13.535c-32.342 6.993-60.624 52.94-60.624 52.94l11.499 6.837s49.74-51.775 83.275-21.444c33.535 30.331 33.654-24.798 33.654-24.798z"
fill={color['800']}
/>
<path
// Search Zone
d="M334.098 220.092a14.333 14.333 0 01-9.838-7.37v-.106l-50.465-96.17-27.774 19.677 19.642 61.796a10.575 10.575 0 01-5.617 12.799 10.563 10.563 0 01-4.945.97 1037.507 1037.507 0 00-47.278-1.067c-85.178 0-154.23 9.93-154.23 22.184 0 12.255 69.052 22.195 154.23 22.195C293.001 255 362 245.07 362 232.837c0-4.756-10.328-9.14-27.902-12.745z"
fill={color['200']}
/>
<path
// Foots
d="M173.611 225.563s1.578 5.333 6.256 5.962c4.679.63 5.66 5.333 1.365 6.293-4.296.96-14.921-5.066-14.921-5.066l.671-6.773 6.629-.416zM82.518 224.657s-5.414 1.173-6.395 5.791c-.98 4.618-5.734 5.237-6.395.875-.66-4.362 6.193-14.484 6.193-14.484l6.693 1.205-.096 6.613z"
fill={color['900']}
/>
<path
// Left Leg
d="M83.5 143s-5.245 25.322 12.713 35.305c17.959 9.983 74.606-7.988 65.856 48.592h12.64s16.338-46.928-26.048-63.609l-12.864-14.601L83.5 143z"
fill={color['600']}
/>
<path
// Magnifying Glass Shadow
d="M257.632 128.216l-4.299-4.112-26.891 28.16 4.299 4.111 26.891-28.159z"
fill={color['700']}
/>
<path
// Magnifying Glass Handle
d="M255.537 126.2l-4.299-4.112-26.891 28.16 4.299 4.111 26.891-28.159z"
fill={color['500']}
/>
<path
// Magnifying Glass Shadow 2
d="M267.233 131.381c6.913-7.239 8.606-16.849 3.78-21.464-4.826-4.615-14.342-2.487-21.256 4.752-6.913 7.24-8.605 16.85-3.779 21.465 4.825 4.615 14.342 2.487 21.255-4.753z"
fill={color['700']}
/>
<path
// Magnifying Glass Ring
d="M265.133 129.382c6.914-7.24 8.606-16.849 3.78-21.464-4.825-4.615-14.342-2.487-21.255 4.752-6.913 7.24-8.606 16.849-3.78 21.464 4.826 4.615 14.342 2.487 21.255-4.752z"
fill={color['500']}
/>
<path
// Magnifying Glass
d="M262.167 126.545c4.566-4.782 5.685-11.13 2.497-14.178-3.187-3.048-9.473-1.642-14.04 3.14-4.567 4.782-5.685 11.13-2.498 14.178 3.188 3.048 9.474 1.642 14.041-3.14z"
fill={color['50']}
/>
<path
// Head
d="M217.261 74.257c1.932-2.325 3.256-4.248 3.256-4.248 3.133-4.106-.267-11.743-6.096-12.084a7.606 7.606 0 00-7.759 4.095l-.966 2.572-19.039 7.678 2.664 15.443 14.063-11.483c.418 1.245 1.052 2.35 1.7 3.26a4.269 4.269 0 004.448 1.638 4.267 4.267 0 001.51-.7 25.197 25.197 0 002.341-1.98l1.613.893a1.364 1.364 0 002.014-1.067l.256-4.02-.005.003z"
fill={color['300']}
/>
<path
// Body
d="M192.199 93.056l-1.791-10.42a25.45 25.45 0 00-15.251-19.325 45.122 45.122 0 00-10.754-2.827c-4.732-.66-11.361 0-18.779 1.952-31.953 8.394-55.4 35.484-60.25 68.141L83 145l52.797 3.687s-2.664-14.931 15.987-14.931 42.269.192 40.415-40.7z"
fill={color['700']}
/>
<path
// Right Arm
d="M169.945 95.488c4.977-16.617-14.324-30.055-28.084-19.496-11.51 8.82-24.513 24.701-25.024 51.503-.885 48.368 45.413 39.718 108.071 21.192l-1.993-14.089s-75.032 14.196-64.843-10.207c4.263-10.206 9.23-20.061 11.873-28.903z"
fill={color['800']}
/>
<path
// Right Leg
d="M143.609 163.406s1.545 53.487-60.995 63.491l-2.42-11.338s50.092-12.425 17.735-45.712l45.68-6.441z"
fill={color['600']}
/>
307s17
<path
// Right Hand
d="M223.298 137.307s17.244-1.824 18.758 4.917c1.513 6.741-1.791 10.313-17.309 5.333l-1.449-10.25z"
fill={color['300']}
/>
<path
// Hair
d="M218.673 68.942a34.11 34.11 0 01-5.649-5.44 7.094 7.094 0 01-4.934 5.994 5.752 5.752 0 01-6.821-2.655l7.034-8.319a8.644 8.644 0 018.27-3.2c1.33.23 2.643.543 3.933.939 3.197 1.066 5.425 5.567 8.942 5.717a2.044 2.044 0 011.946 2.1c-.01.354-.111.7-.294 1.003-1.727 2.87-5.499 6.517-10.114 5.056a7.933 7.933 0 01-2.313-1.195z"
fill={color['800']}
/>
</Box>
);
};
export const Error404 = () => {
return (
<Center flex="1" p="8">
<Stack
direction={{ base: 'column', md: 'row' }}
align="center"
spacing="0"
>
<Illustration />
<Box textAlign={{ base: 'center', md: 'left' }}>
<Heading>Erreur 404</Heading>
<Text color="gray.600">
Cette page n'existe plus ou l'URL a changé
</Text>
<Button as="a" variant="link" href={`/${environment.applName}`}>
Retour à l'accueil
</Button>
</Box>
</Stack>
</Center>
);
};

View File

@ -0,0 +1,51 @@
import React, { FC } from 'react';
import {
Alert,
AlertDescription,
AlertIcon,
AlertTitle,
Box,
Button,
Collapse,
useDisclosure,
} from '@chakra-ui/react';
import {
FallbackProps,
ErrorBoundary as ReactErrorBoundary,
} from 'react-error-boundary';
import { LuChevronDown, LuChevronUp } from 'react-icons/lu';
const ErrorFallback = ({ error }: FallbackProps) => {
const { isOpen, onToggle } = useDisclosure();
return (
<Box p="4" m="auto">
<Alert status="error" borderRadius="md">
<AlertIcon />
<Box flex="1">
<AlertTitle>An unexpected error has occurred.</AlertTitle>
<AlertDescription display="block" lineHeight="1.4">
<Button
variant="link"
color="red.800"
size="sm"
rightIcon={isOpen ? <LuChevronUp /> : <LuChevronDown />}
onClick={onToggle}
>
Show details
</Button>
<Collapse in={isOpen} animateOpacity>
<Box mt={4} fontFamily="monospace">
{error.message}
</Box>
</Collapse>
</AlertDescription>
</Box>
</Alert>
</Box>
);
};
export const ErrorBoundary: FC<React.PropsWithChildren<unknown>> = (props) => {
return <ReactErrorBoundary FallbackComponent={ErrorFallback} {...props} />;
};

View File

@ -0,0 +1 @@
export * from './Error404';

View File

@ -0,0 +1,14 @@
import { createIcon } from '@chakra-ui/react';
export const DoubleArrowIcon = createIcon({
displayName: 'DoubleArrowIcon',
viewBox: '0 0 24 24',
path: (
<path
fillRule="evenodd"
clipRule="evenodd"
d="M1.293 12.207a1 1 0 0 1 0-1.414l6.364-6.364A1 1 0 0 1 9.07 5.843L4.414 10.5h15.172l-4.657-4.657a1 1 0 0 1 1.414-1.414l6.364 6.364a1 1 0 0 1 0 1.414l-6.364 6.364a1 1 0 0 1-1.414-1.414l4.657-4.657H4.414l4.657 4.657a1 1 0 1 1-1.414 1.414l-6.364-6.364Z"
fill="currentColor"
/>
),
});

View File

@ -0,0 +1 @@
export * from './DoubleArrowIcon';

16
front2/src/main.tsx Normal file
View File

@ -0,0 +1,16 @@
import { StrictMode } from 'react';
import ReactDOM from 'react-dom/client';
import App from '@/App';
// Render the app
const rootElement = document.getElementById('root');
if (rootElement && !rootElement.innerHTML) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<StrictMode>
<App />
</StrictMode>
);
}

33
front2/src/scene/App.tsx Normal file
View File

@ -0,0 +1,33 @@
import { createBrowserHistory } from 'history';
import {
unstable_HistoryRouter as HistoryRouter,
Route,
Routes,
} from 'react-router-dom';
import { Error404 } from '@/errors';
import { ErrorBoundary } from '@/errors/ErrorBoundary';
import { ServiceContextProvider } from '@/service/ServiceContext';
import { ArtistRoutes } from '@/scene/artist/ArtistRoutes';
import { HomePage } from '@/scene/home/HomePage';
import { SSORoutes } from '@/scene/sso/SSORoutes';
export const App = () => {
return (
<ServiceContextProvider>
<ErrorBoundary>
<HistoryRouter
history={createBrowserHistory({ window })}
basename="/karusic"
>
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="artist/*" element={<ArtistRoutes />} />
<Route path="sso/*" element={<SSORoutes />} />
<Route path="*" element={<Error404 />} />
</Routes>
</HistoryRouter>
</ErrorBoundary>
</ServiceContextProvider>
);
};

View File

@ -0,0 +1,15 @@
import { Text } from '@chakra-ui/react';
import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar';
export const ArtistRoutes = () => {
return (
<>
<TopBar />
<PageLayout>
<Text>artist Page</Text>;
</PageLayout>
</>
);
};

View File

@ -0,0 +1,105 @@
import { Text } from '@chakra-ui/react';
import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar';
export const HomePage = () => {
return (
<>
<TopBar />
<PageLayout>
<Text>Home Page 1</Text>
<br />
<Text>Home Page 2</Text>
<br />
<Text>Home Page 3</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;<Text>Home Page 2</Text>
<br />
<Text>
Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home
Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page
3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page
3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page
3Home Page 3Home Page 3Home Page 3Home Page 3Home Page 3Home Page
3Home Page 3Home Page 3
</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;<Text>Home Page 2</Text>
<br />
<Text>Home Page 3</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;<Text>Home Page 2</Text>
<br />
<Text>Home Page 3</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;<Text>Home Page 2</Text>
<br />
<Text>Home Page 3</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;<Text>Home Page 2</Text>
<br />
<Text>Home Page 3</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;<Text>Home Page 2</Text>
<br />
<Text>Home Page 3</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;<Text>Home Page 2</Text>
<br />
<Text>Home Page 3</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;<Text>Home Page 2</Text>
<br />
<Text>Home Page 3</Text>
<br />
<Text>Home Page 4</Text>
<br />
<Text>Home Page 5</Text>
<br />
<Text>Home Page 6</Text>
<br />;
</PageLayout>
</>
);
};

View File

@ -0,0 +1,15 @@
import { Text } from '@chakra-ui/react';
import { PageLayout } from '@/components/Layout/PageLayout';
import { TopBar } from '@/components/TopBar/TopBar';
export const SSOEmpty = () => {
return (
<>
<TopBar />
<PageLayout>
<Text>SSO empty page</Text>;
</PageLayout>
</>
);
};

View File

@ -0,0 +1,85 @@
import { useEffect } from 'react';
import { Center, Heading, Image, Text } from '@chakra-ui/react';
import { useNavigate, useParams } from 'react-router-dom';
import avatar_generic from '@/assets/images/avatar_generic.svg';
import { PageLayoutInfoCenter } from '@/components/Layout/PageLayoutInfoCenter';
import { TopBar } from '@/components/TopBar/TopBar';
import { isDevelopmentEnvironment } from '@/environment';
import { SessionState } from '@/service/SessionState';
import { useSessionService } from '@/service/session';
import { b64_to_utf8 } from '@/utils/sso';
export const SSOPage = () => {
const { data, keepConnected, token } = useParams();
console.log(`- data: ${data}`);
console.log(`- keepConnected: ${keepConnected}`);
console.log(`- token: ${token}`);
const navigate = useNavigate();
const { state, setToken, login, clearToken } = useSessionService();
useEffect(() => {
if (token) {
setToken(token);
} else {
clearToken();
}
}, [token, setToken, clearToken]);
const delay = isDevelopmentEnvironment() ? 20000 : 2000;
useEffect(() => {
if (state === SessionState.CONNECTED) {
const destination = data ? b64_to_utf8(data) : '/';
console.log(`program redirect to: ${destination} (${delay}ms)`);
setTimeout(() => {
navigate(`/${destination}`);
}, delay);
}
}, [state]);
/*
const [searchParams] = useSearchParams();
console.log(`data: ${searchParams.get('data')}`);
console.log(`keepConnected: ${searchParams.get('keepConnected')}`);
console.log(`token: ${searchParams.get('token')}`);
const dataFromParam = useGetCreateActionParams();
console.log(`data group: ${JSON.stringify(dataFromParam, null, 2)}`);
*/
return (
<>
<TopBar />
<PageLayoutInfoCenter width="35%" gap="15px">
<Center w="full">
<Heading size="xl">LOGIN (after SSO) </Heading>
</Center>
<Center w="full">
<Image src={avatar_generic} boxSize="150px" borderRadius="full" />
</Center>
{token === '__CANCEL__' && (
<Text>
<b>ERROR: </b> Request cancel of connection !
</Text>
)}
{token === '__FAIL__' && (
<Text>
<b>ERROR: </b> Connection FAIL !
</Text>
)}
{token === '__LOGOUT__' && (
<Text>
<b>Dis-connected: </b> Redirect soon!{' '}
</Text>
)}
{!['__LOGOUT__', '__FAIL__', '__CANCEL__'].includes(token ?? '') && (
<>
<Text>
<b>Connected: </b> Redirect soon!
</Text>
<Text>
<b>Welcome back: </b> {login}
</Text>
</>
)}
</PageLayoutInfoCenter>
</>
);
};

View File

@ -0,0 +1,14 @@
import { Route, Routes } from 'react-router-dom';
import { ArtistRoutes } from '@/scene/artist/ArtistRoutes';
import { SSOEmpty } from '@/scene/sso/SSOEmpty';
import { SSOPage } from '@/scene/sso/SSOPage';
export const SSORoutes = () => {
return (
<Routes>
<Route path=":data/:keepConnected/:token" element={<SSOPage />} />
<Route path="*" element={<SSOEmpty />} />
</Routes>
);
};

View File

@ -0,0 +1,44 @@
import { ReactNode, createContext, useContext, useMemo } from 'react';
import { SessionState } from '@/service/SessionState';
import {
RightPart,
SessionServiceProps,
useSessionService,
useSessionServiceWrapped,
} from '@/service/session';
export type ServiceContextType = {
session: SessionServiceProps;
};
export const ServiceContext = createContext<ServiceContextType>({
session: {
setToken: (token: string) => {},
clearToken: () => {},
hasReadRight: (part: RightPart) => false,
hasWriteRight: (part: RightPart) => false,
state: SessionState.NO_USER,
},
});
export const useServiceContext = () => useContext(ServiceContext);
export const ServiceContextProvider = ({
children,
}: {
children: ReactNode;
}) => {
const session = useSessionServiceWrapped();
const contextObjectData = useMemo(
() => ({
session,
}),
[session]
);
return (
<ServiceContext.Provider value={contextObjectData}>
{children}
</ServiceContext.Provider>
);
};

View File

@ -0,0 +1,7 @@
export enum SessionState {
NO_USER,
CONNECTING,
CONNECTION_FAIL,
CONNECTED,
DISCONNECT,
}

View File

@ -0,0 +1,125 @@
import { useCallback, useState } from 'react';
import { PartRight, RESTConfig, UserMe, UserResource } from '@/back-api';
import { environment, getApiUrl } from '@/environment';
import { useServiceContext } from '@/service/ServiceContext';
import { SessionState } from '@/service/SessionState';
import { isBrowser } from '@/utils/layout';
import { parseToken } from '@/utils/sso';
const TOKEN_KEY = 'karusic-token-key-storage';
export const USERS = {
ADMIN:
'eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiIxIiwiYXBwbGljYXRpb24iOiJrYXJ1c2ljIiwiaXNzIjoiS2FyQXV0aCIsInJpZ2h0Ijp7ImthcnVzaWMiOnsiQURNSU4iOnRydWUsIlVTRVIiOnRydWV9fSwibG9naW4iOiJIZWVyb1l1aSIsImV4cCI6MTcyNDIwNjc5NCwiaWF0IjoxNzI0MTY2ODM0fQ.TEST_SIGNATURE_FOR_LOCAL_TEST_AND_TEST_E2E',
NO_USER: '',
} as const;
export const getUserToken = () => {
return localStorage.getItem(TOKEN_KEY);
};
export type RightPart = 'ADMIN' | 'USER';
export function getRestConfig(): RESTConfig {
return {
server: getApiUrl(),
token: getUserToken() ?? '',
};
}
export type SessionServiceProps = {
token?: string;
setToken: (token: string) => void;
clearToken: () => void;
login?: string;
hasReadRight: (part: RightPart) => boolean;
hasWriteRight: (part: RightPart) => boolean;
state: SessionState;
};
export const useSessionService = (): SessionServiceProps => {
const { session } = useServiceContext();
return session;
};
export const useSessionServiceWrapped = (): SessionServiceProps => {
const [token, setToken] = useState<string | undefined>(
isBrowser ? (localStorage.getItem(TOKEN_KEY) ?? undefined) : undefined
);
const [state, setState] = useState<SessionState>(SessionState.NO_USER);
const [config, setConfig] = useState<UserMe | undefined>(undefined);
const updateRight = useCallback(() => {
if (isBrowser) {
console.log('Detect a new token...');
setState(SessionState.NO_USER);
setConfig(undefined);
if (token === undefined) {
console.log(` ==> No User`);
setState(SessionState.NO_USER);
localStorage.removeItem(TOKEN_KEY);
} else if (token === '__LOGOUT__') {
console.log(` ==> disconnection: ${token}`);
setState(SessionState.DISCONNECT);
localStorage.removeItem(TOKEN_KEY);
} else if (!['__LOGOUT__', '__FAIL__', '__CANCEL__'].includes(token)) {
console.log(' ==> Login ... (try to keep right)');
setState(SessionState.CONNECTING);
localStorage.setItem(TOKEN_KEY, token);
UserResource.getMe({
restConfig: getRestConfig(),
})
.then((response: UserMe) => {
console.log(` ==> New right arrived to '${response.login}'`);
setState(SessionState.CONNECTED);
setConfig(response);
})
.catch((error) => {
setState(SessionState.CONNECTION_FAIL);
console.log(` ==> Fail to get right: '${error}'`);
localStorage.removeItem(TOKEN_KEY);
});
}
}
}, [localStorage, parseToken, token]);
const setTokenLocal = useCallback(
(token: string) => {
setToken(token);
updateRight();
},
[updateRight, setToken]
);
const clearToken = useCallback(() => {
setToken(undefined);
updateRight();
}, [updateRight, setToken]);
const hasReadRight = useCallback(
(part: RightPart) => {
const right = config?.rights[environment.applName];
if (right === undefined) {
return false;
}
return [PartRight.READ, PartRight.READ_WRITE].includes(right[part]);
},
[config]
);
const hasWriteRight = useCallback(
(part: RightPart) => {
const right = config?.rights[environment.applName];
if (right === undefined) {
return false;
}
return [PartRight.READ, PartRight.READ_WRITE].includes(right[part]);
},
[config]
);
return {
token,
setToken: setTokenLocal,
clearToken,
login: config?.login,
hasReadRight,
hasWriteRight,
state,
};
};

65
front2/src/test/setup.ts Normal file
View File

@ -0,0 +1,65 @@
import '@testing-library/jest-dom';
import mediaQuery from 'css-mediaquery';
window.scrollTo = () => undefined;
global.matchMedia =
global.matchMedia ||
function (query) {
const instance = {
matches: mediaQuery.match(query, {
width: window.innerWidth,
height: window.innerHeight,
}),
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
};
// Listen to resize events from window.resizeTo and update the instance's match
window.addEventListener('resize', () => {
const change = mediaQuery.match(query, {
width: window.innerWidth,
height: window.innerHeight,
});
// eslint-disable-next-line
if (change != instance.matches) {
instance.matches = change;
instance.dispatchEvent('change');
}
});
return instance;
};
// Mock window.resizeTo's impl.
Object.defineProperty(window, 'resizeTo', {
value: (width, height) => {
Object.defineProperty(window, 'innerWidth', {
configurable: true,
writable: true,
value: width,
});
Object.defineProperty(window, 'outerWidth', {
configurable: true,
writable: true,
value: width,
});
Object.defineProperty(window, 'innerHeight', {
configurable: true,
writable: true,
value: height,
});
Object.defineProperty(window, 'outerHeight', {
configurable: true,
writable: true,
value: height,
});
window.dispatchEvent(new Event('resize'));
},
});

27
front2/src/test/utils.tsx Normal file
View File

@ -0,0 +1,27 @@
import { ReactElement, ReactNode } from 'react';
import { ChakraProvider } from '@chakra-ui/react';
import { RenderOptions, render } from '@testing-library/react';
import { BrowserRouter } from 'react-router-dom';
import theme from '@/theme';
const CustomWrapper = ({ children }: { children: ReactNode }) => {
return (
<ChakraProvider theme={theme}>
<BrowserRouter>{children}</BrowserRouter>
</ChakraProvider>
);
};
const customRender = (ui: ReactElement, options: RenderOptions = {}) =>
render(ui, {
wrapper: CustomWrapper,
...options,
});
// re-export everything
export * from '@testing-library/react';
// override render method
export { customRender as render };

View File

@ -0,0 +1,24 @@
export default {
sizes: {
'2xs': {
fontSize: '0.5em',
},
xs: {
fontSize: '0.6em',
},
sm: {
fontSize: '0.7em',
},
md: {
fontSize: '0.8em',
textTransform: 'none',
},
lg: {
fontSize: '0.9em',
textTransform: 'none',
},
},
defaultProps: {
size: 'md',
},
};

View File

@ -0,0 +1,187 @@
import { border, defineStyleConfig, keyframes } from '@chakra-ui/react';
import { isAccessible, mode, transparentize } from '@chakra-ui/theme-tools';
import { shadows } from '@/theme/foundations/shadows';
const shimmer = keyframes`
100% {
transform: translateX(100%);
}
`;
const customVariant = ({
theme,
bg,
bgHover = bg,
bgActive = bgHover,
color,
colorHover = color,
boxColorFocus = '#0x000000',
boxShadowHover = 'outline-over',
}) => {
const isColorAccessible = isAccessible(color, bg, {
size: 'large',
level: 'AA',
})(theme);
return {
bg,
color: isColorAccessible ? color : 'black',
border: '1px solid #00000000',
_focus: {
//border: `1px solid ${boxColorFocus}`,
border: `1px solid #000000`,
},
_hover: {
bg: bgHover,
color: isColorAccessible ? colorHover : 'black',
boxShadow: boxShadowHover,
_disabled: {
bg,
boxShadow: 'none',
},
},
_active: { bg: bgActive },
};
};
export default defineStyleConfig({
variants: {
// Custom variants
'@primary': (props) =>
customVariant({
theme: props.theme,
bg: mode('brand.600', 'brand.300')(props),
bgHover: mode('brand.700', 'brand.400')(props),
bgActive: mode('brand.600', 'brand.300')(props),
color: mode('white', 'brand.900')(props),
boxColorFocus: mode('brand.100', 'brand.600')(props),
}),
'@secondary': (props) =>
customVariant({
theme: props.theme,
bg: mode('brand.100', 'brand.900')(props),
bgHover: mode('brand.200', 'brand.800')(props),
bgActive: mode('brand.300', 'brand.700')(props),
color: mode('brand.700', 'brand.50')(props),
colorHover: mode('brand.800', 'brand.100')(props),
boxColorFocus: mode('brand.900', 'brand.300')(props),
}),
'@danger': (props) =>
customVariant({
theme: props.theme,
bg: mode('error.600', 'error.300')(props),
bgHover: mode('error.700', 'error.400')(props),
bgActive: mode('error.600', 'error.300')(props),
color: mode('white', 'error.900')(props),
boxColorFocus: mode('error.900', 'error.500')(props),
}),
'@progress': (props) => ({
...customVariant({
theme: props.theme,
bg: mode(`${props.colorScheme}.500`, `${props.colorScheme}.300`)(props),
bgHover: mode(
`${props.colorScheme}.600`,
`${props.colorScheme}.400`
)(props),
bgActive: mode(
`${props.colorScheme}.700`,
`${props.colorScheme}.500`
)(props),
color: mode('white', `${props.colorScheme}.900`)(props),
boxColorFocus: mode(
`${props.colorScheme}.900`,
`${props.colorScheme}.600`
)(props),
}),
overflow: 'hidden',
_after: !props.isLoading
? {
position: 'absolute',
top: 0,
right: 0,
bottom: 0,
left: 0,
transform: 'translateX(-100%)',
bgGradient: `linear(90deg, ${transparentize(
`${props.colorScheme}.100`,
0
)(props.theme)} 0, ${transparentize(
`${props.colorScheme}.100`,
0.2
)(props.theme)} 20%, ${transparentize(
`${props.colorScheme}.100`,
0.5
)(props.theme)} 60%, ${transparentize(
`${props.colorScheme}.100`,
0
)(props.theme)})`,
animation: `${shimmer} 3s infinite`,
content: '""',
}
: undefined,
}),
'@menu': (props) => ({
bg: mode('back.100', 'back.800')(props),
color: mode('brand.900', 'brand.100')(props),
borderRadius: 0,
border: 0,
_hover: { background: mode('back.300', 'back.600')(props) },
_focus: { border: 'none' },
fontSize: '20px',
textTransform: 'uppercase',
}),
// Default variants
solid: (props) => ({
bg:
props.colorScheme === 'gray'
? mode('gray.100', 'whiteAlpha.100')(props)
: `${props.colorScheme}.600`,
_hover: {
bg:
props.colorScheme === 'gray'
? mode('gray.200', 'whiteAlpha.200')(props)
: `${props.colorScheme}.700`,
},
_focus: {
boxShadow: `outline-${props.colorScheme}`,
},
}),
light: (props) => ({
bg:
props.colorScheme === 'gray'
? mode('gray.100', 'whiteAlpha.100')(props)
: `${props.colorScheme}.100`,
color:
props.colorScheme === 'gray'
? mode('gray.700', 'whiteAlpha.700')(props)
: `${props.colorScheme}.700`,
_hover: {
bg:
props.colorScheme === 'gray'
? mode('gray.200', 'whiteAlpha.200')(props)
: `${props.colorScheme}.200`,
color:
props.colorScheme === 'gray'
? mode('gray.800', 'whiteAlpha.800')(props)
: `${props.colorScheme}.800`,
},
_focus: {
boxShadow: `outline-${props.colorScheme}`,
},
}),
ghost: (props) => ({
bg: transparentize(`${props.colorScheme}.50`, 0.05)(props.theme),
borderRadius: 'full',
_hover: {
bg: transparentize(`${props.colorScheme}.50`, 0.15)(props.theme),
},
_focus: {
boxShadow: 'none',
},
}),
},
});

View File

@ -0,0 +1,5 @@
export default {
defaultProps: {
colorScheme: 'brand',
},
};

View File

@ -0,0 +1,19 @@
import { defineStyleConfig } from '@chakra-ui/react';
import { mode } from '@chakra-ui/theme-tools';
const flexTheme = defineStyleConfig({
variants: {
'@menu': (props) => ({
bg: mode('back.100', 'back.800')(props),
color: mode('brand.900', 'brand.100')(props),
borderRadius: 0,
border: 0,
_hover: { background: mode('back.300', 'back.600')(props) },
_focus: { border: 'none' },
fontSize: '20px',
}),
},
});
export default flexTheme;

View File

@ -0,0 +1,12 @@
export { default as Badge } from './badge';
export { default as Button } from './button';
export { default as Checkbox } from './checkbox';
export { default as Input } from './input';
export { default as NumberInput } from './numberInput';
export { default as Popover } from './popover';
export { default as Radio } from './radio';
export { default as Select } from './select';
export { default as Switch } from './switch';
export { default as Textarea } from './textarea';
export { default as Modal } from './modal';
export { default as Flex } from './flex';

View File

@ -0,0 +1,25 @@
import { getColor, mode } from '@chakra-ui/theme-tools';
export default {
variants: {
outline: (props) => {
const focusBorderColor = getColor(
props.theme,
props.focusBorderColor
? props.focusBorderColor
: mode('brand.500', 'brand.300')(props)
);
return {
field: {
bg: mode('gray.50', 'whiteAlpha.50')(props),
borderColor: mode('gray.200', 'whiteAlpha.100')(props),
color: mode('gray.800', 'gray.50')(props),
_focus: {
borderColor: focusBorderColor,
boxShadow: `0 0 0 1px ${focusBorderColor}`,
},
},
};
},
},
};

View File

@ -0,0 +1,17 @@
import { modalAnatomy as parts } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(parts.keys);
const baseStyle = definePartsStyle({
header: {
textAlign: 'center',
},
});
const modalTheme = defineMultiStyleConfig({
baseStyle,
});
export default modalTheme;

View File

@ -0,0 +1,33 @@
import { numberInputAnatomy } from '@chakra-ui/anatomy';
import { createMultiStyleConfigHelpers } from '@chakra-ui/react';
import { getColor, mode } from '@chakra-ui/theme-tools';
const { definePartsStyle, defineMultiStyleConfig } =
createMultiStyleConfigHelpers(numberInputAnatomy.keys);
const baseStyle = definePartsStyle((props) => {
const focusBorderColor = getColor(
props.theme,
props.focusBorderColor
? props.focusBorderColor
: mode('brand.500', 'brand.300')(props)
);
return {
field: {
border: 0,
_focusVisible: {
borderColor: focusBorderColor,
boxShadow: `0 0 0 1px ${focusBorderColor}`,
ring: '1px',
ringColor: focusBorderColor,
ringOffset: '1px',
ringOffsetColor: focusBorderColor,
},
},
};
});
export default defineMultiStyleConfig({
baseStyle,
});

View File

@ -0,0 +1,77 @@
export default {
sizes: {
'3xs': {
content: {
width: '3xs',
},
},
'2xs': {
content: {
width: '2xs',
},
},
xs: {
content: {
width: 'xs',
},
},
sm: {
content: {
width: 'sm',
},
},
md: {
content: {
width: 'md',
},
},
lg: {
content: {
width: 'lg',
},
},
xl: {
content: {
width: 'xl',
},
},
'2xl': {
content: {
width: '2xl',
},
},
'3xl': {
content: {
width: '3xl',
},
},
'4xl': {
content: {
width: '4xl',
},
},
'5xl': {
content: {
width: '5xl',
},
},
'6xl': {
content: {
width: '6xl',
},
},
'7xl': {
content: {
width: '7xl',
},
},
'8xl': {
content: {
width: '8xl',
},
},
},
defaultProps: {
size: 'xs',
},
};

View File

@ -0,0 +1,5 @@
export default {
defaultProps: {
colorScheme: 'brand',
},
};

View File

@ -0,0 +1,24 @@
import { getColor, mode } from '@chakra-ui/theme-tools';
export default {
variants: {
outline: (props) => {
const focusBorderColor = getColor(
props.theme,
props.focusBorderColor
? props.focusBorderColor
: mode('brand.500', 'brand.300')(props)
);
return {
field: {
bg: mode('gray.50', 'whiteAlpha.50')(props),
borderColor: mode('blackAlpha.100', 'whiteAlpha.100')(props),
_focus: {
borderColor: focusBorderColor,
boxShadow: `0 0 0 1px ${focusBorderColor}`,
},
},
};
},
},
};

View File

@ -0,0 +1,5 @@
export default {
defaultProps: {
colorScheme: 'brand',
},
};

View File

@ -0,0 +1,22 @@
import { getColor, mode } from '@chakra-ui/theme-tools';
export default {
variants: {
outline: (props) => {
const focusBorderColor = getColor(
props.theme,
props.focusBorderColor
? props.focusBorderColor
: mode('brand.500', 'brand.300')(props)
);
return {
bg: mode('gray.50', 'whiteAlpha.50')(props),
borderColor: mode('blackAlpha.100', 'whiteAlpha.100')(props),
_focus: {
borderColor: focusBorderColor,
boxShadow: `0 0 0 1px ${focusBorderColor}`,
},
};
},
},
};

View File

@ -0,0 +1,127 @@
type ThemeModel = {
50: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
};
const isLightMode = false;
const reverseColor = (data: ThemeModel) => {
return {
50: data[900],
100: data[800],
200: data[700],
300: data[600],
400: data[500],
500: data[400],
600: data[300],
700: data[200],
800: data[100],
900: data[50],
};
};
const back = {
50: '#ebf4fa',
100: '#d1dbe0',
200: '#b6c2c9',
300: '#99aab4',
400: '#7c939e',
500: '#637985',
600: '#4d5e67',
700: '#37444a',
800: '#1f292e',
900: '#020f12',
};
const brand = {
50: '#e3edff',
100: '#b6c9fd',
200: '#88a5f7',
300: '#5a81f2',
400: '#2c5ded',
500: '#1543d4',
600: '#0d34a5',
700: '#062577',
800: '#02164a',
900: '#00071e',
};
const normalText = {
50: '#f2f2f2',
100: '#d9d9d9',
200: '#bfbfbf',
300: '#a6a6a6',
400: '#8c8c8c',
500: '#737373',
600: '#595959',
700: '#404040',
800: '#262626',
900: '#0d0d0d',
};
const green = {
50: '#f0fdf4',
100: '#dcfce7',
200: '#bbf7d0',
300: '#86efac',
400: '#4ade80',
500: '#22c55e',
600: '#16a34a',
700: '#15803d',
800: '#166534',
900: '#14532d',
};
const blue = {
50: '#eff6ff',
100: '#dbeafe',
200: '#bfdbfe',
300: '#93c5fd',
400: '#60a5fa',
500: '#3b82f6',
600: '#2563eb',
700: '#1d4ed8',
800: '#1e40af',
900: '#1e3a8a',
};
const orange = {
50: '#fff7ed',
100: '#ffedd5',
200: '#fed7aa',
300: '#fdba74',
400: '#fb923c',
500: '#f97316',
600: '#ea580c',
700: '#c2410c',
800: '#9a3412',
900: '#7c2d12',
};
const red = {
50: '#fef2f2',
100: '#fee2e2',
200: '#fecaca',
300: '#fca5a5',
400: '#f87171',
500: '#ef4444',
600: '#dc2626',
700: '#b91c1c',
800: '#991b1b',
900: '#7f1d1d',
};
export const colors = {
// Update me with other Tailwind colors or with https://smart-swatch.netlify.app/
brand: brand,
back: back,
text: normalText,
/// ????
success: green,
error: red,
warning: orange,
} as const;

Some files were not shown because too many files have changed in this diff Show More