some cleanup, split backend and frontend

This commit is contained in:
2023-07-29 13:04:26 +02:00
parent 14210cf0cf
commit faa0aa62c8
69 changed files with 13194 additions and 12319 deletions

5
frontend/.prettierrc Normal file
View File

@@ -0,0 +1,5 @@
{
"trailingComma": "all",
"tabWidth": 4,
"endOfLine": "auto"
}

View File

@@ -1,7 +1,4 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { pathsToModuleNameMapper } = require("ts-jest");
const { compilerOptions } = require("./tsconfig");
module.exports = {
preset: "ts-jest",
moduleNameMapper: {
@@ -11,6 +8,7 @@ module.exports = {
"react-spring/renderprops":
"<rootDir>/node_modules/react-spring/renderprops.cjs",
"react-spring": "<rootDir>/node_modules/react-spring/web.cjs",
"~(.*)": "<rootDir>/$1",
},
setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
testEnvironment: "jsdom",

View File

@@ -26,6 +26,7 @@
"eslint-plugin-react": "^7.33.0",
"eslint-plugin-react-hooks": "^4.6.0",
"flush-promises": "^1.0.2",
"io-ts": "^2.2.20",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
"jest-junit": "^14.0.1",
@@ -60,7 +61,8 @@
"@types/react-redux": "^7.1.25",
"@types/react-router": "^5.1.20",
"@types/react-router-dom": "^5.3.3",
"@types/spark-md5": "^3.0.2"
"@types/spark-md5": "^3.0.2",
"parcel-resolver-ts-base-url": "^1.3.1"
}
},
"node_modules/@aashutoshrathi/word-wrap": {
@@ -6353,6 +6355,12 @@
"node": ">= 6"
}
},
"node_modules/fp-ts": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.1.tgz",
"integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA==",
"peer": true
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -6937,6 +6945,14 @@
"node": ">= 0.4"
}
},
"node_modules/io-ts": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.20.tgz",
"integrity": "sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA==",
"peerDependencies": {
"fp-ts": "^2.5.0"
}
},
"node_modules/is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -8063,6 +8079,12 @@
"node": ">=6"
}
},
"node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"node_modules/jsx-ast-utils": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
@@ -9031,6 +9053,23 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/parcel-resolver-ts-base-url": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/parcel-resolver-ts-base-url/-/parcel-resolver-ts-base-url-1.3.1.tgz",
"integrity": "sha512-/GfSk1P6oW7uXtViKlqGtH406kQ17Cu+bKHVTdbuIh8t8hICsJrYRqeDDx1dJWsiMAKvtXzXT0TBWLkdusS8PQ==",
"dev": true,
"dependencies": {
"jsonc-parser": "^3.2.0"
},
"engines": {
"node": ">=12",
"parcel": "^2"
},
"peerDependencies": {
"@parcel/plugin": "^2.0.0",
"parcel": "^2.0.0"
}
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -15762,6 +15801,12 @@
"mime-types": "^2.1.12"
}
},
"fp-ts": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/fp-ts/-/fp-ts-2.16.1.tgz",
"integrity": "sha512-by7U5W8dkIzcvDofUcO42yl9JbnHTEDBrzu3pt5fKT+Z4Oy85I21K80EYJYdjQGC2qum4Vo55Ag57iiIK4FYuA==",
"peer": true
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -16150,6 +16195,12 @@
"side-channel": "^1.0.4"
}
},
"io-ts": {
"version": "2.2.20",
"resolved": "https://registry.npmjs.org/io-ts/-/io-ts-2.2.20.tgz",
"integrity": "sha512-Rq2BsYmtwS5vVttie4rqrOCIfHCS9TgpRLFpKQCM1wZBBRY9nWVGmEvm2FnDbSE2un1UE39DvFpTR5UL47YDcA==",
"requires": {}
},
"is-arguments": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -16949,6 +17000,12 @@
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
},
"jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"dev": true
},
"jsx-ast-utils": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
@@ -17613,6 +17670,15 @@
"get-port": "^4.2.0"
}
},
"parcel-resolver-ts-base-url": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/parcel-resolver-ts-base-url/-/parcel-resolver-ts-base-url-1.3.1.tgz",
"integrity": "sha512-/GfSk1P6oW7uXtViKlqGtH406kQ17Cu+bKHVTdbuIh8t8hICsJrYRqeDDx1dJWsiMAKvtXzXT0TBWLkdusS8PQ==",
"dev": true,
"requires": {
"jsonc-parser": "^3.2.0"
}
},
"parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",

View File

@@ -3,9 +3,11 @@
"scripts": {
"start": "parcel src/index.html",
"build": "parcel build src/index.html",
"test": "jest",
"lint": "eslint ./src/** --ext .js,.jsx,.ts,.tsx",
"lint-fix": "eslint ./src/** --ext .js,.jsx,.ts,.tsx --fix",
"test": "jest"
"prettier-check": "prettier src/**/*.ts src/**/*.tsx --check",
"prettify": "prettier src/**/*.ts src/**/*.tsx --write"
},
"dependencies": {
"@blueprintjs/core": "^4.14.1",
@@ -28,6 +30,7 @@
"eslint-plugin-react": "^7.33.0",
"eslint-plugin-react-hooks": "^4.6.0",
"flush-promises": "^1.0.2",
"io-ts": "^2.2.20",
"jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2",
"jest-junit": "^14.0.1",

View File

@@ -1,6 +1,5 @@
import { Position, Toaster } from "@blueprintjs/core";
import { isNumber } from "class-validator";
import { IPhotoReqJSON } from "../../src/entity/Photo";
import { IPhotoReqJSON } from "./shared/types";
export const AppToaster = Toaster.create({
className: "recipe-toaster",

View File

@@ -2,29 +2,24 @@ import "./Home.scss";
import {
Alignment,
Breadcrumbs,
Button,
Classes,
IBreadcrumbProps,
Icon,
Menu,
MenuItem,
Navbar,
Popover,
Spinner,
} from "@blueprintjs/core";
import * as React from "react";
import { connect } from "react-redux";
import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
import { animated, config, Transition } from "react-spring/renderprops";
import { Dispatch } from "redux";
import { IUserJSON } from "../../../src/entity/User";
import { IUserJSON } from "../shared/types";
import { Account } from "../Account/Account";
import { Overview } from "../Photos/Overview";
import { toggleDarkMode } from "../redux/localSettings/actions";
import { IAppState } from "../redux/reducers";
import { logoutUser } from "../redux/user/actions";
import { Photo } from "../Photos/Photo";
import { PhotoRoute } from "../Photos/PhotoRoute";
import { UploadStatus } from "./UploadStatus";
@@ -95,28 +90,24 @@ export class HomeComponent extends React.PureComponent<IHomeProps> {
transform: "translate3d(400px,0,0)",
}}
>
{(_location: any) => (style: any) =>
(
<animated.div
style={style}
className="viewComponent"
>
<Switch location={_location}>
<Route
path="/account"
component={Account}
/>
<Route
path="/photos/:id"
component={PhotoRoute}
/>
<Route
path="/"
component={Overview}
/>
</Switch>
</animated.div>
)}
{(_location: any) => (style: any) => (
<animated.div
style={style}
className="viewComponent"
>
<Switch location={_location}>
<Route
path="/account"
component={Account}
/>
<Route
path="/photos/:id"
component={PhotoRoute}
/>
<Route path="/" component={Overview} />
</Switch>
</animated.div>
)}
</Transition>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { Button, Icon, Popover, Spinner } from "@blueprintjs/core";
import { Button } from "@blueprintjs/core";
import * as React from "react";
import { connect } from "react-redux";
import { IAppState } from "../redux/reducers";
@@ -12,9 +12,9 @@ export interface IUploadStatusComponentProps {
uploadingQueue: number;
}
export const UploadStatusComponent: React.FunctionComponent<IUploadStatusComponentProps> = (
props,
) => {
export const UploadStatusComponent: React.FunctionComponent<
IUploadStatusComponentProps
> = (props) => {
const { creatingNow, creatingQueue, uploadingNow, uploadingQueue } = props;
const uploading =
creatingNow > 0 ||

View File

@@ -3,20 +3,18 @@ import "./Overview.scss";
import * as React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { IAppState } from "../redux/reducers";
import { IAppState } from "~/src/redux/reducers";
import {
photosDeleteCancel,
photosDeleteStart,
photosLoadStart,
} from "../redux/photos/actions";
import { IPhotoReqJSON } from "../../../src/entity/Photo";
import { LoadingStub } from "../LoadingStub";
import { IPhotoReqJSON } from "~/src/shared/types";
import { PhotoCard } from "./PhotoCard";
import {
Alignment,
Button,
Classes,
H1,
H2,
H3,
Navbar,
@@ -25,8 +23,7 @@ import {
} from "@blueprintjs/core";
import { UploadButton } from "./UploadButton";
import { Photo } from "./Photo";
import { getPhotoThumbPath } from "../redux/api/photos";
import { showDeletionToast } from "../AppToaster";
import { showDeletionToast } from "~/src/AppToaster";
export interface IOverviewComponentProps {
photos: IPhotoReqJSON[];

View File

@@ -2,13 +2,9 @@ import "./Photo.scss";
import * as React from "react";
import { connect } from "react-redux";
import { Dispatch } from "redux";
import { IPhotoReqJSON } from "../../../src/entity/Photo";
import { IPhotoReqJSON } from "~/src/shared/types";
import { LoadingStub } from "../LoadingStub";
import {
fetchPhoto,
getPhotoImgPath,
getPhotoThumbPath,
} from "../redux/api/photos";
import { getPhotoImgPath, getPhotoThumbPath } from "../redux/api/photos";
import { photoLoadStart } from "../redux/photos/actions";
import { IPhotoState } from "../redux/photos/reducer";
import { IAppState } from "../redux/reducers";

View File

@@ -8,15 +8,14 @@ import {
Spinner,
} from "@blueprintjs/core";
import * as React from "react";
import { IPhotoReqJSON } from "../../../src/entity/Photo";
import { getPhotoImgPath, getPhotoThumbPath } from "../redux/api/photos";
import { IPhotoReqJSON } from "~/src/shared/types";
import { getPhotoThumbPath } from "../redux/api/photos";
import { showDeletionToast } from "../AppToaster";
import { Dispatch } from "redux";
import { photosDeleteCancel, photosDeleteStart } from "../redux/photos/actions";
import { connect } from "react-redux";
import { LoadingStub } from "../LoadingStub";
import { RouteComponentProps, withRouter } from "react-router";
import { LargeSize, PreviewSize } from "./helper";
import { PreviewSize } from "./helper";
export interface IPhotoCardComponentProps extends RouteComponentProps {
photo: IPhotoReqJSON;

View File

@@ -1,7 +1,4 @@
import {
IUserLoginRespBody,
IUserSignupRespBody,
} from "../../../../../src/routes/users";
import { IUserLoginRespBody, IUserSignupRespBody } from "~/src/shared/types";
import { fetchJSON } from "../utils";
export async function login(

View File

@@ -1,12 +1,12 @@
import { IPhotoReqJSON } from "../../../../src/entity/Photo";
import { IPhotoReqJSON } from "~/src/shared/types";
import {
IPhotosByIDGetRespBody,
IPhotosDeleteRespBody,
IPhotosListRespBody,
IPhotosNewRespBody,
IPhotosUploadRespBody,
} from "../../../../src/routes/photos";
import { apiRoot } from "../../env";
} from "~/src/shared/types";
import { apiRoot } from "~src/env";
import { fetchJSONAuth } from "./utils";
export function getPhotoImgPath(photo: IPhotoReqJSON): string {

View File

@@ -1,10 +1,11 @@
import { fetchJSONAuth } from "../utils";
import { IUserEditRespBody, IUserGetRespBody } from "../../../../../src/routes/users";
import { IUserEditRespBody, IUserGetRespBody } from "~/src/shared/types";
export async function fetchUser(): Promise<IUserGetRespBody> {
return (fetchJSONAuth("/users/user", "GET") as unknown) as Promise<
IUserGetRespBody
>;
return fetchJSONAuth(
"/users/user",
"GET",
) as unknown as Promise<IUserGetRespBody>;
}
export async function changeUserPassword(

View File

@@ -1,4 +1,5 @@
import { apiRoot } from "../../env";
import { apiRoot } from "~src/env";
import { IAPIResponse } from "~/src/shared/types";
let token: string | null;
@@ -14,12 +15,12 @@ export function deleteToken(): void {
token = null;
}
export async function fetchJSON(
export async function fetchJSON<T>(
path: string,
method: string,
body?: string | Record<string, unknown> | File,
headers?: Record<string, string>,
): Promise<Record<string, unknown>> {
): Promise<IAPIResponse<T>> {
if (typeof body === "object" && !(body instanceof File)) {
body = JSON.stringify(body);
headers = {
@@ -27,7 +28,7 @@ export async function fetchJSON(
"Content-Type": "application/json",
};
}
// TODO: io-ts or something like that
if (body instanceof File) {
const formData = new FormData();
formData.append("photo", body);
@@ -37,7 +38,7 @@ export async function fetchJSON(
body: formData,
});
const json = (await response.json()) as Record<string, unknown>;
return json;
return json as unknown as IAPIResponse<T>;
}
const response = await fetch(apiRoot + path, {
@@ -46,15 +47,15 @@ export async function fetchJSON(
headers,
});
const json = (await response.json()) as Record<string, unknown>;
return json;
return json as unknown as IAPIResponse<T>;
}
export async function fetchJSONAuth(
export async function fetchJSONAuth<T>(
path: string,
method: string,
body?: string | Record<string, unknown> | File,
headers?: Record<string, unknown>,
): Promise<Record<string, unknown>> {
): Promise<IAPIResponse<T>> {
if (token) {
return fetchJSON(path, method, body, {
...headers,

View File

@@ -1,5 +1,5 @@
import { Action } from "redux";
import { IUserAuthJSON } from "../../../../src/entity/User";
import { IUserAuthJSON } from "~/src/shared/types";
export enum AuthTypes {
AUTH_START = "AUTH_START",

View File

@@ -1,7 +1,7 @@
import { Reducer } from "react";
import { setToken } from "../../redux/api/utils";
import { UserAction, UserTypes } from "../../redux/user/actions";
import { setToken } from "~src/redux/api/utils";
import { UserAction, UserTypes } from "~src/redux/user/actions";
import { AuthAction, AuthTypes } from "./actions";
export interface IAuthState {

View File

@@ -8,7 +8,7 @@ import {
race,
takeLatest,
} from "redux-saga/effects";
import { login, signup } from "../../redux/api/auth";
import { login, signup } from "~src/redux/api/auth";
import {
authFail,

View File

@@ -1,5 +1,5 @@
import { Reducer } from "react";
import { UserAction, UserTypes } from "../../redux/user/actions";
import { UserAction, UserTypes } from "~src/redux/user/actions";
import { LocalSettingsAction, LocalSettingsTypes } from "./actions";

View File

@@ -1,10 +1,10 @@
import { Action } from "redux";
import { IPhotoReqJSON, Photo } from "../../../../src/entity/Photo";
import { IPhotoReqJSON } from "~src/shared/types";
import {
showPhotoCreateFailToast,
showPhotoUploadFileFailToast,
showPhotoUploadJSONFailToast,
} from "../../AppToaster";
} from "~src/AppToaster";
export enum PhotoTypes {
PHOTOS_LOAD_START = "PHOTOS_LOAD",

View File

@@ -1,6 +1,6 @@
import { Reducer } from "redux";
import { IPhotoJSON, IPhotoReqJSON } from "../../../../src/entity/Photo";
import { UserAction, UserTypes } from "../../redux/user/actions";
import { IPhotoReqJSON } from "~/src/shared/types";
import { UserAction, UserTypes } from "~src/redux/user/actions";
import { PhotoAction, PhotoTypes } from "./actions";
export interface IPhotoState {

View File

@@ -1,9 +1,7 @@
import {
all,
call,
cancel,
delay,
fork,
put,
race,
takeLatest,
@@ -18,7 +16,7 @@ import {
fetchPhoto,
fetchPhotosList,
uploadPhoto,
} from "../../redux/api/photos";
} from "~src/redux/api/photos";
import {
IPhotosDeleteStartAction,
IPhotoLoadStartAction,
@@ -35,14 +33,12 @@ import {
photosLoadSuccess,
photosStartFetchingSpinner,
PhotoTypes,
photoUploadFail,
photoUploadFailWithFile,
photoUploadQueue,
photoUploadStart,
photoUploadSuccess,
} from "./actions";
import { IPhotosNewRespBody } from "../../../../src/routes/photos";
import { IPhotosListPagination } from "../../../../src/shared/types";
import { IPhotosNewRespBody, IPhotosListPagination } from "~src/shared/types";
// Thanks, https://dev.to/qortex/compute-md5-checksum-for-a-file-in-typescript-59a4
function computeChecksumMd5(file: File): Promise<string> {

View File

@@ -1,6 +1,9 @@
import { Action } from "redux";
import { IUserAuthJSON, IUserJSON } from "../../../../src/entity/User";
import { showPasswordNotSavedToast, showPasswordSavedToast } from "../../AppToaster";
import { IUserAuthJSON } from "~src/shared/types";
import {
showPasswordNotSavedToast,
showPasswordSavedToast,
} from "~src/AppToaster";
export enum UserTypes {
USER_GET = "USER_GET",

View File

@@ -1,6 +1,6 @@
import { Reducer } from "react";
import { IUserJSON } from "../../../../src/entity/User";
import { AuthAction, AuthTypes } from "../../redux/auth/actions";
import { IUserJSON } from "~/src/entity/User";
import { AuthAction, AuthTypes } from "~src/redux/auth/actions";
import { UserAction, UserTypes } from "./actions";
export interface IUserState {

View File

@@ -1,5 +1,5 @@
import { all, call, delay, put, race, takeLatest } from "redux-saga/effects";
import { changeUserPassword, fetchUser } from "../../redux/api/user";
import { changeUserPassword, fetchUser } from "~src/redux/api/user";
import {
getUserFail,

1
frontend/src/shared Symbolic link
View File

@@ -0,0 +1 @@
../../shared

View File

@@ -15,7 +15,13 @@
"strictNullChecks": true,
"skipLibCheck": true,
"isolatedModules": true,
"downlevelIteration": true
"downlevelIteration": true,
"baseUrl": "./",
"paths": {
"~*": [
"./*"
]
}
},
"include": ["./src/**/*.ts", "./src/**/*.tsx", "./jest.config.js"]
}