more type safety with zod

This commit is contained in:
2023-07-30 12:56:11 +02:00
parent c114a72619
commit 503d42121e
86 changed files with 17389 additions and 644 deletions

2
.gitignore vendored
View File

@@ -1,6 +1,6 @@
.DS_Store .DS_Store
.idea/ .idea/
node_modules/ /node_modules
build/ build/
tmp/ tmp/
temp/ temp/

View File

@@ -15,7 +15,6 @@
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"exifreader": "^4.13.0", "exifreader": "^4.13.0",
"hasha": "^5.2.2", "hasha": "^5.2.2",
"io-ts": "^2.2.20",
"jsonwebtoken": "^9.0.1", "jsonwebtoken": "^9.0.1",
"koa": "^2.14.2", "koa": "^2.14.2",
"koa-body": "^5.0.0", "koa-body": "^5.0.0",
@@ -2951,12 +2950,6 @@
"url": "https://ko-fi.com/tunnckoCore/commissions" "url": "https://ko-fi.com/tunnckoCore/commissions"
} }
}, },
"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/fresh": { "node_modules/fresh": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -3602,14 +3595,6 @@
"node": ">= 0.4" "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-array-buffer": { "node_modules/is-array-buffer": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -4514,9 +4499,9 @@
} }
}, },
"node_modules/make-dir/node_modules/semver": { "node_modules/make-dir/node_modules/semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
@@ -9572,12 +9557,6 @@
"qs": "^6.11.0" "qs": "^6.11.0"
} }
}, },
"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
},
"fresh": { "fresh": {
"version": "0.5.2", "version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
@@ -10027,12 +10006,6 @@
"side-channel": "^1.0.4" "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-array-buffer": { "is-array-buffer": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@@ -10694,9 +10667,9 @@
}, },
"dependencies": { "dependencies": {
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
} }
} }
}, },

View File

@@ -20,7 +20,6 @@
"class-validator": "^0.14.0", "class-validator": "^0.14.0",
"exifreader": "^4.13.0", "exifreader": "^4.13.0",
"hasha": "^5.2.2", "hasha": "^5.2.2",
"io-ts": "^2.2.20",
"jsonwebtoken": "^9.0.1", "jsonwebtoken": "^9.0.1",
"koa": "^2.14.2", "koa": "^2.14.2",
"koa-body": "^5.0.0", "koa-body": "^5.0.0",
@@ -48,12 +47,12 @@
"@types/hasha": "^3.0.1", "@types/hasha": "^3.0.1",
"@types/jsonwebtoken": "^9.0.2", "@types/jsonwebtoken": "^9.0.2",
"@types/koa": "^2.13.7", "@types/koa": "^2.13.7",
"@types/koa__cors": "^4.0.0",
"@types/koa__router": "^12.0.0",
"@types/koa-logger": "^3.1.2", "@types/koa-logger": "^3.1.2",
"@types/koa-send": "^4.1.3", "@types/koa-send": "^4.1.3",
"@types/koa-sslify": "^4.0.3", "@types/koa-sslify": "^4.0.3",
"@types/koa-static": "^4.0.2", "@types/koa-static": "^4.0.2",
"@types/koa__cors": "^4.0.0",
"@types/koa__router": "^12.0.0",
"@types/mime-types": "^2.1.1", "@types/mime-types": "^2.1.1",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/mysql": "^2.15.21", "@types/mysql": "^2.15.21",
@@ -85,4 +84,4 @@
"pre-commit": "npm run lint-all && npm run prettier-check" "pre-commit": "npm run lint-all && npm run prettier-check"
} }
} }
} }

View File

@@ -15,8 +15,15 @@ import { config, EnvType } from "~config";
import { userRouter } from "~routes/users"; import { userRouter } from "~routes/users";
import { devRouter } from "~routes/dev"; import { devRouter } from "~routes/dev";
import { photosRouter } from "~routes/photos"; import { photosRouter } from "~routes/photos";
import { TUserJWT } from "~shared/types";
export const app = new Koa(); export interface IAppState extends Koa.DefaultState {
user?: TUserJWT;
}
export interface IAppContext extends Koa.DefaultContext {}
export const app = new Koa<IAppState, IAppContext>();
const tmpPath = path.join(config.dataDir, "tmp"); const tmpPath = path.join(config.dataDir, "tmp");

View File

@@ -2,7 +2,7 @@ import * as path from "path";
import * as fs from "fs/promises"; import * as fs from "fs/promises";
import * as mime from "mime-types"; import * as mime from "mime-types";
import * as jwt from "jsonwebtoken"; import * as jwt from "jsonwebtoken";
import { IPhotoReqJSON, IPhotoJSON } from "~/shared/types"; import { TPhotoReqJSON, TPhotoJSON } from "~/shared/types";
import { import {
BaseEntity, BaseEntity,
@@ -212,7 +212,7 @@ export class Photo extends BaseEntity {
} }
} }
public async toJSON(): Promise<IPhotoJSON> { public async toJSON(): Promise<TPhotoJSON> {
if (!isNumber(this.user.id)) { if (!isNumber(this.user.id)) {
throw new Error("User not loaded"); throw new Error("User not loaded");
} }
@@ -232,7 +232,7 @@ export class Photo extends BaseEntity {
}; };
} }
public async toReqJSON(): Promise<IPhotoReqJSON> { public async toReqJSON(): Promise<TPhotoReqJSON> {
const token = await this.getJWTToken(); const token = await this.getJWTToken();
return { ...(await this.toJSON()), accessToken: token }; return { ...(await this.toJSON()), accessToken: token };
} }

View File

@@ -2,7 +2,7 @@ import * as bcrypt from "bcrypt";
import * as jwt from "jsonwebtoken"; import * as jwt from "jsonwebtoken";
import * as path from "path"; import * as path from "path";
import * as fs from "fs/promises"; import * as fs from "fs/promises";
import { IUserJSON, IUserAuthJSON } from "~/shared/types"; import { TUserJSON, TUserAuthJSON } from "~/shared/types";
import { import {
AfterInsert, AfterInsert,
@@ -83,12 +83,12 @@ export class User extends BaseEntity {
return validateOrReject(this); return validateOrReject(this);
} }
public toJSON(): IUserJSON { public toJSON(): TUserJSON {
const { id, username, isAdmin } = this; const { id, username, isAdmin } = this;
return { id, username, isAdmin }; return { id, username, isAdmin };
} }
public toAuthJSON(): IUserAuthJSON { public toAuthJSON(): TUserAuthJSON {
const json = this.toJSON(); const json = this.toJSON();
return { ...json, jwt: this.toJWT() }; return { ...json, jwt: this.toJWT() };
} }

View File

@@ -1,10 +1,13 @@
import * as Router from "@koa/router"; import * as Router from "@koa/router";
import { Photo } from "~entity/Photo"; import { Photo } from "~entity/Photo";
import { User } from "~entity/User"; import { User } from "~entity/User";
import { IAppContext, IAppState } from "~app";
export const devRouter = new Router(); export const devRouter = new Router<IAppState, IAppContext>();
devRouter.get("/dev/clean", async (ctx) => { type ContextType = Parameters<Parameters<(typeof devRouter)["post"]>["2"]>["0"];
devRouter.get("/dev/clean", async (ctx: ContextType) => {
await Photo.remove(await Photo.find()); await Photo.remove(await Photo.find());
await User.remove(await User.find()); await User.remove(await User.find());
ctx.body = { success: true }; ctx.body = { success: true };

View File

@@ -1,19 +1,18 @@
import * as Router from "@koa/router"; import * as Router from "@koa/router";
import { Photo } from "~entity/Photo"; import { Photo } from "~entity/Photo";
import { import {
IPhotoReqJSON, TPhotoReqJSON,
IPhotosNewRespBody, TPhotosNewRespBody,
IPhotosNewPostBody, TPhotoByIDDeleteRespBody,
IPhotoByIDDeleteRespBody, TPhotosUploadRespBody,
IPhotosUploadRespBody, TPhotosListRespBody,
IPhotosListRespBody, TPhotosByIDGetRespBody,
IPhotosByIDGetRespBody, TPhotosDeleteRespBody,
IPhotosDeleteRespBody, PhotosListPagination,
IPhotosDeleteBody, PhotosNewPostBody,
IPhotosGetShowTokenByID, PhotoJSON,
IPhotoShowToken, TPhotosGetShowTokenByIDRespBody,
IAPIResponse, PhotosDeleteBody,
IPhotosListPagination,
} from "~/shared/types"; } from "~/shared/types";
import send = require("koa-send"); import send = require("koa-send");
import { getHash, getSize } from "~util"; import { getHash, getSize } from "~util";
@@ -21,24 +20,25 @@ import * as jwt from "jsonwebtoken";
import { config } from "~config"; import { config } from "~config";
import { ValidationError } from "class-validator"; import { ValidationError } from "class-validator";
import { In } from "typeorm"; import { In } from "typeorm";
import { IAppContext, IAppState } from "~app";
export const photosRouter = new Router(); export const photosRouter = new Router<IAppState, IAppContext>();
photosRouter.post("/photos/new", async (ctx) => { // Typescript requires explicit type annotations for CFA......
type ContextType = Parameters<
Parameters<(typeof photosRouter)["post"]>["2"]
>["0"];
photosRouter.post("/photos/new", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
const { user } = ctx.state; const { user } = ctx.state;
const body = ctx.request.body as IPhotosNewPostBody; const body = PhotosNewPostBody.parse(ctx.request.body);
const { hash, size, format } = body; const { hash, size, format } = body;
if (!(hash && size && format)) { const photo = Photo.create({ user, hash, size, format });
ctx.throw(400);
return;
}
const photo = new Photo(user, hash, size, format);
try { try {
await photo.save(); await photo.save();
@@ -47,13 +47,12 @@ photosRouter.post("/photos/new", async (ctx) => {
const photo = await Photo.findOne({ hash, size, user }); const photo = await Photo.findOne({ hash, size, user });
if (!photo) { if (!photo) {
ctx.throw(404); ctx.throw(404);
return;
} }
ctx.body = { ctx.body = {
error: false, error: false,
data: await photo.toReqJSON(), data: await photo.toReqJSON(),
} as IPhotosNewRespBody; } as TPhotosNewRespBody;
return; return;
} }
if ( if (
@@ -61,7 +60,6 @@ photosRouter.post("/photos/new", async (ctx) => {
(Array.isArray(e) && e.some((e) => e instanceof ValidationError)) (Array.isArray(e) && e.some((e) => e instanceof ValidationError))
) { ) {
ctx.throw(400); ctx.throw(400);
return;
} }
console.log(e); console.log(e);
ctx.throw(500); ctx.throw(500);
@@ -70,10 +68,10 @@ photosRouter.post("/photos/new", async (ctx) => {
ctx.body = { ctx.body = {
error: false, error: false,
data: await photo.toReqJSON(), data: await photo.toReqJSON(),
} as IPhotosNewRespBody; } as TPhotosNewRespBody;
}); });
photosRouter.post("/photos/upload/:id", async (ctx) => { photosRouter.post("/photos/upload/:id", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
@@ -84,32 +82,28 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
if (!id) { if (!id) {
ctx.throw(400); ctx.throw(400);
return;
} }
const { user } = ctx.state; const { user } = ctx.state;
const photo = await Photo.findOne({ id: parseInt(id), user }); const photo = await Photo.findOne({ id: parseInt(id), user });
if (!photo) { if (!photo) {
ctx.throw(404); ctx.throw(404);
return;
} }
if (!ctx.request.files || Object.keys(ctx.request.files).length === 0) { if (!ctx.request.files || Object.keys(ctx.request.files).length === 0) {
ctx.throw(400, "No file"); ctx.throw(400, "No file");
return;
} }
if (photo.uploaded) { if (photo.uploaded) {
ctx.throw(400, "Already uploaded"); ctx.throw(400, "Already uploaded");
return;
} }
if (ctx.request.files) { if (ctx.request.files) {
const files = ctx.request.files; const files = ctx.request.files;
if (Object.keys(files).length > 1) { if (Object.keys(files).length > 1) {
ctx.throw(400, "Too many files"); ctx.throw(400, "Too many files");
return;
} }
const file = Object.values(files)[0]; const file = Object.values(files)[0];
if (Array.isArray(file)) { if (Array.isArray(file)) {
throw "more than one file uploaded"; throw "more than one file uploaded";
@@ -120,7 +114,6 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
if (photoHash !== photo.hash || photoSize !== photo.size) { if (photoHash !== photo.hash || photoSize !== photo.size) {
ctx.throw(400, "Wrong photo"); ctx.throw(400, "Wrong photo");
return;
} }
try { try {
@@ -134,14 +127,14 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
ctx.body = { ctx.body = {
error: false, error: false,
data: await photo.toReqJSON(), data: await photo.toReqJSON(),
} as IPhotosUploadRespBody; } as TPhotosUploadRespBody;
}); });
/** /**
export interface IPhotosByIDPatchBody { export interface TPhotosByIDPatchBody {
} }
export type IPhotosByIDPatchRespBody = IAPIResponse<IPhotoReqJSON>; export type TPhotosByIDPatchRespBody = IAPIResponse<TPhotoReqJSON>;
photosRouter.patch("/photos/byID/:id", async (ctx) => { photosRouter.patch("/photos/byID/:id", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
return; return;
@@ -180,7 +173,7 @@ photosRouter.patch("/photos/byID/:id", async (ctx) => {
}); });
*/ */
photosRouter.get("/photos/list", async (ctx) => { photosRouter.get("/photos/list", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
@@ -200,8 +193,8 @@ photosRouter.get("/photos/list", async (ctx) => {
skip = parseInt(skip); skip = parseInt(skip);
} }
if (!num || num > IPhotosListPagination) { if (!num || num > PhotosListPagination) {
num = IPhotosListPagination; num = PhotosListPagination;
} }
const photos = await Photo.find({ const photos = await Photo.find({
@@ -211,17 +204,17 @@ photosRouter.get("/photos/list", async (ctx) => {
order: { shotAt: "DESC" }, order: { shotAt: "DESC" },
}); });
const photosList: IPhotoReqJSON[] = await Promise.all( const photosList: TPhotoReqJSON[] = await Promise.all(
photos.map(async (photo) => await photo.toReqJSON()), photos.map(async (photo) => await photo.toReqJSON()),
); );
ctx.body = { ctx.body = {
error: false, error: false,
data: photosList, data: photosList,
} as IPhotosListRespBody; } as TPhotosListRespBody;
}); });
photosRouter.get("/photos/byID/:id", async (ctx) => { photosRouter.get("/photos/byID/:id", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
@@ -232,7 +225,6 @@ photosRouter.get("/photos/byID/:id", async (ctx) => {
if (!id) { if (!id) {
ctx.throw(400); ctx.throw(400);
return;
} }
const { user } = ctx.state; const { user } = ctx.state;
@@ -241,16 +233,15 @@ photosRouter.get("/photos/byID/:id", async (ctx) => {
if (!photo) { if (!photo) {
ctx.throw(404); ctx.throw(404);
return;
} }
ctx.body = { ctx.body = {
error: false, error: false,
data: await photo.toReqJSON(), data: await photo.toReqJSON(),
} as IPhotosByIDGetRespBody; } as TPhotosByIDGetRespBody;
}); });
photosRouter.get("/photos/showByID/:id/:token", async (ctx) => { photosRouter.get("/photos/showByID/:id/:token", async (ctx: ContextType) => {
const { id, token } = ctx.params as { const { id, token } = ctx.params as {
id: string | undefined; id: string | undefined;
token: string | undefined; token: string | undefined;
@@ -258,7 +249,6 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
if (!(id && token)) { if (!(id && token)) {
ctx.throw(400); ctx.throw(400);
return;
} }
try { try {
@@ -267,7 +257,7 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
ctx.throw(401); ctx.throw(401);
} }
const photoReqJSON = jwt.decode(token) as IPhotoReqJSON; const photoReqJSON = PhotoJSON.parse(jwt.decode(token));
const { user } = photoReqJSON; const { user } = photoReqJSON;
const photo = await Photo.findOne({ const photo = await Photo.findOne({
@@ -277,7 +267,6 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
if (!photo) { if (!photo) {
ctx.throw(404); ctx.throw(404);
return;
} }
if ( if (
@@ -292,7 +281,7 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
await send(ctx, await photo.getReadyPath("original")); await send(ctx, await photo.getReadyPath("original"));
}); });
photosRouter.get("/photos/showByID/:id", async (ctx) => { photosRouter.get("/photos/showByID/:id", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
@@ -303,7 +292,6 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => {
if (!id) { if (!id) {
ctx.throw(400); ctx.throw(400);
return;
} }
const { user } = ctx.state; const { user } = ctx.state;
@@ -312,7 +300,6 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => {
if (!photo) { if (!photo) {
ctx.throw(404); ctx.throw(404);
return;
} }
if ( if (
@@ -327,7 +314,7 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => {
await send(ctx, await photo.getReadyPath("original")); await send(ctx, await photo.getReadyPath("original"));
}); });
photosRouter.get("/photos/getShowByIDToken/:id", async (ctx) => { photosRouter.get("/photos/getShowByIDToken/:id", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
@@ -338,7 +325,6 @@ photosRouter.get("/photos/getShowByIDToken/:id", async (ctx) => {
if (!id) { if (!id) {
ctx.throw(400); ctx.throw(400);
return;
} }
const { user } = ctx.state; const { user } = ctx.state;
@@ -346,15 +332,14 @@ photosRouter.get("/photos/getShowByIDToken/:id", async (ctx) => {
const photo = await Photo.findOne({ id: parseInt(id), user }); const photo = await Photo.findOne({ id: parseInt(id), user });
if (!photo) { if (!photo) {
ctx.throw(404); ctx.throw(404);
return;
} }
const token = await photo.getJWTToken(); const token = await photo.getJWTToken();
ctx.body = { error: false, data: token } as IPhotosGetShowTokenByID; ctx.body = { error: false, data: token } as TPhotosGetShowTokenByIDRespBody;
}); });
photosRouter.delete("/photos/byID/:id", async (ctx) => { photosRouter.delete("/photos/byID/:id", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
@@ -365,7 +350,6 @@ photosRouter.delete("/photos/byID/:id", async (ctx) => {
if (!id) { if (!id) {
ctx.throw(400); ctx.throw(400);
return;
} }
const { user } = ctx.state; const { user } = ctx.state;
@@ -374,7 +358,6 @@ photosRouter.delete("/photos/byID/:id", async (ctx) => {
if (!photo) { if (!photo) {
ctx.throw(404); ctx.throw(404);
return;
} }
await photo.remove(); await photo.remove();
@@ -382,22 +365,17 @@ photosRouter.delete("/photos/byID/:id", async (ctx) => {
ctx.body = { ctx.body = {
error: false, error: false,
data: true, data: true,
} as IPhotoByIDDeleteRespBody; } as TPhotoByIDDeleteRespBody;
}); });
photosRouter.post("/photos/delete", async (ctx) => { photosRouter.post("/photos/delete", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
const body = ctx.request.body as IPhotosDeleteBody; const body = PhotosDeleteBody.parse(ctx.request.body);
const { photos } = body; const { photos } = body;
if (!photos || !Array.isArray(photos) || photos.length == 0) {
ctx.throw(400);
return;
}
const { user } = ctx.state; const { user } = ctx.state;
try { try {
await Photo.delete({ await Photo.delete({
@@ -408,11 +386,11 @@ photosRouter.post("/photos/delete", async (ctx) => {
ctx.body = { ctx.body = {
error: false, error: false,
data: true, data: true,
} as IPhotosDeleteRespBody; } as TPhotosDeleteRespBody;
} catch (e) { } catch (e) {
ctx.body = { ctx.body = {
data: null, data: null,
error: "Internal server error", error: "Internal server error",
} as IPhotosDeleteRespBody; } as TPhotosDeleteRespBody;
} }
}); });

View File

@@ -2,70 +2,65 @@ import * as Router from "@koa/router";
import { getConfigValue, ConfigKey } from "~entity/Config"; import { getConfigValue, ConfigKey } from "~entity/Config";
import { User } from "~entity/User"; import { User } from "~entity/User";
import { import {
IUserJWT, TUserJWT,
IUserGetRespBody, TUserGetRespBody,
IUserEditRespBody, TUserEditRespBody,
IUserSignupBody, TUserSignupBody,
IUserSignupRespBody, TUserSignupRespBody,
IUserLoginRespBody, TUserLoginRespBody,
IUserEditBody, TUserEditBody,
IUserLoginBody, TUserLoginBody,
UserLoginBody,
UserSignupBody,
UserEditBody,
} from "~/shared/types"; } from "~/shared/types";
import { IAppContext, IAppState } from "~app";
export const userRouter = new Router(); export const userRouter = new Router<IAppState, IAppContext>();
userRouter.get("/users/user", async (ctx) => { type ContextType = Parameters<
Parameters<(typeof userRouter)["post"]>["2"]
>["0"];
userRouter.get("/users/user", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
const jwt = ctx.state.user as IUserJWT; const jwt = ctx.state.user;
const user = await User.findOne(jwt.id); const user = await User.findOne(jwt.id);
if (!user) { if (!user) {
ctx.throw(401); ctx.throw(401);
return;
} }
ctx.body = { error: false, data: user.toAuthJSON() } as IUserGetRespBody; ctx.body = { error: false, data: user.toAuthJSON() } as TUserGetRespBody;
}); });
userRouter.post("/users/login", async (ctx) => { userRouter.post("/users/login", async (ctx: ContextType) => {
const request = ctx.request; const request = ctx.request;
if (!request.body) { if (!request.body) {
ctx.throw(400); ctx.throw(400);
} }
const { username, password } = request.body as IUserLoginBody; const { username, password } = UserLoginBody.parse(request.body);
if (!(username && password)) {
ctx.throw(400);
return;
}
const user = await User.findOne({ username }); const user = await User.findOne({ username });
if (!user || !(await user.verifyPassword(password))) { if (!user || !(await user.verifyPassword(password))) {
ctx.throw(404, "User not found"); ctx.throw(404, "User not found");
return;
} }
ctx.body = { error: false, data: user.toAuthJSON() } as IUserLoginRespBody; ctx.body = { error: false, data: user.toAuthJSON() } as TUserLoginRespBody;
}); });
userRouter.post("/users/signup", async (ctx) => { userRouter.post("/users/signup", async (ctx: ContextType) => {
const request = ctx.request; const request = ctx.request;
if (!request.body) { if (!request.body) {
ctx.throw(400); ctx.throw(400);
} }
const { username, password, email } = request.body as IUserSignupBody; const { username, password, email } = UserSignupBody.parse(request.body);
if (!(username && password && email)) {
ctx.throw(400);
return;
}
const user = new User(username, email); const user = new User(username, email);
const users = await User.find(); const users = await User.find();
@@ -91,33 +86,30 @@ userRouter.post("/users/signup", async (ctx) => {
ctx.throw(500); ctx.throw(500);
} }
ctx.body = { error: false, data: user.toAuthJSON() } as IUserSignupRespBody; ctx.body = { error: false, data: user.toAuthJSON() } as TUserSignupRespBody;
}); });
userRouter.post("/users/edit", async (ctx) => { userRouter.post("/users/edit", async (ctx: ContextType) => {
if (!ctx.state.user) { if (!ctx.state.user) {
ctx.throw(401); ctx.throw(401);
} }
const jwt = ctx.state.user as IUserJWT; const jwt = ctx.state.user;
const user = await User.findOne(jwt.id); const user = await User.findOne(jwt.id);
const request = ctx.request; const request = ctx.request;
if (!user) { if (!user) {
ctx.throw(401); ctx.throw(401);
return;
} }
if (!request.body) { if (!request.body) {
ctx.throw(400); ctx.throw(400);
return;
} }
const { password } = request.body as IUserEditBody; const { password } = UserEditBody.parse(request.body);
if (!password) { if (!password) {
ctx.throw(400); ctx.throw(400);
return;
} }
await user.setPassword(password); await user.setPassword(password);
@@ -129,5 +121,5 @@ userRouter.post("/users/edit", async (ctx) => {
ctx.throw(500); ctx.throw(500);
} }
ctx.body = { error: false, data: user.toAuthJSON() } as IUserEditRespBody; ctx.body = { error: false, data: user.toAuthJSON() } as TUserEditRespBody;
}); });

View File

@@ -4,10 +4,11 @@ import * as request from "supertest";
import { getConnection } from "typeorm"; import { getConnection } from "typeorm";
import { app } from "~app"; import { app } from "~app";
import { Photo } from "~entity/Photo"; import { Photo } from "~entity/Photo";
import { IPhotoReqJSON , import {
IPhotosDeleteBody, TPhotoReqJSON,
IPhotosListRespBody, TPhotosDeleteBody,
IPhotosNewPostBody, TPhotosListRespBody,
TPhotosNewPostBody,
} from "~shared/types"; } from "~shared/types";
import * as fs from "fs/promises"; import * as fs from "fs/promises";
import { constants as fsConstants } from "fs"; import { constants as fsConstants } from "fs";
@@ -60,7 +61,7 @@ describe("photos", function () {
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
const usedPhoto = await seed.dogPhoto.toReqJSON(); const usedPhoto = await seed.dogPhoto.toReqJSON();
@@ -85,9 +86,7 @@ describe("photos", function () {
Authorization: `Bearer ${seed.user2.toJWT()}`, Authorization: `Bearer ${seed.user2.toJWT()}`,
}) })
.expect(200); .expect(200);
expect(parseInt(response.header["content-length"])).to.equal( expect(parseInt(response.get("content-length"))).to.equal(dogFileSize);
dogFileSize,
);
}); });
it("should delete a photo after file has been deleted", async function () { it("should delete a photo after file has been deleted", async function () {
@@ -97,12 +96,10 @@ describe("photos", function () {
Authorization: `Bearer ${seed.user2.toJWT()}`, Authorization: `Bearer ${seed.user2.toJWT()}`,
}) })
.expect(200); .expect(200);
expect(parseInt(response.header["content-length"])).to.equal( expect(parseInt(response.get("content-length"))).to.equal(dogFileSize);
dogFileSize,
);
await fs.unlink(await seed.dogPhoto.getReadyPath("original")); await fs.unlink(await seed.dogPhoto.getReadyPath("original"));
const response2 = await request(callback) await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}`) .get(`/photos/showByID/${seed.dogPhoto.id}`)
.set({ .set({
Authorization: `Bearer ${seed.user2.toJWT()}`, Authorization: `Bearer ${seed.user2.toJWT()}`,
@@ -122,13 +119,13 @@ describe("photos", function () {
const dogSmallThumbSize = ( const dogSmallThumbSize = (
await fs.stat(seed.dogPhoto.getThumbPath("512")) await fs.stat(seed.dogPhoto.getThumbPath("512"))
).size; ).size;
expect(parseInt(response.header["content-length"])).to.equal( expect(parseInt(response.get("content-length"))).to.equal(
dogSmallThumbSize, dogSmallThumbSize,
); );
await fs.unlink(await seed.dogPhoto.getReadyPath("512")); await fs.unlink(await seed.dogPhoto.getReadyPath("512"));
await fs.unlink(await seed.dogPhoto.getReadyPath("original")); await fs.unlink(await seed.dogPhoto.getReadyPath("original"));
const response2 = await request(callback) await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}?size=512`) .get(`/photos/showByID/${seed.dogPhoto.id}?size=512`)
.set({ .set({
Authorization: `Bearer ${seed.user2.toJWT()}`, Authorization: `Bearer ${seed.user2.toJWT()}`,
@@ -146,7 +143,7 @@ describe("photos", function () {
Authorization: `Bearer ${seed.user2.toJWT()}`, Authorization: `Bearer ${seed.user2.toJWT()}`,
}) })
.expect(200); .expect(200);
expect(parseInt(response.header["content-length"])).to.be.lessThan( expect(parseInt(response.get("content-length"))).to.be.lessThan(
dogFileSize, dogFileSize,
); );
}); });
@@ -161,12 +158,12 @@ describe("photos", function () {
const dogSmallThumbSize = ( const dogSmallThumbSize = (
await fs.stat(seed.dogPhoto.getThumbPath("512")) await fs.stat(seed.dogPhoto.getThumbPath("512"))
).size; ).size;
expect(parseInt(response.header["content-length"])).to.equal( expect(parseInt(response.get("content-length"))).to.equal(
dogSmallThumbSize, dogSmallThumbSize,
); );
await fs.unlink(seed.dogPhoto.getThumbPath("512")); await fs.unlink(seed.dogPhoto.getThumbPath("512"));
const response2 = await request(callback) await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}?size=512`) .get(`/photos/showByID/${seed.dogPhoto.id}?size=512`)
.set({ .set({
Authorization: `Bearer ${seed.user2.toJWT()}`, Authorization: `Bearer ${seed.user2.toJWT()}`,
@@ -175,7 +172,7 @@ describe("photos", function () {
const dogSmallThumbSize2 = ( const dogSmallThumbSize2 = (
await fs.stat(seed.dogPhoto.getThumbPath("512")) await fs.stat(seed.dogPhoto.getThumbPath("512"))
).size; ).size;
expect(parseInt(response.header["content-length"])).to.equal( expect(parseInt(response.get("content-length"))).to.equal(
dogSmallThumbSize2, dogSmallThumbSize2,
); );
}); });
@@ -188,7 +185,7 @@ describe("photos", function () {
}) })
.expect(200); .expect(200);
const listRespBody = listResp.body as IPhotosListRespBody; const listRespBody = listResp.body as TPhotosListRespBody;
if (listRespBody.error !== false) { if (listRespBody.error !== false) {
expect(listResp.body.error).to.be.false; expect(listResp.body.error).to.be.false;
@@ -201,7 +198,7 @@ describe("photos", function () {
const listAnyResp = await request(callback) const listAnyResp = await request(callback)
.get(`/photos/showByID/${photos[0].id}/${photos[0].accessToken}`) .get(`/photos/showByID/${photos[0].id}/${photos[0].accessToken}`)
.expect(200); .expect(200);
expect(parseInt(listAnyResp.header["content-length"])).to.be.oneOf([ expect(parseInt(listAnyResp.get("content-length"))).to.be.oneOf([
dogFileSize, dogFileSize,
catFileSize, catFileSize,
]); ]);
@@ -219,9 +216,7 @@ describe("photos", function () {
const response = await request(callback) const response = await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}/${token}`) .get(`/photos/showByID/${seed.dogPhoto.id}/${token}`)
.expect(200); .expect(200);
expect(parseInt(response.header["content-length"])).to.equal( expect(parseInt(response.get("content-length"))).to.equal(dogFileSize);
dogFileSize,
);
const tokenSelfSigned = jwt.sign( const tokenSelfSigned = jwt.sign(
await seed.dogPhoto.toReqJSON(), await seed.dogPhoto.toReqJSON(),
@@ -234,7 +229,7 @@ describe("photos", function () {
const responseSS = await request(callback) const responseSS = await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}/${tokenSelfSigned}`) .get(`/photos/showByID/${seed.dogPhoto.id}/${tokenSelfSigned}`)
.expect(200); .expect(200);
expect(parseInt(responseSS.header["content-length"])).to.equal( expect(parseInt(responseSS.get("content-length"))).to.equal(
dogFileSize, dogFileSize,
); );
}); });
@@ -248,7 +243,7 @@ describe("photos", function () {
}, },
); );
const response = await request(callback) await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}/${token}`) .get(`/photos/showByID/${seed.dogPhoto.id}/${token}`)
.expect(401); .expect(401);
}); });
@@ -275,17 +270,17 @@ describe("photos", function () {
hash: dogHash, hash: dogHash,
size: dogSize, size: dogSize,
format: dogFormat, format: dogFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
expect(photo.hash).to.be.equal(dogHash); expect(photo.hash).to.be.equal(dogHash);
const dbPhoto = await Photo.findOneOrFail({ const dbPhoto = await Photo.findOneOrFail({
id: photo.id, id: photo.id,
user: seed.user1.id as any, user: { id: seed.user1.id },
}); });
expect(dbPhoto.hash).to.be.equal(dogHash); expect(dbPhoto.hash).to.be.equal(dogHash);
@@ -302,7 +297,7 @@ describe("photos", function () {
const dbPhotoUpl = await Photo.findOneOrFail({ const dbPhotoUpl = await Photo.findOneOrFail({
id: photo.id, id: photo.id,
user: seed.user1.id as any, user: { id: seed.user1.id },
}); });
expect(dbPhotoUpl.hash).to.be.equal(dogHash); expect(dbPhotoUpl.hash).to.be.equal(dogHash);
expect(await dbPhotoUpl.origFileExists()).to.be.equal(true); expect(await dbPhotoUpl.origFileExists()).to.be.equal(true);
@@ -317,9 +312,7 @@ describe("photos", function () {
}) })
.expect(200); .expect(200);
expect(parseInt(showResp.header["content-length"])).to.equal( expect(parseInt(showResp.get("content-length"))).to.equal(dogFileSize);
dogFileSize,
);
}); });
it("should create, upload and show a png file", async function () { it("should create, upload and show a png file", async function () {
@@ -333,17 +326,17 @@ describe("photos", function () {
hash: pngHash, hash: pngHash,
size: pngSize, size: pngSize,
format: pngFormat, format: pngFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
expect(photo.hash).to.be.equal(pngHash); expect(photo.hash).to.be.equal(pngHash);
const dbPhoto = await Photo.findOneOrFail({ const dbPhoto = await Photo.findOneOrFail({
id: photo.id, id: photo.id,
user: seed.user1.id as any, user: { id: seed.user1.id },
}); });
expect(dbPhoto.hash).to.be.equal(pngHash); expect(dbPhoto.hash).to.be.equal(pngHash);
@@ -360,7 +353,7 @@ describe("photos", function () {
const dbPhotoUpl = await Photo.findOneOrFail({ const dbPhotoUpl = await Photo.findOneOrFail({
id: photo.id, id: photo.id,
user: seed.user1.id as any, user: { id: seed.user1.id },
}); });
expect(dbPhotoUpl.hash).to.be.equal(pngHash); expect(dbPhotoUpl.hash).to.be.equal(pngHash);
expect(dbPhotoUpl.format).to.be.equal(pngFormat); expect(dbPhotoUpl.format).to.be.equal(pngFormat);
@@ -377,9 +370,7 @@ describe("photos", function () {
}) })
.expect(200); .expect(200);
expect(parseInt(showResp.header["content-length"])).to.equal( expect(parseInt(showResp.get("content-length"))).to.equal(pngFileSize);
pngFileSize,
);
}); });
it("should not create a photo twice", async function () { it("should not create a photo twice", async function () {
@@ -393,12 +384,12 @@ describe("photos", function () {
hash: dogHash, hash: dogHash,
size: dogSize, size: dogSize,
format: dogFormat, format: dogFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const response2 = await request(callback) await request(callback)
.post("/photos/new") .post("/photos/new")
.set({ .set({
Authorization: `Bearer ${seed.user1.toJWT()}`, Authorization: `Bearer ${seed.user1.toJWT()}`,
@@ -408,7 +399,7 @@ describe("photos", function () {
hash: dogHash, hash: dogHash,
size: dogSize, size: dogSize,
format: dogFormat, format: dogFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(200); .expect(200);
const dbPhoto = await Photo.find({ const dbPhoto = await Photo.find({
@@ -430,17 +421,17 @@ describe("photos", function () {
hash: dogHash, hash: dogHash,
size: dogSize, size: dogSize,
format: dogFormat, format: dogFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
expect(photo.hash).to.be.equal(dogHash); expect(photo.hash).to.be.equal(dogHash);
const dbPhoto = await Photo.findOneOrFail({ const dbPhoto = await Photo.findOneOrFail({
id: photo.id, id: photo.id,
user: seed.user1.id as any, user: { id: seed.user1.id },
}); });
expect(dbPhoto.hash).to.be.equal(dogHash); expect(dbPhoto.hash).to.be.equal(dogHash);
@@ -473,9 +464,7 @@ describe("photos", function () {
}) })
.expect(200); .expect(200);
expect(parseInt(showResp.header["content-length"])).to.equal( expect(parseInt(showResp.get("content-length"))).to.equal(dogFileSize);
dogFileSize,
);
}); });
it("should not upload a wrong photo", async function () { it("should not upload a wrong photo", async function () {
@@ -489,17 +478,17 @@ describe("photos", function () {
hash: dogHash, hash: dogHash,
size: dogSize, size: dogSize,
format: dogFormat, format: dogFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
expect(photo.hash).to.be.equal(dogHash); expect(photo.hash).to.be.equal(dogHash);
const dbPhoto = await Photo.findOneOrFail({ const dbPhoto = await Photo.findOneOrFail({
id: photo.id, id: photo.id,
user: seed.user1.id as any, user: { id: seed.user1.id },
}); });
expect(dbPhoto.hash).to.be.equal(dogHash); expect(dbPhoto.hash).to.be.equal(dogHash);
@@ -516,7 +505,7 @@ describe("photos", function () {
expect(await dbPhoto.origFileExists()).to.be.equal(false); expect(await dbPhoto.origFileExists()).to.be.equal(false);
const showResp = await request(callback) await request(callback)
.get(`/photos/showByID/${photo.id}`) .get(`/photos/showByID/${photo.id}`)
.set({ .set({
Authorization: `Bearer ${seed.user1.toJWT()}`, Authorization: `Bearer ${seed.user1.toJWT()}`,
@@ -535,17 +524,17 @@ describe("photos", function () {
hash: dogHash, hash: dogHash,
size: dogSize, size: dogSize,
format: dogFormat, format: dogFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
expect(photo.hash).to.be.equal(dogHash); expect(photo.hash).to.be.equal(dogHash);
const dbPhoto = await Photo.findOneOrFail({ const dbPhoto = await Photo.findOneOrFail({
id: photo.id, id: photo.id,
user: seed.user1.id as any, user: { id: seed.user1.id },
}); });
expect(dbPhoto.hash).to.be.equal(dogHash); expect(dbPhoto.hash).to.be.equal(dogHash);
expect(await dbPhoto.origFileExists()).to.be.equal(false); expect(await dbPhoto.origFileExists()).to.be.equal(false);
@@ -573,17 +562,17 @@ describe("photos", function () {
hash: dogHash, hash: dogHash,
size: dogSize, size: dogSize,
format: dogFormat, format: dogFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
expect(photo.hash).to.be.equal(dogHash); expect(photo.hash).to.be.equal(dogHash);
const dbPhoto = await Photo.findOneOrFail({ const dbPhoto = await Photo.findOneOrFail({
id: photo.id, id: photo.id,
user: seed.user1.id as any, user: { id: seed.user1.id },
}); });
expect(dbPhoto.hash).to.be.equal(dogHash); expect(dbPhoto.hash).to.be.equal(dogHash);
expect(await dbPhoto.origFileExists()).to.be.equal(false); expect(await dbPhoto.origFileExists()).to.be.equal(false);
@@ -608,7 +597,7 @@ describe("photos", function () {
}); });
it("should not create a photo with weird properties", async function () { it("should not create a photo with weird properties", async function () {
const response = await request(callback) await request(callback)
.post("/photos/new") .post("/photos/new")
.set({ .set({
Authorization: `Bearer ${seed.user1.toJWT()}`, Authorization: `Bearer ${seed.user1.toJWT()}`,
@@ -618,7 +607,7 @@ describe("photos", function () {
hash: "../test", hash: "../test",
size: "33333", size: "33333",
format: dogFormat, format: dogFormat,
} as IPhotosNewPostBody) } as TPhotosNewPostBody)
.expect(400); .expect(400);
}); });
@@ -635,13 +624,13 @@ describe("photos", function () {
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
expect(photo.name).to.be.equal("Test1"); expect(photo.name).to.be.equal("Test1");
const dbPhoto = await Photo.findOne({ const dbPhoto = await Photo.findOne({
id: seed.dogPhoto.id, id: seed.dogPhoto.id,
user: seed.user1.id as any, user: {id:seed.user1.id} ,
}); });
expect(dbPhoto.name).to.be.equal("Test1"); expect(dbPhoto.name).to.be.equal("Test1");
@@ -662,7 +651,7 @@ describe("photos", function () {
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photos = response.body.data as IPhotoReqJSON[]; const photos = response.body.data as TPhotoReqJSON[];
const userPhotos = [ const userPhotos = [
await seed.dogPhoto.toReqJSON(), await seed.dogPhoto.toReqJSON(),
await seed.catPhoto.toReqJSON(), await seed.catPhoto.toReqJSON(),
@@ -685,7 +674,7 @@ describe("photos", function () {
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
const photo = response.body.data as IPhotoReqJSON; const photo = response.body.data as TPhotoReqJSON;
const usedPhoto = seed.catPhoto.toReqJSON(); const usedPhoto = seed.catPhoto.toReqJSON();
@@ -704,7 +693,7 @@ describe("photos", function () {
}) })
.send({ .send({
photos: [await seed.dogPhoto.toReqJSON()], photos: [await seed.dogPhoto.toReqJSON()],
} as IPhotosDeleteBody) } as TPhotosDeleteBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;
@@ -736,7 +725,7 @@ describe("photos", function () {
await seed.dogPhoto.toReqJSON(), await seed.dogPhoto.toReqJSON(),
await seed.catPhoto.toReqJSON(), await seed.catPhoto.toReqJSON(),
], ],
} as IPhotosDeleteBody) } as TPhotosDeleteBody)
.expect(200); .expect(200);
expect(response.body.error).to.be.false; expect(response.body.error).to.be.false;

View File

@@ -5,13 +5,13 @@ import { getConnection } from "typeorm";
import { app } from "~app"; import { app } from "~app";
import { User } from "~entity/User"; import { User } from "~entity/User";
import { import {
IUserEditBody, TUserEditBody,
IUserEditRespBody, TUserEditRespBody,
IUserGetRespBody, TUserGetRespBody,
IUserLoginBody, TUserLoginBody,
IUserLoginRespBody, TUserLoginRespBody,
IUserSignupBody, TUserSignupBody,
IUserSignupRespBody, TUserSignupRespBody,
} from "~shared/types"; } from "~shared/types";
import { allowSignups, ISeed, seedDB } from "./util"; import { allowSignups, ISeed, seedDB } from "./util";
@@ -43,13 +43,13 @@ describe("users", function () {
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200); .expect(200);
const body = response.body as IUserGetRespBody; const body = response.body as TUserGetRespBody;
if (body.error !== false) { if (body.error !== false) {
assert(false); assert(false);
return;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { jwt: _, ...user } = body.data; const { jwt: _, ...user } = body.data;
expect(user).to.deep.equal(seed.user1.toJSON()); expect(user).to.deep.equal(seed.user1.toJSON());
@@ -59,17 +59,17 @@ describe("users", function () {
const response = await request(callback) const response = await request(callback)
.post("/users/login") .post("/users/login")
.set({ "Content-Type": "application/json" }) .set({ "Content-Type": "application/json" })
.send({ username: "User1", password: "User1" } as IUserLoginBody) .send({ username: "User1", password: "User1" } as TUserLoginBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200); .expect(200);
const body = response.body as IUserLoginRespBody; const body = response.body as TUserLoginRespBody;
if (body.error !== false) { if (body.error !== false) {
assert(false); assert(false);
return;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { jwt: _, ...user } = response.body.data; const { jwt: _, ...user } = response.body.data;
expect(user).to.deep.equal(seed.user1.toJSON()); expect(user).to.deep.equal(seed.user1.toJSON());
}); });
@@ -78,10 +78,10 @@ describe("users", function () {
const response = await request(callback) const response = await request(callback)
.post("/users/login") .post("/users/login")
.set({ "Content-Type": "application/json" }) .set({ "Content-Type": "application/json" })
.send({ username: "User1", password: "asdf" } as IUserLoginBody) .send({ username: "User1", password: "asdf" } as TUserLoginBody)
.expect(404); .expect(404);
const body = response.body as IUserLoginRespBody; const body = response.body as TUserLoginRespBody;
expect(body.error).to.be.equal("User not found"); expect(body.error).to.be.equal("User not found");
expect(body.data).to.be.false; expect(body.data).to.be.false;
}); });
@@ -96,17 +96,17 @@ describe("users", function () {
username: "NUser1", username: "NUser1",
password: "NUser1", password: "NUser1",
email: "nuser1@users.com", email: "nuser1@users.com",
} as IUserSignupBody) } as TUserSignupBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200); .expect(200);
const body = response.body as IUserSignupRespBody; const body = response.body as TUserSignupRespBody;
if (body.error !== false) { if (body.error !== false) {
assert(false); assert(false);
return;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { jwt: _, ...user } = body.data; const { jwt: _, ...user } = body.data;
const newUser = await User.findOneOrFail({ username: "NUser1" }); const newUser = await User.findOneOrFail({ username: "NUser1" });
expect(user).to.deep.equal(newUser.toJSON()); expect(user).to.deep.equal(newUser.toJSON());
@@ -120,11 +120,11 @@ describe("users", function () {
username: "NUser1", username: "NUser1",
password: "NUser1", password: "NUser1",
email: "nuser1@users.com", email: "nuser1@users.com",
} as IUserSignupBody) } as TUserSignupBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(400); .expect(400);
const body = response.body as IUserSignupRespBody; const body = response.body as TUserSignupRespBody;
expect(body.error).to.be.equal("Signups not allowed"); expect(body.error).to.be.equal("Signups not allowed");
expect(body.data).to.be.false; expect(body.data).to.be.false;
@@ -140,17 +140,17 @@ describe("users", function () {
username: "NUser1", username: "NUser1",
password: "NUser1", password: "NUser1",
email: "nuser1@users.com", email: "nuser1@users.com",
} as IUserSignupBody) } as TUserSignupBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200); .expect(200);
const body = response.body as IUserSignupRespBody; const body = response.body as TUserSignupRespBody;
if (body.error !== false) { if (body.error !== false) {
assert(false); assert(false);
return;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { jwt: _, ...user } = body.data; const { jwt: _, ...user } = body.data;
const newUser = await User.findOneOrFail({ username: "NUser1" }); const newUser = await User.findOneOrFail({ username: "NUser1" });
expect(user).to.deep.equal(newUser.toJSON()); expect(user).to.deep.equal(newUser.toJSON());
@@ -163,11 +163,11 @@ describe("users", function () {
username: "NUser2", username: "NUser2",
password: "NUser2", password: "NUser2",
email: "nuser2@users.com", email: "nuser2@users.com",
} as IUserSignupBody) } as TUserSignupBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(400); .expect(400);
const body2 = response2.body as IUserSignupRespBody; const body2 = response2.body as TUserSignupRespBody;
expect(body2.error).to.be.equal("Signups not allowed"); expect(body2.error).to.be.equal("Signups not allowed");
expect(body2.data).to.be.false; expect(body2.data).to.be.false;
@@ -184,17 +184,17 @@ describe("users", function () {
username: "NUser1", username: "NUser1",
password: "NUser1", password: "NUser1",
email: "nuser1@users.com", email: "nuser1@users.com",
} as IUserSignupBody) } as TUserSignupBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200); .expect(200);
const body = response.body as IUserSignupRespBody; const body = response.body as TUserSignupRespBody;
if (body.error !== false) { if (body.error !== false) {
assert(false); assert(false);
return;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { jwt: jwt1, ...user } = body.data; const { jwt: jwt1, ...user } = body.data;
const newUser = await User.findOneOrFail({ username: "NUser1" }); const newUser = await User.findOneOrFail({ username: "NUser1" });
expect(user).to.deep.equal(newUser.toJSON()); expect(user).to.deep.equal(newUser.toJSON());
@@ -207,17 +207,17 @@ describe("users", function () {
username: "NUser2", username: "NUser2",
password: "NUser2", password: "NUser2",
email: "nuser2@users.com", email: "nuser2@users.com",
} as IUserSignupBody) } as TUserSignupBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200); .expect(200);
const body2 = response2.body as IUserSignupRespBody; const body2 = response2.body as TUserSignupRespBody;
if (body2.error !== false) { if (body2.error !== false) {
assert(false); assert(false);
return;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { jwt: jwt2, ...user2 } = body2.data; const { jwt: jwt2, ...user2 } = body2.data;
const newUser2 = await User.findOneOrFail({ username: "NUser2" }); const newUser2 = await User.findOneOrFail({ username: "NUser2" });
expect(user2).to.deep.equal(newUser2.toJSON()); expect(user2).to.deep.equal(newUser2.toJSON());
@@ -234,10 +234,10 @@ describe("users", function () {
username: "User1", username: "User1",
password: "NUser1", password: "NUser1",
email: "user1@users.com", email: "user1@users.com",
} as IUserSignupBody) } as TUserSignupBody)
.expect(400); .expect(400);
const body = response.body as IUserSignupRespBody; const body = response.body as TUserSignupRespBody;
expect(body.error).to.be.equal("User already exists"); expect(body.error).to.be.equal("User already exists");
expect(body.data).to.be.false; expect(body.data).to.be.false;
@@ -252,15 +252,14 @@ describe("users", function () {
}) })
.send({ .send({
password: "User1NewPass", password: "User1NewPass",
} as IUserEditBody) } as TUserEditBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200); .expect(200);
const body = response.body as IUserEditRespBody; const body = response.body as TUserEditRespBody;
if (body.error !== false) { if (body.error !== false) {
assert(false); assert(false);
return;
} }
const loginResponse = await request(callback) const loginResponse = await request(callback)
@@ -269,27 +268,27 @@ describe("users", function () {
.send({ .send({
username: "User1", username: "User1",
password: "User1NewPass", password: "User1NewPass",
} as IUserLoginBody) } as TUserLoginBody)
.expect("Content-Type", /json/) .expect("Content-Type", /json/)
.expect(200); .expect(200);
const loginBody = loginResponse.body as IUserLoginRespBody; const loginBody = loginResponse.body as TUserLoginRespBody;
if (loginBody.error !== false) { if (loginBody.error !== false) {
assert(false); assert(false);
return;
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { jwt: _, ...user } = loginBody.data; const { jwt: _, ...user } = loginBody.data;
expect(user).to.deep.equal(seed.user1.toJSON()); expect(user).to.deep.equal(seed.user1.toJSON());
const badLoginResponse = await request(callback) const badLoginResponse = await request(callback)
.post("/users/login") .post("/users/login")
.set({ "Content-Type": "application/json" }) .set({ "Content-Type": "application/json" })
.send({ username: "User1", password: "User1" } as IUserLoginBody) .send({ username: "User1", password: "User1" } as TUserLoginBody)
.expect(404); .expect(404);
const badLoginBody = badLoginResponse.body as IUserLoginRespBody; const badLoginBody = badLoginResponse.body as TUserLoginRespBody;
expect(badLoginBody.error).to.be.equal("User not found"); expect(badLoginBody.error).to.be.equal("User not found");
expect(badLoginBody.data).to.be.false; expect(badLoginBody.data).to.be.false;

View File

@@ -24,8 +24,5 @@
"include": [ "include": [
"./src/**/*.ts", "./src/**/*.ts",
"./tests/**/*.ts", "./tests/**/*.ts",
],
"exclude": [
"frontend"
] ]
} }

View File

@@ -26,7 +26,6 @@
"eslint-plugin-react": "^7.33.0", "eslint-plugin-react": "^7.33.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"flush-promises": "^1.0.2", "flush-promises": "^1.0.2",
"io-ts": "^2.2.20",
"jest": "^29.6.2", "jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2", "jest-environment-jsdom": "^29.6.2",
"jest-junit": "^14.0.1", "jest-junit": "^14.0.1",
@@ -140,9 +139,9 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
}, },
"node_modules/@babel/core/node_modules/semver": { "node_modules/@babel/core/node_modules/semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
@@ -180,9 +179,9 @@
} }
}, },
"node_modules/@babel/helper-compilation-targets/node_modules/semver": { "node_modules/@babel/helper-compilation-targets/node_modules/semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
@@ -5963,9 +5962,9 @@
} }
}, },
"node_modules/eslint-plugin-jsx-a11y/node_modules/semver": { "node_modules/eslint-plugin-jsx-a11y/node_modules/semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
@@ -6372,12 +6371,6 @@
"node": ">= 6" "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": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -6962,14 +6955,6 @@
"node": ">= 0.4" "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": { "node_modules/is-arguments": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -7387,9 +7372,9 @@
} }
}, },
"node_modules/istanbul-lib-instrument/node_modules/semver": { "node_modules/istanbul-lib-instrument/node_modules/semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": { "bin": {
"semver": "bin/semver.js" "semver": "bin/semver.js"
} }
@@ -10820,9 +10805,9 @@
} }
}, },
"node_modules/tough-cookie": { "node_modules/tough-cookie": {
"version": "4.1.2", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"dependencies": { "dependencies": {
"psl": "^1.1.33", "psl": "^1.1.33",
"punycode": "^2.1.1", "punycode": "^2.1.1",
@@ -11351,9 +11336,9 @@
} }
}, },
"node_modules/word-wrap": { "node_modules/word-wrap": {
"version": "1.2.3", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -11540,9 +11525,9 @@
"integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
}, },
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
} }
} }
}, },
@@ -11570,9 +11555,9 @@
}, },
"dependencies": { "dependencies": {
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
} }
} }
}, },
@@ -15526,9 +15511,9 @@
}, },
"dependencies": { "dependencies": {
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
} }
} }
}, },
@@ -15810,12 +15795,6 @@
"mime-types": "^2.1.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": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -16204,12 +16183,6 @@
"side-channel": "^1.0.4" "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": { "is-arguments": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz",
@@ -16479,9 +16452,9 @@
}, },
"dependencies": { "dependencies": {
"semver": { "semver": {
"version": "6.3.0", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
} }
} }
}, },
@@ -18916,9 +18889,9 @@
} }
}, },
"tough-cookie": { "tough-cookie": {
"version": "4.1.2", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", "integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
"requires": { "requires": {
"psl": "^1.1.33", "psl": "^1.1.33",
"punycode": "^2.1.1", "punycode": "^2.1.1",
@@ -19292,9 +19265,9 @@
} }
}, },
"word-wrap": { "word-wrap": {
"version": "1.2.3", "version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "7.0.0", "version": "7.0.0",

View File

@@ -30,7 +30,6 @@
"eslint-plugin-react": "^7.33.0", "eslint-plugin-react": "^7.33.0",
"eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-hooks": "^4.6.0",
"flush-promises": "^1.0.2", "flush-promises": "^1.0.2",
"io-ts": "^2.2.20",
"jest": "^29.6.2", "jest": "^29.6.2",
"jest-environment-jsdom": "^29.6.2", "jest-environment-jsdom": "^29.6.2",
"jest-junit": "^14.0.1", "jest-junit": "^14.0.1",

View File

@@ -16,7 +16,7 @@ export function AccountComponent(props: IAccountComponentProps) {
return ( return (
<Card className="AuthForm" elevation={2}> <Card className="AuthForm" elevation={2}>
<form <form
onSubmit={(e: React.FormEvent<any>) => { onSubmit={(e: React.FormEvent<never>) => {
e.preventDefault(); e.preventDefault();
if (pass.trim()) { if (pass.trim()) {
props.changePass(pass); props.changePass(pass);

View File

@@ -1,5 +1,5 @@
import { Position, Toaster } from "@blueprintjs/core"; import { Position, Toaster } from "@blueprintjs/core";
import { IPhotoReqJSON } from "./shared/types"; import { TPhotoReqJSON } from "./shared/types";
export const AppToaster = Toaster.create({ export const AppToaster = Toaster.create({
className: "recipe-toaster", className: "recipe-toaster",
@@ -43,7 +43,7 @@ export function showPhotoCreateFailToast(f: File, e: string): void {
} }
export function showPhotoUploadJSONFailToast( export function showPhotoUploadJSONFailToast(
p: IPhotoReqJSON | number, p: TPhotoReqJSON | number,
e: string, e: string,
): void { ): void {
const photoMsg = typeof p === "number" ? p : p.hash; const photoMsg = typeof p === "number" ? p : p.hash;

View File

@@ -23,7 +23,9 @@ export class AuthScreenComponent extends React.PureComponent<IAuthScreenProps> {
} }
public render() { public render() {
const { location } = this.props.history; const { location } = this.props.history;
const { from } = (this.props.location.state as any) || { from: "/" }; const { from } = (this.props.location.state as { from: string }) || {
from: "/",
};
const { loggedIn } = this.props; const { loggedIn } = this.props;
return loggedIn ? ( return loggedIn ? (
<Redirect to={from} /> <Redirect to={from} />
@@ -46,7 +48,7 @@ export class AuthScreenComponent extends React.PureComponent<IAuthScreenProps> {
transform: "translate3d(400px,0,0)", transform: "translate3d(400px,0,0)",
}} }}
> >
{(_location: any) => (style: any) => ( {(_location) => (style) => (
<animated.div style={style}> <animated.div style={style}>
<Switch location={_location}> <Switch location={_location}>
<Route path="/login" component={Login} /> <Route path="/login" component={Login} />

View File

@@ -36,7 +36,7 @@ export class LoginComponent extends React.PureComponent<
this.props.history.push("/signup"); this.props.history.push("/signup");
} }
public submit(e: React.FormEvent<any>) { public submit<T extends React.FormEvent>(e: T) {
e.preventDefault(); e.preventDefault();
const { username, password } = this.state; const { username, password } = this.state;
if (!this.props.inProgress) { if (!this.props.inProgress) {

View File

@@ -37,7 +37,7 @@ export class SignupComponent extends React.PureComponent<
this.props.history.push("/login"); this.props.history.push("/login");
} }
public submit(e: React.FormEvent<any>) { public submit<T extends React.FormEvent>(e: T) {
e.preventDefault(); e.preventDefault();
const { username, password, email } = this.state; const { username, password, email } = this.state;
if (!this.props.inProgress) { if (!this.props.inProgress) {

View File

@@ -14,7 +14,7 @@ import { connect } from "react-redux";
import { Route, RouteComponentProps, Switch, withRouter } from "react-router"; import { Route, RouteComponentProps, Switch, withRouter } from "react-router";
import { animated, config, Transition } from "react-spring/renderprops"; import { animated, config, Transition } from "react-spring/renderprops";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import { IUserJSON } from "../shared/types"; import { TUserJSON } from "../shared/types";
import { Account } from "../Account/Account"; import { Account } from "../Account/Account";
import { Overview } from "../Photos/Overview"; import { Overview } from "../Photos/Overview";
import { toggleDarkMode } from "../redux/localSettings/actions"; import { toggleDarkMode } from "../redux/localSettings/actions";
@@ -24,7 +24,7 @@ import { PhotoRoute } from "../Photos/PhotoRoute";
import { UploadStatus } from "./UploadStatus"; import { UploadStatus } from "./UploadStatus";
export interface IHomeProps extends RouteComponentProps { export interface IHomeProps extends RouteComponentProps {
user: IUserJSON | null; user: TUserJSON | null;
darkMode: boolean; darkMode: boolean;
@@ -90,7 +90,7 @@ export class HomeComponent extends React.PureComponent<IHomeProps> {
transform: "translate3d(400px,0,0)", transform: "translate3d(400px,0,0)",
}} }}
> >
{(_location: any) => (style: any) => ( {(_location) => (style) => (
<animated.div <animated.div
style={style} style={style}
className="viewComponent" className="viewComponent"

View File

@@ -9,7 +9,7 @@ import {
photosDeleteStart, photosDeleteStart,
photosLoadStart, photosLoadStart,
} from "../redux/photos/actions"; } from "../redux/photos/actions";
import { IPhotoReqJSON } from "~/src/shared/types"; import { TPhotoReqJSON } from "~/src/shared/types";
import { PhotoCard } from "./PhotoCard"; import { PhotoCard } from "./PhotoCard";
import { import {
Alignment, Alignment,
@@ -26,7 +26,7 @@ import { Photo } from "./Photo";
import { showDeletionToast } from "~/src/AppToaster"; import { showDeletionToast } from "~/src/AppToaster";
export interface IOverviewComponentProps { export interface IOverviewComponentProps {
photos: IPhotoReqJSON[]; photos: TPhotoReqJSON[];
triedLoading: boolean; triedLoading: boolean;
allPhotosLoaded: boolean; allPhotosLoaded: boolean;
overviewFetching: boolean; overviewFetching: boolean;
@@ -35,8 +35,8 @@ export interface IOverviewComponentProps {
darkMode: boolean; darkMode: boolean;
fetchPhotos: () => void; fetchPhotos: () => void;
startDeletePhotos: (photos: IPhotoReqJSON[]) => void; startDeletePhotos: (photos: TPhotoReqJSON[]) => void;
cancelDelete: (photos: IPhotoReqJSON[]) => void; cancelDelete: (photos: TPhotoReqJSON[]) => void;
} }
const PhotoCardM = React.memo(PhotoCard); const PhotoCardM = React.memo(PhotoCard);
@@ -80,7 +80,7 @@ export const OverviewComponent: React.FunctionComponent<
( (
acc: Record< acc: Record<
string, string,
Record<string, Record<string, IPhotoReqJSON[]>> Record<string, Record<string, TPhotoReqJSON[]>>
>, >,
photo, photo,
) => { ) => {
@@ -108,7 +108,7 @@ export const OverviewComponent: React.FunctionComponent<
const els = Object.keys(dates[year]).reduce( const els = Object.keys(dates[year]).reduce(
(accMonths: JSX.Element[], month): JSX.Element[] => { (accMonths: JSX.Element[], month): JSX.Element[] => {
const photos = Object.values(dates[year][month]).reduce( const photos = Object.values(dates[year][month]).reduce(
(accDays: IPhotoReqJSON[], day) => { (accDays: TPhotoReqJSON[], day) => {
return [...day, ...accDays]; return [...day, ...accDays];
}, },
[], [],
@@ -264,9 +264,9 @@ function mapStateToProps(state: IAppState) {
function mapDispatchToProps(dispatch: Dispatch) { function mapDispatchToProps(dispatch: Dispatch) {
return { return {
fetchPhotos: () => dispatch(photosLoadStart()), fetchPhotos: () => dispatch(photosLoadStart()),
startDeletePhotos: (photos: IPhotoReqJSON[]) => startDeletePhotos: (photos: TPhotoReqJSON[]) =>
dispatch(photosDeleteStart(photos)), dispatch(photosDeleteStart(photos)),
cancelDelete: (photos: IPhotoReqJSON[]) => cancelDelete: (photos: TPhotoReqJSON[]) =>
dispatch(photosDeleteCancel(photos)), dispatch(photosDeleteCancel(photos)),
}; };
} }

View File

@@ -2,24 +2,31 @@ import "./Photo.scss";
import * as React from "react"; import * as React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
import { IPhotoReqJSON } from "~/src/shared/types"; import { TPhotoReqJSON } from "~/src/shared/types";
import { LoadingStub } from "../LoadingStub"; import { LoadingStub } from "../LoadingStub";
import { getPhotoImgPath, getPhotoThumbPath } from "../redux/api/photos"; import { getPhotoImgPath, getPhotoThumbPath } from "../redux/api/photos";
import { photoLoadStart } from "../redux/photos/actions"; import { photoLoadStart } from "../redux/photos/actions";
import { IPhotoState } from "../redux/photos/reducer"; import { TPhotoState } from "../redux/photos/reducer";
import { IAppState } from "../redux/reducers"; import { IAppState } from "../redux/reducers";
import { LargeSize, PreviewSize } from "./helper"; import { LargeSize, PreviewSize } from "./helper";
export interface IPhotoComponentProps { type StateProps = {
id: number; photo: TPhotoReqJSON | undefined;
photo: IPhotoReqJSON | undefined; photoState: TPhotoState | undefined;
photoState: IPhotoState | undefined; };
type DispatchProps = {
fetchPhoto: (id: number) => void; fetchPhoto: (id: number) => void;
close: () => void; };
}
export const PhotoComponent: React.FunctionComponent<IPhotoComponentProps> = ( type OwnProps = {
id: number;
close: () => void;
};
export type TPhotoComponentProps = StateProps & DispatchProps & OwnProps;
export const PhotoComponent: React.FunctionComponent<TPhotoComponentProps> = (
props, props,
) => { ) => {
const [smallPreviewFetching, setSmallPreviewFetching] = const [smallPreviewFetching, setSmallPreviewFetching] =
@@ -114,7 +121,7 @@ export const PhotoComponent: React.FunctionComponent<IPhotoComponentProps> = (
); );
}; };
function mapStateToProps(state: IAppState, props: IPhotoComponentProps) { function mapStateToProps(state: IAppState, props: OwnProps) {
const { id } = props; const { id } = props;
return { return {
photo: state.photos?.photos?.find((p) => p.id === id), photo: state.photos?.photos?.find((p) => p.id === id),
@@ -126,8 +133,7 @@ function mapDispatchToProps(dispatch: Dispatch) {
return { fetchPhoto: (id: number) => dispatch(photoLoadStart(id)) }; return { fetchPhoto: (id: number) => dispatch(photoLoadStart(id)) };
} }
// Because https://github.com/DefinitelyTyped/DefinitelyTyped/issues/16990 export const Photo = connect<StateProps, DispatchProps, OwnProps, IAppState>(
export const Photo = connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps, mapDispatchToProps,
)(PhotoComponent) as any; )(PhotoComponent);

View File

@@ -8,7 +8,7 @@ import {
Spinner, Spinner,
} from "@blueprintjs/core"; } from "@blueprintjs/core";
import * as React from "react"; import * as React from "react";
import { IPhotoReqJSON } from "~/src/shared/types"; import { TPhotoReqJSON } from "~/src/shared/types";
import { getPhotoThumbPath } from "../redux/api/photos"; import { getPhotoThumbPath } from "../redux/api/photos";
import { showDeletionToast } from "../AppToaster"; import { showDeletionToast } from "../AppToaster";
import { Dispatch } from "redux"; import { Dispatch } from "redux";
@@ -17,30 +17,30 @@ import { connect } from "react-redux";
import { RouteComponentProps, withRouter } from "react-router"; import { RouteComponentProps, withRouter } from "react-router";
import { PreviewSize } from "./helper"; import { PreviewSize } from "./helper";
export interface IPhotoCardComponentProps extends RouteComponentProps { export interface TPhotoCardComponentProps extends RouteComponentProps {
photo: IPhotoReqJSON; photo: TPhotoReqJSON;
selected: boolean; selected: boolean;
id: string; id: string;
deletePhoto: (photos: IPhotoReqJSON[]) => void; deletePhoto: (photos: TPhotoReqJSON[]) => void;
cancelDelete: (photos: IPhotoReqJSON[]) => void; cancelDelete: (photos: TPhotoReqJSON[]) => void;
onClick: (e: React.MouseEvent<HTMLElement>) => void; onClick: (e: React.MouseEvent<HTMLElement>) => void;
} }
export interface IPhotoCardComponentState { export interface TPhotoCardComponentState {
loaded: boolean; loaded: boolean;
} }
const defaultPhotoCardState: IPhotoCardComponentState = { const defaultPhotoCardState: TPhotoCardComponentState = {
loaded: false, loaded: false,
}; };
@ContextMenuTarget @ContextMenuTarget
export class PhotoCardComponent extends React.PureComponent< export class PhotoCardComponent extends React.PureComponent<
IPhotoCardComponentProps, TPhotoCardComponentProps,
IPhotoCardComponentState TPhotoCardComponentState
> { > {
constructor(props: IPhotoCardComponentProps) { constructor(props: TPhotoCardComponentProps) {
super(props); super(props);
this.handleDelete = this.handleDelete.bind(this); this.handleDelete = this.handleDelete.bind(this);
@@ -121,9 +121,9 @@ export class PhotoCardComponent extends React.PureComponent<
function mapDispatchToProps(dispatch: Dispatch) { function mapDispatchToProps(dispatch: Dispatch) {
return { return {
deletePhoto: (photos: IPhotoReqJSON[]) => deletePhoto: (photos: TPhotoReqJSON[]) =>
dispatch(photosDeleteStart(photos)), dispatch(photosDeleteStart(photos)),
cancelDelete: (photos: IPhotoReqJSON[]) => cancelDelete: (photos: TPhotoReqJSON[]) =>
dispatch(photosDeleteCancel(photos)), dispatch(photosDeleteCancel(photos)),
}; };
} }

View File

@@ -6,12 +6,12 @@ function getId(props: RouteComponentProps) {
return parseInt((props.match?.params as { id: string }).id); return parseInt((props.match?.params as { id: string }).id);
} }
export const PhotoRouteComponent: React.FunctionComponent<RouteComponentProps> = ( export const PhotoRouteComponent: React.FunctionComponent<
props: RouteComponentProps, RouteComponentProps
) => { > = (props: RouteComponentProps) => {
const id = getId(props); const id = getId(props);
return <Photo id={id} />; return <Photo id={id} close={() => {}} />;
}; };
export const PhotoRoute = withRouter(PhotoRouteComponent); export const PhotoRoute = withRouter(PhotoRouteComponent);

View File

@@ -1,11 +1,16 @@
import { IUserLoginRespBody, IUserSignupRespBody } from "~/src/shared/types"; import {
TUserLoginRespBody,
TUserSignupRespBody,
UserLoginRespBody,
UserSignupRespBody,
} from "~/src/shared/types";
import { fetchJSON } from "../utils"; import { fetchJSON } from "../utils";
export async function login( export async function login(
username: string, username: string,
password: string, password: string,
): Promise<IUserLoginRespBody> { ): Promise<TUserLoginRespBody> {
return fetchJSON("/users/login", "POST", { return fetchJSON("/users/login", "POST", UserLoginRespBody, {
username, username,
password, password,
}); });
@@ -15,8 +20,8 @@ export async function signup(
username: string, username: string,
password: string, password: string,
email: string, email: string,
): Promise<IUserSignupRespBody> { ): Promise<TUserSignupRespBody> {
return fetchJSON("/users/signup", "POST", { return fetchJSON("/users/signup", "POST", UserSignupRespBody, {
username, username,
password, password,
email, email,

View File

@@ -1,19 +1,26 @@
import { IPhotoReqJSON } from "~/src/shared/types";
import { import {
IPhotosByIDGetRespBody, PhotosByIDGetRespBody,
IPhotosDeleteRespBody, PhotosDeleteRespBody,
IPhotosListRespBody, PhotosListRespBody,
IPhotosNewRespBody, PhotosNewRespBody,
IPhotosUploadRespBody, PhotosUploadRespBody,
TPhotoReqJSON,
} from "~/src/shared/types";
import {
TPhotosByIDGetRespBody,
TPhotosDeleteRespBody,
TPhotosListRespBody,
TPhotosNewRespBody,
TPhotosUploadRespBody,
} from "~/src/shared/types"; } from "~/src/shared/types";
import { apiRoot } from "~src/env"; import { apiRoot } from "~src/env";
import { fetchJSONAuth } from "./utils"; import { fetchJSONAuth } from "./utils";
export function getPhotoImgPath(photo: IPhotoReqJSON): string { export function getPhotoImgPath(photo: TPhotoReqJSON): string {
return `${apiRoot}/photos/showByID/${photo.id}/${photo.accessToken}`; return `${apiRoot}/photos/showByID/${photo.id}/${photo.accessToken}`;
} }
export function getPhotoThumbPath(photo: IPhotoReqJSON, size: number): string { export function getPhotoThumbPath(photo: TPhotoReqJSON, size: number): string {
return `${apiRoot}/photos/showByID/${photo.id}/${ return `${apiRoot}/photos/showByID/${photo.id}/${
photo.accessToken photo.accessToken
}?size=${size.toString()}`; }?size=${size.toString()}`;
@@ -22,35 +29,50 @@ export function getPhotoThumbPath(photo: IPhotoReqJSON, size: number): string {
export async function fetchPhotosList( export async function fetchPhotosList(
skip: number, skip: number,
num: number, num: number,
): Promise<IPhotosListRespBody> { ): Promise<TPhotosListRespBody> {
const params = new URLSearchParams({ const params = new URLSearchParams({
skip: skip.toString(), skip: skip.toString(),
num: num.toString(), num: num.toString(),
}); });
return fetchJSONAuth(`/photos/list?${params.toString()}`, "GET"); return fetchJSONAuth(
`/photos/list?${params.toString()}`,
"GET",
PhotosListRespBody,
);
} }
export async function fetchPhoto(id: number): Promise<IPhotosByIDGetRespBody> { export async function fetchPhoto(id: number): Promise<TPhotosByIDGetRespBody> {
return fetchJSONAuth(`/photos/byID/${id}`, "GET"); return fetchJSONAuth(`/photos/byID/${id}`, "GET", PhotosByIDGetRespBody);
} }
export async function createPhoto( export async function createPhoto(
hash: string, hash: string,
size: string, size: string,
format: string, format: string,
): Promise<IPhotosNewRespBody> { ): Promise<TPhotosNewRespBody> {
return fetchJSONAuth("/photos/new", "POST", { hash, size, format }); return fetchJSONAuth("/photos/new", "POST", PhotosNewRespBody, {
hash,
size,
format,
});
} }
export async function uploadPhoto( export async function uploadPhoto(
file: File, file: File,
id: number, id: number,
): Promise<IPhotosUploadRespBody> { ): Promise<TPhotosUploadRespBody> {
return fetchJSONAuth(`/photos/upload/${id}`, "POST", file); return fetchJSONAuth(
`/photos/upload/${id}`,
"POST",
PhotosUploadRespBody,
file,
);
} }
export async function deletePhotos( export async function deletePhotos(
photos: IPhotoReqJSON[], photos: TPhotoReqJSON[],
): Promise<IPhotosDeleteRespBody> { ): Promise<TPhotosDeleteRespBody> {
return fetchJSONAuth(`/photos/delete`, "POST", { photos }); return fetchJSONAuth(`/photos/delete`, "POST", PhotosDeleteRespBody, {
photos,
});
} }

View File

@@ -1,17 +1,19 @@
import { fetchJSONAuth } from "../utils"; import { fetchJSONAuth } from "../utils";
import { IUserEditRespBody, IUserGetRespBody } from "~/src/shared/types"; import {
TUserEditRespBody,
TUserGetRespBody,
UserEditRespBody,
UserGetRespBody,
} from "~/src/shared/types";
export async function fetchUser(): Promise<IUserGetRespBody> { export async function fetchUser(): Promise<TUserGetRespBody> {
return fetchJSONAuth( return fetchJSONAuth("/users/user", "GET", UserGetRespBody);
"/users/user",
"GET",
) as unknown as Promise<IUserGetRespBody>;
} }
export async function changeUserPassword( export async function changeUserPassword(
newPassword: string, newPassword: string,
): Promise<IUserEditRespBody> { ): Promise<TUserEditRespBody> {
return fetchJSONAuth("/users/edit", "POST", { return fetchJSONAuth("/users/edit", "POST", UserEditRespBody, {
password: newPassword, password: newPassword,
}); });
} }

View File

@@ -1,5 +1,4 @@
import { apiRoot } from "~src/env"; import { apiRoot } from "~src/env";
import { IAPIResponse } from "~/src/shared/types";
let token: string | null; let token: string | null;
@@ -15,49 +14,44 @@ export function deleteToken(): void {
token = null; token = null;
} }
export async function fetchJSON<T>( export async function fetchJSON<T, P extends { parse: (string) => T }>(
path: string, path: string,
method: string, method: string,
parser: P,
body?: string | Record<string, unknown> | File, body?: string | Record<string, unknown> | File,
headers?: Record<string, string>, headers?: Record<string, string>,
): Promise<IAPIResponse<T>> { ): Promise<T> {
if (typeof body === "object" && !(body instanceof File)) { const reqBody = () =>
body = JSON.stringify(body); body instanceof File
headers = { ? (() => {
...headers, const fd = new FormData();
"Content-Type": "application/json", fd.append("photo", body);
}; return fd;
} })()
// TODO: io-ts or something like that : JSON.stringify(body);
if (body instanceof File) {
const formData = new FormData(); const reqHeaders = () =>
formData.append("photo", body); body instanceof File
const response = await fetch(apiRoot + path, { ? headers
method, : { ...headers, "Content-Type": "application/json" };
headers,
body: formData,
});
const json = (await response.json()) as Record<string, unknown>;
return json as unknown as IAPIResponse<T>;
}
const response = await fetch(apiRoot + path, { const response = await fetch(apiRoot + path, {
method, method,
body, headers: reqHeaders(),
headers, body: reqBody(),
}); });
const json = (await response.json()) as Record<string, unknown>; return parser.parse(await response.json());
return json as unknown as IAPIResponse<T>;
} }
export async function fetchJSONAuth<T>( export async function fetchJSONAuth<T, P extends { parse: (string) => T }>(
path: string, path: string,
method: string, method: string,
parser: P,
body?: string | Record<string, unknown> | File, body?: string | Record<string, unknown> | File,
headers?: Record<string, unknown>, headers?: Record<string, unknown>,
): Promise<IAPIResponse<T>> { ): Promise<T> {
if (token) { if (token) {
return fetchJSON(path, method, body, { return fetchJSON(path, method, parser, body, {
...headers, ...headers,
Authorization: `Bearer ${token}`, Authorization: `Bearer ${token}`,
}); });

View File

@@ -1,5 +1,5 @@
import { Action } from "redux"; import { Action } from "redux";
import { IUserAuthJSON } from "~/src/shared/types"; import { TUserAuthJSON } from "~/src/shared/types";
export enum AuthTypes { export enum AuthTypes {
AUTH_START = "AUTH_START", AUTH_START = "AUTH_START",
@@ -28,7 +28,7 @@ export interface ISignupStartAction extends Action {
export interface IAuthSuccessAction extends Action { export interface IAuthSuccessAction extends Action {
type: AuthTypes.AUTH_SUCCESS; type: AuthTypes.AUTH_SUCCESS;
payload: IUserAuthJSON; payload: TUserAuthJSON;
} }
export interface IAuthFailureAction extends Action { export interface IAuthFailureAction extends Action {
@@ -64,7 +64,7 @@ export function signupStart(
}; };
} }
export function authSuccess(user: IUserAuthJSON): IAuthSuccessAction { export function authSuccess(user: TUserAuthJSON): IAuthSuccessAction {
return { type: AuthTypes.AUTH_SUCCESS, payload: user }; return { type: AuthTypes.AUTH_SUCCESS, payload: user };
} }

View File

@@ -1,5 +1,5 @@
import { Action } from "redux"; import { Action } from "redux";
import { IPhotoReqJSON } from "~src/shared/types"; import { TPhotoReqJSON } from "~src/shared/types";
import { import {
showPhotoCreateFailToast, showPhotoCreateFailToast,
showPhotoUploadFileFailToast, showPhotoUploadFileFailToast,
@@ -29,242 +29,242 @@ export enum PhotoTypes {
PHOTOS_DELETE_CANCEL = "PHOTOS_DELETE_CANCEL", PHOTOS_DELETE_CANCEL = "PHOTOS_DELETE_CANCEL",
} }
export interface IPhotosLoadStartAction extends Action { export interface TPhotosLoadStartAction extends Action {
type: PhotoTypes.PHOTOS_LOAD_START; type: PhotoTypes.PHOTOS_LOAD_START;
} }
export interface IPhotosLoadSuccessAction extends Action { export interface TPhotosLoadSuccessAction extends Action {
type: PhotoTypes.PHOTOS_LOAD_SUCCESS; type: PhotoTypes.PHOTOS_LOAD_SUCCESS;
photos: IPhotoReqJSON[]; photos: TPhotoReqJSON[];
} }
export interface IPhotosLoadFailAction extends Action { export interface TPhotosLoadFailAction extends Action {
type: PhotoTypes.PHOTOS_LOAD_FAIL; type: PhotoTypes.PHOTOS_LOAD_FAIL;
error: string; error: string;
} }
export interface IPhotoLoadStartAction extends Action { export interface TPhotoLoadStartAction extends Action {
type: PhotoTypes.PHOTO_LOAD_START; type: PhotoTypes.PHOTO_LOAD_START;
id: number; id: number;
} }
export interface IPhotoLoadSuccessAction extends Action { export interface TPhotoLoadSuccessAction extends Action {
type: PhotoTypes.PHOTO_LOAD_SUCCESS; type: PhotoTypes.PHOTO_LOAD_SUCCESS;
photo: IPhotoReqJSON; photo: TPhotoReqJSON;
} }
export interface IPhotoLoadFailAction extends Action { export interface TPhotoLoadFailAction extends Action {
type: PhotoTypes.PHOTO_LOAD_FAIL; type: PhotoTypes.PHOTO_LOAD_FAIL;
id: number; id: number;
error: string; error: string;
} }
export interface IPhotosUploadStartAction extends Action { export interface TPhotosUploadStartAction extends Action {
type: PhotoTypes.PHOTOS_UPLOAD_START; type: PhotoTypes.PHOTOS_UPLOAD_START;
files: FileList; files: FileList;
} }
export interface IPhotoCreateQueue extends Action { export interface TPhotoCreateQueue extends Action {
type: PhotoTypes.PHOTO_CREATE_QUEUE; type: PhotoTypes.PHOTO_CREATE_QUEUE;
file: File; file: File;
} }
export interface IPhotoUploadQueue extends Action { export interface TPhotoUploadQueue extends Action {
type: PhotoTypes.PHOTO_UPLOAD_QUEUE; type: PhotoTypes.PHOTO_UPLOAD_QUEUE;
file: File; file: File;
id: number; id: number;
} }
export interface IPhotoCreateStart extends Action { export interface TPhotoCreateStart extends Action {
type: PhotoTypes.PHOTO_CREATE_START; type: PhotoTypes.PHOTO_CREATE_START;
file: File; file: File;
} }
export interface IPhotoUploadStart extends Action { export interface TPhotoUploadStart extends Action {
type: PhotoTypes.PHOTO_UPLOAD_START; type: PhotoTypes.PHOTO_UPLOAD_START;
file: File; file: File;
id: number; id: number;
} }
export interface IPhotoUploadSuccessAction extends Action { export interface TPhotoUploadSuccessAction extends Action {
type: PhotoTypes.PHOTO_UPLOAD_SUCCESS; type: PhotoTypes.PHOTO_UPLOAD_SUCCESS;
photo: IPhotoReqJSON; photo: TPhotoReqJSON;
} }
export interface IPhotoUploadFailAction extends Action { export interface TPhotoUploadFailAction extends Action {
type: PhotoTypes.PHOTO_UPLOAD_FAIL; type: PhotoTypes.PHOTO_UPLOAD_FAIL;
photo: IPhotoReqJSON | number; photo: TPhotoReqJSON | number;
error: string; error: string;
} }
export interface IPhotoCreateSuccessAction extends Action { export interface TPhotoCreateSuccessAction extends Action {
type: PhotoTypes.PHOTO_CREATE_SUCCESS; type: PhotoTypes.PHOTO_CREATE_SUCCESS;
photo: IPhotoReqJSON; photo: TPhotoReqJSON;
file: File; file: File;
} }
export interface IPhotoCreateFailAction extends Action { export interface TPhotoCreateFailAction extends Action {
type: PhotoTypes.PHOTO_CREATE_FAIL; type: PhotoTypes.PHOTO_CREATE_FAIL;
file: File; file: File;
error: string; error: string;
} }
export interface IPhotosDeleteStartAction extends Action { export interface TPhotosDeleteStartAction extends Action {
type: PhotoTypes.PHOTOS_DELETE_START; type: PhotoTypes.PHOTOS_DELETE_START;
photos: IPhotoReqJSON[]; photos: TPhotoReqJSON[];
} }
export interface IPhotosDeleteSuccessAction extends Action { export interface TPhotosDeleteSuccessAction extends Action {
type: PhotoTypes.PHOTOS_DELETE_SUCCESS; type: PhotoTypes.PHOTOS_DELETE_SUCCESS;
photos: IPhotoReqJSON[]; photos: TPhotoReqJSON[];
} }
export interface IPhotosDeleteFailAction extends Action { export interface TPhotosDeleteFailAction extends Action {
type: PhotoTypes.PHOTOS_DELETE_FAIL; type: PhotoTypes.PHOTOS_DELETE_FAIL;
photos: IPhotoReqJSON[]; photos: TPhotoReqJSON[];
error?: string; error?: string;
} }
export interface IPhotosDeleteCancelAction extends Action { export interface TPhotosDeleteCancelAction extends Action {
type: PhotoTypes.PHOTOS_DELETE_CANCEL; type: PhotoTypes.PHOTOS_DELETE_CANCEL;
photos: IPhotoReqJSON[]; photos: TPhotoReqJSON[];
} }
export interface IPhotosStartFetchingSpinner extends Action { export interface TPhotosStartFetchingSpinner extends Action {
type: PhotoTypes.PHOTOS_START_FETCHING_SPINNER; type: PhotoTypes.PHOTOS_START_FETCHING_SPINNER;
} }
export function photoCreateQueue(file: File): IPhotoCreateQueue { export function photoCreateQueue(file: File): TPhotoCreateQueue {
return { type: PhotoTypes.PHOTO_CREATE_QUEUE, file }; return { type: PhotoTypes.PHOTO_CREATE_QUEUE, file };
} }
export function photoUploadQueue(file: File, id: number): IPhotoUploadQueue { export function photoUploadQueue(file: File, id: number): TPhotoUploadQueue {
return { type: PhotoTypes.PHOTO_UPLOAD_QUEUE, file, id }; return { type: PhotoTypes.PHOTO_UPLOAD_QUEUE, file, id };
} }
export function photoCreateStart(file: File): IPhotoCreateStart { export function photoCreateStart(file: File): TPhotoCreateStart {
return { type: PhotoTypes.PHOTO_CREATE_START, file }; return { type: PhotoTypes.PHOTO_CREATE_START, file };
} }
export function photoUploadStart(file: File, id: number): IPhotoUploadStart { export function photoUploadStart(file: File, id: number): TPhotoUploadStart {
return { type: PhotoTypes.PHOTO_UPLOAD_START, file, id }; return { type: PhotoTypes.PHOTO_UPLOAD_START, file, id };
} }
export function photosLoadStart(): IPhotosLoadStartAction { export function photosLoadStart(): TPhotosLoadStartAction {
return { type: PhotoTypes.PHOTOS_LOAD_START }; return { type: PhotoTypes.PHOTOS_LOAD_START };
} }
export function photoLoadStart(id: number): IPhotoLoadStartAction { export function photoLoadStart(id: number): TPhotoLoadStartAction {
return { type: PhotoTypes.PHOTO_LOAD_START, id }; return { type: PhotoTypes.PHOTO_LOAD_START, id };
} }
export function photosUploadStart(files: FileList): IPhotosUploadStartAction { export function photosUploadStart(files: FileList): TPhotosUploadStartAction {
return { type: PhotoTypes.PHOTOS_UPLOAD_START, files }; return { type: PhotoTypes.PHOTOS_UPLOAD_START, files };
} }
export function photoUploadSuccess( export function photoUploadSuccess(
photo: IPhotoReqJSON, photo: TPhotoReqJSON,
): IPhotoUploadSuccessAction { ): TPhotoUploadSuccessAction {
return { type: PhotoTypes.PHOTO_UPLOAD_SUCCESS, photo }; return { type: PhotoTypes.PHOTO_UPLOAD_SUCCESS, photo };
} }
export function photoUploadFail( export function photoUploadFail(
photo: IPhotoReqJSON | number, photo: TPhotoReqJSON | number,
error: string, error: string,
): IPhotoUploadFailAction { ): TPhotoUploadFailAction {
showPhotoUploadJSONFailToast(photo, error); showPhotoUploadJSONFailToast(photo, error);
return { type: PhotoTypes.PHOTO_UPLOAD_FAIL, photo, error }; return { type: PhotoTypes.PHOTO_UPLOAD_FAIL, photo, error };
} }
export function photoUploadFailWithFile( export function photoUploadFailWithFile(
photo: IPhotoReqJSON | number, photo: TPhotoReqJSON | number,
file: File, file: File,
error: string, error: string,
): IPhotoUploadFailAction { ): TPhotoUploadFailAction {
showPhotoUploadFileFailToast(file, error); showPhotoUploadFileFailToast(file, error);
return { type: PhotoTypes.PHOTO_UPLOAD_FAIL, photo, error }; return { type: PhotoTypes.PHOTO_UPLOAD_FAIL, photo, error };
} }
export function photoCreateSuccess( export function photoCreateSuccess(
photo: IPhotoReqJSON, photo: TPhotoReqJSON,
file: File, file: File,
): IPhotoCreateSuccessAction { ): TPhotoCreateSuccessAction {
return { type: PhotoTypes.PHOTO_CREATE_SUCCESS, photo, file }; return { type: PhotoTypes.PHOTO_CREATE_SUCCESS, photo, file };
} }
export function photoCreateFail( export function photoCreateFail(
file: File, file: File,
error: string, error: string,
): IPhotoCreateFailAction { ): TPhotoCreateFailAction {
showPhotoCreateFailToast(file, error); showPhotoCreateFailToast(file, error);
return { type: PhotoTypes.PHOTO_CREATE_FAIL, file, error }; return { type: PhotoTypes.PHOTO_CREATE_FAIL, file, error };
} }
export function photosLoadSuccess( export function photosLoadSuccess(
photos: IPhotoReqJSON[], photos: TPhotoReqJSON[],
): IPhotosLoadSuccessAction { ): TPhotosLoadSuccessAction {
return { type: PhotoTypes.PHOTOS_LOAD_SUCCESS, photos }; return { type: PhotoTypes.PHOTOS_LOAD_SUCCESS, photos };
} }
export function photosLoadFail(error: string): IPhotosLoadFailAction { export function photosLoadFail(error: string): TPhotosLoadFailAction {
return { type: PhotoTypes.PHOTOS_LOAD_FAIL, error }; return { type: PhotoTypes.PHOTOS_LOAD_FAIL, error };
} }
export function photoLoadSuccess( export function photoLoadSuccess(
photo: IPhotoReqJSON, photo: TPhotoReqJSON,
): IPhotoLoadSuccessAction { ): TPhotoLoadSuccessAction {
return { type: PhotoTypes.PHOTO_LOAD_SUCCESS, photo }; return { type: PhotoTypes.PHOTO_LOAD_SUCCESS, photo };
} }
export function photoLoadFail(id: number, error: string): IPhotoLoadFailAction { export function photoLoadFail(id: number, error: string): TPhotoLoadFailAction {
return { type: PhotoTypes.PHOTO_LOAD_FAIL, id, error }; return { type: PhotoTypes.PHOTO_LOAD_FAIL, id, error };
} }
export function photosDeleteStart( export function photosDeleteStart(
photos: IPhotoReqJSON[], photos: TPhotoReqJSON[],
): IPhotosDeleteStartAction { ): TPhotosDeleteStartAction {
return { type: PhotoTypes.PHOTOS_DELETE_START, photos }; return { type: PhotoTypes.PHOTOS_DELETE_START, photos };
} }
export function photosDeleteSuccess( export function photosDeleteSuccess(
photos: IPhotoReqJSON[], photos: TPhotoReqJSON[],
): IPhotosDeleteSuccessAction { ): TPhotosDeleteSuccessAction {
return { type: PhotoTypes.PHOTOS_DELETE_SUCCESS, photos }; return { type: PhotoTypes.PHOTOS_DELETE_SUCCESS, photos };
} }
export function photosDeleteFail( export function photosDeleteFail(
photos: IPhotoReqJSON[], photos: TPhotoReqJSON[],
error?: string, error?: string,
): IPhotosDeleteFailAction { ): TPhotosDeleteFailAction {
return { type: PhotoTypes.PHOTOS_DELETE_FAIL, photos, error }; return { type: PhotoTypes.PHOTOS_DELETE_FAIL, photos, error };
} }
export function photosDeleteCancel( export function photosDeleteCancel(
photos: IPhotoReqJSON[], photos: TPhotoReqJSON[],
): IPhotosDeleteCancelAction { ): TPhotosDeleteCancelAction {
return { type: PhotoTypes.PHOTOS_DELETE_CANCEL, photos }; return { type: PhotoTypes.PHOTOS_DELETE_CANCEL, photos };
} }
export function photosStartFetchingSpinner(): IPhotosStartFetchingSpinner { export function photosStartFetchingSpinner(): TPhotosStartFetchingSpinner {
return { type: PhotoTypes.PHOTOS_START_FETCHING_SPINNER }; return { type: PhotoTypes.PHOTOS_START_FETCHING_SPINNER };
} }
export type PhotoAction = export type PhotoAction =
| IPhotosLoadStartAction | TPhotosLoadStartAction
| IPhotosLoadFailAction | TPhotosLoadFailAction
| IPhotosLoadSuccessAction | TPhotosLoadSuccessAction
| IPhotosStartFetchingSpinner | TPhotosStartFetchingSpinner
| IPhotosUploadStartAction | TPhotosUploadStartAction
| IPhotoCreateFailAction | TPhotoCreateFailAction
| IPhotoCreateSuccessAction | TPhotoCreateSuccessAction
| IPhotoUploadFailAction | TPhotoUploadFailAction
| IPhotoUploadSuccessAction | TPhotoUploadSuccessAction
| IPhotosDeleteFailAction | TPhotosDeleteFailAction
| IPhotosDeleteStartAction | TPhotosDeleteStartAction
| IPhotosDeleteSuccessAction | TPhotosDeleteSuccessAction
| IPhotosDeleteCancelAction | TPhotosDeleteCancelAction
| IPhotoLoadFailAction | TPhotoLoadFailAction
| IPhotoLoadStartAction | TPhotoLoadStartAction
| IPhotoLoadSuccessAction | TPhotoLoadSuccessAction
| IPhotoUploadQueue | TPhotoUploadQueue
| IPhotoCreateQueue | TPhotoCreateQueue
| IPhotoCreateStart | TPhotoCreateStart
| IPhotoUploadStart; | TPhotoUploadStart;

View File

@@ -1,17 +1,17 @@
import { Reducer } from "redux"; import { Reducer } from "redux";
import { IPhotoReqJSON } from "~/src/shared/types"; import { TPhotoReqJSON } from "~/src/shared/types";
import { UserAction, UserTypes } from "~src/redux/user/actions"; import { UserAction, UserTypes } from "~src/redux/user/actions";
import { PhotoAction, PhotoTypes } from "./actions"; import { PhotoAction, PhotoTypes } from "./actions";
export interface IPhotoState { export interface TPhotoState {
fetching: boolean; fetching: boolean;
fetchingError: string | null; fetchingError: string | null;
} }
export interface IPhotosState { export interface TPhotosState {
photos: IPhotoReqJSON[]; photos: TPhotoReqJSON[];
photoStates: Record<number, IPhotoState>; photoStates: Record<number, TPhotoState>;
overviewFetching: boolean; overviewFetching: boolean;
allPhotosLoaded: boolean; allPhotosLoaded: boolean;
@@ -24,10 +24,10 @@ export interface IPhotosState {
photoUploadQueue: Record<number, File>; photoUploadQueue: Record<number, File>;
photosUploading: number; photosUploading: number;
deleteCache: Record<number, IPhotoReqJSON>; deleteCache: Record<number, TPhotoReqJSON>;
} }
const defaultPhotosState: IPhotosState = { const defaultPhotosState: TPhotosState = {
photos: [], photos: [],
allPhotosLoaded: false, allPhotosLoaded: false,
overviewFetching: false, overviewFetching: false,
@@ -45,12 +45,12 @@ const defaultPhotosState: IPhotosState = {
deleteCache: {}, deleteCache: {},
}; };
export function sortPhotos(photos: IPhotoReqJSON[]): IPhotoReqJSON[] { export function sortPhotos(photos: TPhotoReqJSON[]): TPhotoReqJSON[] {
return [...photos].sort((a, b) => b.shotAt - a.shotAt); return [...photos].sort((a, b) => b.shotAt - a.shotAt);
} }
export const photosReducer: Reducer<IPhotosState, PhotoAction> = ( export const photosReducer: Reducer<TPhotosState, PhotoAction> = (
state: IPhotosState = defaultPhotosState, state: TPhotosState = defaultPhotosState,
action: PhotoAction | UserAction, action: PhotoAction | UserAction,
) => { ) => {
switch (action.type) { switch (action.type) {
@@ -230,7 +230,7 @@ export const photosReducer: Reducer<IPhotosState, PhotoAction> = (
case PhotoTypes.PHOTOS_DELETE_FAIL: case PhotoTypes.PHOTOS_DELETE_FAIL:
case PhotoTypes.PHOTOS_DELETE_CANCEL: { case PhotoTypes.PHOTOS_DELETE_CANCEL: {
const delCache = { ...state.deleteCache }; const delCache = { ...state.deleteCache };
let photos: IPhotoReqJSON[] = [...state.photos]; let photos: TPhotoReqJSON[] = [...state.photos];
for (const photo of action.photos) { for (const photo of action.photos) {
if (delCache[photo.id]) { if (delCache[photo.id]) {
photos = sortPhotos([...photos, delCache[photo.id]]); photos = sortPhotos([...photos, delCache[photo.id]]);

View File

@@ -18,9 +18,9 @@ import {
uploadPhoto, uploadPhoto,
} from "~src/redux/api/photos"; } from "~src/redux/api/photos";
import { import {
IPhotosDeleteStartAction, TPhotosDeleteStartAction,
IPhotoLoadStartAction, TPhotoLoadStartAction,
IPhotosUploadStartAction, TPhotosUploadStartAction,
photoCreateFail, photoCreateFail,
photoCreateQueue, photoCreateQueue,
photoCreateStart, photoCreateStart,
@@ -38,7 +38,7 @@ import {
photoUploadStart, photoUploadStart,
photoUploadSuccess, photoUploadSuccess,
} from "./actions"; } from "./actions";
import { IPhotosNewRespBody, IPhotosListPagination } from "~src/shared/types"; import { TPhotosNewRespBody, PhotosListPagination } from "~src/shared/types";
// Thanks, https://dev.to/qortex/compute-md5-checksum-for-a-file-in-typescript-59a4 // Thanks, https://dev.to/qortex/compute-md5-checksum-for-a-file-in-typescript-59a4
function computeChecksumMd5(file: File): Promise<string> { function computeChecksumMd5(file: File): Promise<string> {
@@ -112,7 +112,7 @@ function* photosLoad() {
const skip = state.photos.photos ? state.photos.photos.length : 0; const skip = state.photos.photos ? state.photos.photos.length : 0;
const { response, timeout } = yield race({ const { response, timeout } = yield race({
response: call(fetchPhotosList, skip, IPhotosListPagination), response: call(fetchPhotosList, skip, PhotosListPagination),
timeout: delay(10000), timeout: delay(10000),
}); });
@@ -133,7 +133,7 @@ function* photosLoad() {
} }
} }
function* photoLoad(action: IPhotoLoadStartAction) { function* photoLoad(action: TPhotoLoadStartAction) {
try { try {
//const spinner = yield fork(startSpinner); //const spinner = yield fork(startSpinner);
@@ -185,7 +185,7 @@ function* photoCreate() {
return; return;
} }
if (response.data || response.error === "Photo already exists") { if (response.data || response.error === "Photo already exists") {
const photo = (response as IPhotosNewRespBody).data; const photo = (response as TPhotosNewRespBody).data;
yield put(photoCreateSuccess(photo, f)); yield put(photoCreateSuccess(photo, f));
yield put(photoUploadQueue(f, photo.id)); yield put(photoUploadQueue(f, photo.id));
} else { } else {
@@ -233,14 +233,14 @@ function* photoUpload() {
} }
} }
function* photosUpload(action: IPhotosUploadStartAction) { function* photosUpload(action: TPhotosUploadStartAction) {
const files = Array.from(action.files); const files = Array.from(action.files);
for (const file of files) { for (const file of files) {
yield put(photoCreateQueue(file)); yield put(photoCreateQueue(file));
} }
} }
function* photosDelete(action: IPhotosDeleteStartAction) { function* photosDelete(action: TPhotosDeleteStartAction) {
try { try {
const { cancelled } = yield race({ const { cancelled } = yield race({
timeout: delay(3000), timeout: delay(3000),

View File

@@ -7,14 +7,14 @@ import {
ILocalSettingsState, ILocalSettingsState,
localSettingsReducer, localSettingsReducer,
} from "./localSettings/reducer"; } from "./localSettings/reducer";
import { IPhotosState, photosReducer } from "./photos/reducer"; import { TPhotosState, photosReducer } from "./photos/reducer";
import { IUserState, userReducer } from "./user/reducer"; import { TUserState, userReducer } from "./user/reducer";
export interface IAppState { export interface IAppState {
auth: IAuthState & PersistPartial; auth: IAuthState & PersistPartial;
user: IUserState; user: TUserState;
localSettings: ILocalSettingsState & PersistPartial; localSettings: ILocalSettingsState & PersistPartial;
photos: IPhotosState; photos: TPhotosState;
} }
const authPersistConfig = { const authPersistConfig = {

View File

@@ -1,5 +1,5 @@
import { Action } from "redux"; import { Action } from "redux";
import { IUserAuthJSON } from "~src/shared/types"; import { TUserAuthJSON } from "~src/shared/types";
import { import {
showPasswordNotSavedToast, showPasswordNotSavedToast,
showPasswordSavedToast, showPasswordSavedToast,
@@ -15,20 +15,20 @@ export enum UserTypes {
USER_PASS_CHANGE_FAIL = "USER_PASS_CHANGE_FAIL", USER_PASS_CHANGE_FAIL = "USER_PASS_CHANGE_FAIL",
} }
export interface IUserGetAction extends Action { export interface TUserGetAction extends Action {
type: UserTypes.USER_GET; type: UserTypes.USER_GET;
} }
export interface IUserLogoutAction extends Action { export interface TUserLogoutAction extends Action {
type: UserTypes.USER_LOGOUT; type: UserTypes.USER_LOGOUT;
} }
export interface IUserGetSuccessAction extends Action { export interface TUserGetSuccessAction extends Action {
type: UserTypes.USER_GET_SUCCESS; type: UserTypes.USER_GET_SUCCESS;
payload: IUserAuthJSON; payload: TUserAuthJSON;
} }
export interface IUserGetFailAction extends Action { export interface TUserGetFailAction extends Action {
type: UserTypes.USER_GET_FAIL; type: UserTypes.USER_GET_FAIL;
payload: { payload: {
error: string; error: string;
@@ -36,17 +36,17 @@ export interface IUserGetFailAction extends Action {
}; };
} }
export interface IUserPassChangeAction extends Action { export interface TUserPassChangeAction extends Action {
type: UserTypes.USER_PASS_CHANGE; type: UserTypes.USER_PASS_CHANGE;
password: string; password: string;
} }
export interface IUserPassChangeSuccessAction extends Action { export interface TUserPassChangeSuccessAction extends Action {
type: UserTypes.USER_PASS_CHANGE_SUCCESS; type: UserTypes.USER_PASS_CHANGE_SUCCESS;
payload: IUserAuthJSON; payload: TUserAuthJSON;
} }
export interface IUserPassChangeFailAction extends Action { export interface TUserPassChangeFailAction extends Action {
type: UserTypes.USER_PASS_CHANGE_FAIL; type: UserTypes.USER_PASS_CHANGE_FAIL;
payload: { payload: {
error: string; error: string;
@@ -54,32 +54,32 @@ export interface IUserPassChangeFailAction extends Action {
}; };
} }
export function getUser(): IUserGetAction { export function getUser(): TUserGetAction {
return { type: UserTypes.USER_GET }; return { type: UserTypes.USER_GET };
} }
export function logoutUser(): IUserLogoutAction { export function logoutUser(): TUserLogoutAction {
return { type: UserTypes.USER_LOGOUT }; return { type: UserTypes.USER_LOGOUT };
} }
export function getUserSuccess(user: IUserAuthJSON): IUserGetSuccessAction { export function getUserSuccess(user: TUserAuthJSON): TUserGetSuccessAction {
return { type: UserTypes.USER_GET_SUCCESS, payload: user }; return { type: UserTypes.USER_GET_SUCCESS, payload: user };
} }
export function getUserFail( export function getUserFail(
error: string, error: string,
logout: boolean, logout: boolean,
): IUserGetFailAction { ): TUserGetFailAction {
return { type: UserTypes.USER_GET_FAIL, payload: { error, logout } }; return { type: UserTypes.USER_GET_FAIL, payload: { error, logout } };
} }
export function userPassChange(password: string): IUserPassChangeAction { export function userPassChange(password: string): TUserPassChangeAction {
return { type: UserTypes.USER_PASS_CHANGE, password }; return { type: UserTypes.USER_PASS_CHANGE, password };
} }
export function userPassChangeSuccess( export function userPassChangeSuccess(
user: IUserAuthJSON, user: TUserAuthJSON,
): IUserPassChangeSuccessAction { ): TUserPassChangeSuccessAction {
showPasswordSavedToast(); showPasswordSavedToast();
return { type: UserTypes.USER_PASS_CHANGE_SUCCESS, payload: user }; return { type: UserTypes.USER_PASS_CHANGE_SUCCESS, payload: user };
} }
@@ -87,7 +87,7 @@ export function userPassChangeSuccess(
export function userPassChangeFail( export function userPassChangeFail(
error: string, error: string,
logout: boolean, logout: boolean,
): IUserPassChangeFailAction { ): TUserPassChangeFailAction {
showPasswordNotSavedToast(error); showPasswordNotSavedToast(error);
return { return {
type: UserTypes.USER_PASS_CHANGE_FAIL, type: UserTypes.USER_PASS_CHANGE_FAIL,
@@ -96,10 +96,10 @@ export function userPassChangeFail(
} }
export type UserAction = export type UserAction =
| IUserGetAction | TUserGetAction
| IUserGetSuccessAction | TUserGetSuccessAction
| IUserGetFailAction | TUserGetFailAction
| IUserLogoutAction | TUserLogoutAction
| IUserPassChangeAction | TUserPassChangeAction
| IUserPassChangeFailAction | TUserPassChangeFailAction
| IUserPassChangeSuccessAction; | TUserPassChangeSuccessAction;

View File

@@ -1,20 +1,20 @@
import { Reducer } from "react"; import { Reducer } from "react";
import { IUserJSON } from "~/src/shared/types"; import { TUserJSON } from "~/src/shared/types";
import { AuthAction, AuthTypes } from "~src/redux/auth/actions"; import { AuthAction, AuthTypes } from "~src/redux/auth/actions";
import { UserAction, UserTypes } from "./actions"; import { UserAction, UserTypes } from "./actions";
export interface IUserState { export interface TUserState {
user: IUserJSON | null; user: TUserJSON | null;
} }
const defaultUserState: IUserState = { const defaultUserState: TUserState = {
user: null, user: null,
}; };
export const userReducer: Reducer<IUserState, AuthAction> = ( export const userReducer: Reducer<TUserState, AuthAction> = (
state: IUserState = defaultUserState, state: TUserState = defaultUserState,
action: AuthAction | UserAction, action: AuthAction | UserAction,
): IUserState => { ): TUserState => {
switch (action.type) { switch (action.type) {
case AuthTypes.AUTH_SUCCESS: case AuthTypes.AUTH_SUCCESS:
case UserTypes.USER_GET_SUCCESS: case UserTypes.USER_GET_SUCCESS:

View File

@@ -4,7 +4,7 @@ import { changeUserPassword, fetchUser } from "~src/redux/api/user";
import { import {
getUserFail, getUserFail,
getUserSuccess, getUserSuccess,
IUserPassChangeAction, TUserPassChangeAction,
userPassChangeFail, userPassChangeFail,
userPassChangeSuccess, userPassChangeSuccess,
UserTypes, UserTypes,
@@ -32,7 +32,7 @@ function* getUser() {
} }
} }
function* userPassChange(action: IUserPassChangeAction) { function* userPassChange(action: TUserPassChangeAction) {
try { try {
const { response, timeout } = yield race({ const { response, timeout } = yield race({
response: call(changeUserPassword, action.password), response: call(changeUserPassword, action.password),

15
shared/node_modules/.package-lock.json generated vendored Normal file
View File

@@ -0,0 +1,15 @@
{
"name": "photos-shared",
"lockfileVersion": 2,
"requires": true,
"packages": {
"node_modules/zod": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

21
shared/node_modules/zod/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Colin McDonnell
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2739
shared/node_modules/zod/README.md generated vendored Normal file

File diff suppressed because it is too large Load Diff

2
shared/node_modules/zod/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export * from "./lib";
export as namespace Zod;

163
shared/node_modules/zod/lib/ZodError.d.ts generated vendored Normal file
View File

@@ -0,0 +1,163 @@
import type { TypeOf, ZodType } from ".";
import { Primitive } from "./helpers/typeAliases";
import { util, ZodParsedType } from "./helpers/util";
declare type allKeys<T> = T extends any ? keyof T : never;
export declare type inferFlattenedErrors<T extends ZodType<any, any, any>, U = string> = typeToFlattenedError<TypeOf<T>, U>;
export declare type typeToFlattenedError<T, U = string> = {
formErrors: U[];
fieldErrors: {
[P in allKeys<T>]?: U[];
};
};
export declare const ZodIssueCode: {
invalid_type: "invalid_type";
invalid_literal: "invalid_literal";
custom: "custom";
invalid_union: "invalid_union";
invalid_union_discriminator: "invalid_union_discriminator";
invalid_enum_value: "invalid_enum_value";
unrecognized_keys: "unrecognized_keys";
invalid_arguments: "invalid_arguments";
invalid_return_type: "invalid_return_type";
invalid_date: "invalid_date";
invalid_string: "invalid_string";
too_small: "too_small";
too_big: "too_big";
invalid_intersection_types: "invalid_intersection_types";
not_multiple_of: "not_multiple_of";
not_finite: "not_finite";
};
export declare type ZodIssueCode = keyof typeof ZodIssueCode;
export declare type ZodIssueBase = {
path: (string | number)[];
message?: string;
};
export interface ZodInvalidTypeIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_type;
expected: ZodParsedType;
received: ZodParsedType;
}
export interface ZodInvalidLiteralIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_literal;
expected: unknown;
received: unknown;
}
export interface ZodUnrecognizedKeysIssue extends ZodIssueBase {
code: typeof ZodIssueCode.unrecognized_keys;
keys: string[];
}
export interface ZodInvalidUnionIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_union;
unionErrors: ZodError[];
}
export interface ZodInvalidUnionDiscriminatorIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_union_discriminator;
options: Primitive[];
}
export interface ZodInvalidEnumValueIssue extends ZodIssueBase {
received: string | number;
code: typeof ZodIssueCode.invalid_enum_value;
options: (string | number)[];
}
export interface ZodInvalidArgumentsIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_arguments;
argumentsError: ZodError;
}
export interface ZodInvalidReturnTypeIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_return_type;
returnTypeError: ZodError;
}
export interface ZodInvalidDateIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_date;
}
export declare type StringValidation = "email" | "url" | "emoji" | "uuid" | "regex" | "cuid" | "cuid2" | "ulid" | "datetime" | "ip" | {
includes: string;
position?: number;
} | {
startsWith: string;
} | {
endsWith: string;
};
export interface ZodInvalidStringIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_string;
validation: StringValidation;
}
export interface ZodTooSmallIssue extends ZodIssueBase {
code: typeof ZodIssueCode.too_small;
minimum: number | bigint;
inclusive: boolean;
exact?: boolean;
type: "array" | "string" | "number" | "set" | "date" | "bigint";
}
export interface ZodTooBigIssue extends ZodIssueBase {
code: typeof ZodIssueCode.too_big;
maximum: number | bigint;
inclusive: boolean;
exact?: boolean;
type: "array" | "string" | "number" | "set" | "date" | "bigint";
}
export interface ZodInvalidIntersectionTypesIssue extends ZodIssueBase {
code: typeof ZodIssueCode.invalid_intersection_types;
}
export interface ZodNotMultipleOfIssue extends ZodIssueBase {
code: typeof ZodIssueCode.not_multiple_of;
multipleOf: number | bigint;
}
export interface ZodNotFiniteIssue extends ZodIssueBase {
code: typeof ZodIssueCode.not_finite;
}
export interface ZodCustomIssue extends ZodIssueBase {
code: typeof ZodIssueCode.custom;
params?: {
[k: string]: any;
};
}
export declare type DenormalizedError = {
[k: string]: DenormalizedError | string[];
};
export declare type ZodIssueOptionalMessage = ZodInvalidTypeIssue | ZodInvalidLiteralIssue | ZodUnrecognizedKeysIssue | ZodInvalidUnionIssue | ZodInvalidUnionDiscriminatorIssue | ZodInvalidEnumValueIssue | ZodInvalidArgumentsIssue | ZodInvalidReturnTypeIssue | ZodInvalidDateIssue | ZodInvalidStringIssue | ZodTooSmallIssue | ZodTooBigIssue | ZodInvalidIntersectionTypesIssue | ZodNotMultipleOfIssue | ZodNotFiniteIssue | ZodCustomIssue;
export declare type ZodIssue = ZodIssueOptionalMessage & {
fatal?: boolean;
message: string;
};
export declare const quotelessJson: (obj: any) => string;
declare type recursiveZodFormattedError<T> = T extends [any, ...any[]] ? {
[K in keyof T]?: ZodFormattedError<T[K]>;
} : T extends any[] ? {
[k: number]: ZodFormattedError<T[number]>;
} : T extends object ? {
[K in keyof T]?: ZodFormattedError<T[K]>;
} : unknown;
export declare type ZodFormattedError<T, U = string> = {
_errors: U[];
} & recursiveZodFormattedError<NonNullable<T>>;
export declare type inferFormattedError<T extends ZodType<any, any, any>, U = string> = ZodFormattedError<TypeOf<T>, U>;
export declare class ZodError<T = any> extends Error {
issues: ZodIssue[];
get errors(): ZodIssue[];
constructor(issues: ZodIssue[]);
format(): ZodFormattedError<T>;
format<U>(mapper: (issue: ZodIssue) => U): ZodFormattedError<T, U>;
static create: (issues: ZodIssue[]) => ZodError<any>;
toString(): string;
get message(): string;
get isEmpty(): boolean;
addIssue: (sub: ZodIssue) => void;
addIssues: (subs?: ZodIssue[]) => void;
flatten(): typeToFlattenedError<T>;
flatten<U>(mapper?: (issue: ZodIssue) => U): typeToFlattenedError<T, U>;
get formErrors(): typeToFlattenedError<T, string>;
}
declare type stripPath<T extends object> = T extends any ? util.OmitKeys<T, "path"> : never;
export declare type IssueData = stripPath<ZodIssueOptionalMessage> & {
path?: (string | number)[];
fatal?: boolean;
};
export declare type ErrorMapCtx = {
defaultError: string;
data: any;
};
export declare type ZodErrorMap = (issue: ZodIssueOptionalMessage, _ctx: ErrorMapCtx) => {
message: string;
};
export {};

124
shared/node_modules/zod/lib/ZodError.js generated vendored Normal file
View File

@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ZodError = exports.quotelessJson = exports.ZodIssueCode = void 0;
const util_1 = require("./helpers/util");
exports.ZodIssueCode = util_1.util.arrayToEnum([
"invalid_type",
"invalid_literal",
"custom",
"invalid_union",
"invalid_union_discriminator",
"invalid_enum_value",
"unrecognized_keys",
"invalid_arguments",
"invalid_return_type",
"invalid_date",
"invalid_string",
"too_small",
"too_big",
"invalid_intersection_types",
"not_multiple_of",
"not_finite",
]);
const quotelessJson = (obj) => {
const json = JSON.stringify(obj, null, 2);
return json.replace(/"([^"]+)":/g, "$1:");
};
exports.quotelessJson = quotelessJson;
class ZodError extends Error {
constructor(issues) {
super();
this.issues = [];
this.addIssue = (sub) => {
this.issues = [...this.issues, sub];
};
this.addIssues = (subs = []) => {
this.issues = [...this.issues, ...subs];
};
const actualProto = new.target.prototype;
if (Object.setPrototypeOf) {
Object.setPrototypeOf(this, actualProto);
}
else {
this.__proto__ = actualProto;
}
this.name = "ZodError";
this.issues = issues;
}
get errors() {
return this.issues;
}
format(_mapper) {
const mapper = _mapper ||
function (issue) {
return issue.message;
};
const fieldErrors = { _errors: [] };
const processError = (error) => {
for (const issue of error.issues) {
if (issue.code === "invalid_union") {
issue.unionErrors.map(processError);
}
else if (issue.code === "invalid_return_type") {
processError(issue.returnTypeError);
}
else if (issue.code === "invalid_arguments") {
processError(issue.argumentsError);
}
else if (issue.path.length === 0) {
fieldErrors._errors.push(mapper(issue));
}
else {
let curr = fieldErrors;
let i = 0;
while (i < issue.path.length) {
const el = issue.path[i];
const terminal = i === issue.path.length - 1;
if (!terminal) {
curr[el] = curr[el] || { _errors: [] };
}
else {
curr[el] = curr[el] || { _errors: [] };
curr[el]._errors.push(mapper(issue));
}
curr = curr[el];
i++;
}
}
}
};
processError(this);
return fieldErrors;
}
toString() {
return this.message;
}
get message() {
return JSON.stringify(this.issues, util_1.util.jsonStringifyReplacer, 2);
}
get isEmpty() {
return this.issues.length === 0;
}
flatten(mapper = (issue) => issue.message) {
const fieldErrors = {};
const formErrors = [];
for (const sub of this.issues) {
if (sub.path.length > 0) {
fieldErrors[sub.path[0]] = fieldErrors[sub.path[0]] || [];
fieldErrors[sub.path[0]].push(mapper(sub));
}
else {
formErrors.push(mapper(sub));
}
}
return { formErrors, fieldErrors };
}
get formErrors() {
return this.flatten();
}
}
exports.ZodError = ZodError;
ZodError.create = (issues) => {
const error = new ZodError(issues);
return error;
};

17
shared/node_modules/zod/lib/__tests__/Mocker.d.ts generated vendored Normal file
View File

@@ -0,0 +1,17 @@
export declare class Mocker {
pick: (...args: any[]) => any;
get string(): string;
get number(): number;
get bigint(): bigint;
get boolean(): boolean;
get date(): Date;
get symbol(): symbol;
get null(): null;
get undefined(): undefined;
get stringOptional(): any;
get stringNullable(): any;
get numberOptional(): any;
get numberNullable(): any;
get booleanOptional(): any;
get booleanNullable(): any;
}

57
shared/node_modules/zod/lib/__tests__/Mocker.js generated vendored Normal file
View File

@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Mocker = void 0;
function getRandomInt(max) {
return Math.floor(Math.random() * Math.floor(max));
}
const testSymbol = Symbol("test");
class Mocker {
constructor() {
this.pick = (...args) => {
return args[getRandomInt(args.length)];
};
}
get string() {
return Math.random().toString(36).substring(7);
}
get number() {
return Math.random() * 100;
}
get bigint() {
return BigInt(Math.floor(Math.random() * 10000));
}
get boolean() {
return Math.random() < 0.5;
}
get date() {
return new Date(Math.floor(Date.now() * Math.random()));
}
get symbol() {
return testSymbol;
}
get null() {
return null;
}
get undefined() {
return undefined;
}
get stringOptional() {
return this.pick(this.string, this.undefined);
}
get stringNullable() {
return this.pick(this.string, this.null);
}
get numberOptional() {
return this.pick(this.number, this.undefined);
}
get numberNullable() {
return this.pick(this.number, this.null);
}
get booleanOptional() {
return this.pick(this.boolean, this.undefined);
}
get booleanNullable() {
return this.pick(this.boolean, this.null);
}
}
exports.Mocker = Mocker;

View File

@@ -0,0 +1,5 @@
import Benchmark from "benchmark";
declare const _default: {
suites: Benchmark.Suite[];
};
export default _default;

View File

@@ -0,0 +1,79 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const benchmark_1 = __importDefault(require("benchmark"));
const index_1 = require("../index");
const doubleSuite = new benchmark_1.default.Suite("z.discriminatedUnion: double");
const manySuite = new benchmark_1.default.Suite("z.discriminatedUnion: many");
const aSchema = index_1.z.object({
type: index_1.z.literal("a"),
});
const objA = {
type: "a",
};
const bSchema = index_1.z.object({
type: index_1.z.literal("b"),
});
const objB = {
type: "b",
};
const cSchema = index_1.z.object({
type: index_1.z.literal("c"),
});
const objC = {
type: "c",
};
const dSchema = index_1.z.object({
type: index_1.z.literal("d"),
});
const double = index_1.z.discriminatedUnion("type", [aSchema, bSchema]);
const many = index_1.z.discriminatedUnion("type", [aSchema, bSchema, cSchema, dSchema]);
doubleSuite
.add("valid: a", () => {
double.parse(objA);
})
.add("valid: b", () => {
double.parse(objB);
})
.add("invalid: null", () => {
try {
double.parse(null);
}
catch (err) { }
})
.add("invalid: wrong shape", () => {
try {
double.parse(objC);
}
catch (err) { }
})
.on("cycle", (e) => {
console.log(`${doubleSuite.name}: ${e.target}`);
});
manySuite
.add("valid: a", () => {
many.parse(objA);
})
.add("valid: c", () => {
many.parse(objC);
})
.add("invalid: null", () => {
try {
many.parse(null);
}
catch (err) { }
})
.add("invalid: wrong shape", () => {
try {
many.parse({ type: "unknown" });
}
catch (err) { }
})
.on("cycle", (e) => {
console.log(`${manySuite.name}: ${e.target}`);
});
exports.default = {
suites: [doubleSuite, manySuite],
};

1
shared/node_modules/zod/lib/benchmarks/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export {};

46
shared/node_modules/zod/lib/benchmarks/index.js generated vendored Normal file
View File

@@ -0,0 +1,46 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const discriminatedUnion_1 = __importDefault(require("./discriminatedUnion"));
const object_1 = __importDefault(require("./object"));
const primitives_1 = __importDefault(require("./primitives"));
const realworld_1 = __importDefault(require("./realworld"));
const string_1 = __importDefault(require("./string"));
const union_1 = __importDefault(require("./union"));
const argv = process.argv.slice(2);
let suites = [];
if (!argv.length) {
suites = [
...realworld_1.default.suites,
...primitives_1.default.suites,
...string_1.default.suites,
...object_1.default.suites,
...union_1.default.suites,
...discriminatedUnion_1.default.suites,
];
}
else {
if (argv.includes("--realworld")) {
suites.push(...realworld_1.default.suites);
}
if (argv.includes("--primitives")) {
suites.push(...primitives_1.default.suites);
}
if (argv.includes("--string")) {
suites.push(...string_1.default.suites);
}
if (argv.includes("--object")) {
suites.push(...object_1.default.suites);
}
if (argv.includes("--union")) {
suites.push(...union_1.default.suites);
}
if (argv.includes("--discriminatedUnion")) {
suites.push(...discriminatedUnion_1.default.suites);
}
}
for (const suite of suites) {
suite.run();
}

5
shared/node_modules/zod/lib/benchmarks/object.d.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
import Benchmark from "benchmark";
declare const _default: {
suites: Benchmark.Suite[];
};
export default _default;

70
shared/node_modules/zod/lib/benchmarks/object.js generated vendored Normal file
View File

@@ -0,0 +1,70 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const benchmark_1 = __importDefault(require("benchmark"));
const index_1 = require("../index");
const emptySuite = new benchmark_1.default.Suite("z.object: empty");
const shortSuite = new benchmark_1.default.Suite("z.object: short");
const longSuite = new benchmark_1.default.Suite("z.object: long");
const empty = index_1.z.object({});
const short = index_1.z.object({
string: index_1.z.string(),
});
const long = index_1.z.object({
string: index_1.z.string(),
number: index_1.z.number(),
boolean: index_1.z.boolean(),
});
emptySuite
.add("valid", () => {
empty.parse({});
})
.add("valid: extra keys", () => {
empty.parse({ string: "string" });
})
.add("invalid: null", () => {
try {
empty.parse(null);
}
catch (err) { }
})
.on("cycle", (e) => {
console.log(`${emptySuite.name}: ${e.target}`);
});
shortSuite
.add("valid", () => {
short.parse({ string: "string" });
})
.add("valid: extra keys", () => {
short.parse({ string: "string", number: 42 });
})
.add("invalid: null", () => {
try {
short.parse(null);
}
catch (err) { }
})
.on("cycle", (e) => {
console.log(`${shortSuite.name}: ${e.target}`);
});
longSuite
.add("valid", () => {
long.parse({ string: "string", number: 42, boolean: true });
})
.add("valid: extra keys", () => {
long.parse({ string: "string", number: 42, boolean: true, list: [] });
})
.add("invalid: null", () => {
try {
long.parse(null);
}
catch (err) { }
})
.on("cycle", (e) => {
console.log(`${longSuite.name}: ${e.target}`);
});
exports.default = {
suites: [emptySuite, shortSuite, longSuite],
};

View File

@@ -0,0 +1,5 @@
import Benchmark from "benchmark";
declare const _default: {
suites: Benchmark.Suite[];
};
export default _default;

136
shared/node_modules/zod/lib/benchmarks/primitives.js generated vendored Normal file
View File

@@ -0,0 +1,136 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const benchmark_1 = __importDefault(require("benchmark"));
const Mocker_1 = require("../__tests__/Mocker");
const index_1 = require("../index");
const val = new Mocker_1.Mocker();
const enumSuite = new benchmark_1.default.Suite("z.enum");
const enumSchema = index_1.z.enum(["a", "b", "c"]);
enumSuite
.add("valid", () => {
enumSchema.parse("a");
})
.add("invalid", () => {
try {
enumSchema.parse("x");
}
catch (e) { }
})
.on("cycle", (e) => {
console.log(`z.enum: ${e.target}`);
});
const undefinedSuite = new benchmark_1.default.Suite("z.undefined");
const undefinedSchema = index_1.z.undefined();
undefinedSuite
.add("valid", () => {
undefinedSchema.parse(undefined);
})
.add("invalid", () => {
try {
undefinedSchema.parse(1);
}
catch (e) { }
})
.on("cycle", (e) => {
console.log(`z.undefined: ${e.target}`);
});
const literalSuite = new benchmark_1.default.Suite("z.literal");
const short = "short";
const bad = "bad";
const literalSchema = index_1.z.literal("short");
literalSuite
.add("valid", () => {
literalSchema.parse(short);
})
.add("invalid", () => {
try {
literalSchema.parse(bad);
}
catch (e) { }
})
.on("cycle", (e) => {
console.log(`z.literal: ${e.target}`);
});
const numberSuite = new benchmark_1.default.Suite("z.number");
const numberSchema = index_1.z.number().int();
numberSuite
.add("valid", () => {
numberSchema.parse(1);
})
.add("invalid type", () => {
try {
numberSchema.parse("bad");
}
catch (e) { }
})
.add("invalid number", () => {
try {
numberSchema.parse(0.5);
}
catch (e) { }
})
.on("cycle", (e) => {
console.log(`z.number: ${e.target}`);
});
const dateSuite = new benchmark_1.default.Suite("z.date");
const plainDate = index_1.z.date();
const minMaxDate = index_1.z
.date()
.min(new Date("2021-01-01"))
.max(new Date("2030-01-01"));
dateSuite
.add("valid", () => {
plainDate.parse(new Date());
})
.add("invalid", () => {
try {
plainDate.parse(1);
}
catch (e) { }
})
.add("valid min and max", () => {
minMaxDate.parse(new Date("2023-01-01"));
})
.add("invalid min", () => {
try {
minMaxDate.parse(new Date("2019-01-01"));
}
catch (e) { }
})
.add("invalid max", () => {
try {
minMaxDate.parse(new Date("2031-01-01"));
}
catch (e) { }
})
.on("cycle", (e) => {
console.log(`z.date: ${e.target}`);
});
const symbolSuite = new benchmark_1.default.Suite("z.symbol");
const symbolSchema = index_1.z.symbol();
symbolSuite
.add("valid", () => {
symbolSchema.parse(val.symbol);
})
.add("invalid", () => {
try {
symbolSchema.parse(1);
}
catch (e) { }
})
.on("cycle", (e) => {
console.log(`z.symbol: ${e.target}`);
});
exports.default = {
suites: [
enumSuite,
undefinedSuite,
literalSuite,
numberSuite,
dateSuite,
symbolSuite,
],
};

View File

@@ -0,0 +1,5 @@
import Benchmark from "benchmark";
declare const _default: {
suites: Benchmark.Suite[];
};
export default _default;

56
shared/node_modules/zod/lib/benchmarks/realworld.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const benchmark_1 = __importDefault(require("benchmark"));
const index_1 = require("../index");
const shortSuite = new benchmark_1.default.Suite("realworld");
const People = index_1.z.array(index_1.z.object({
type: index_1.z.literal("person"),
hair: index_1.z.enum(["blue", "brown"]),
active: index_1.z.boolean(),
name: index_1.z.string(),
age: index_1.z.number().int(),
hobbies: index_1.z.array(index_1.z.string()),
address: index_1.z.object({
street: index_1.z.string(),
zip: index_1.z.string(),
country: index_1.z.string(),
}),
}));
let i = 0;
function num() {
return ++i;
}
function str() {
return (++i % 100).toString(16);
}
function array(fn) {
return Array.from({ length: ++i % 10 }, () => fn());
}
const people = Array.from({ length: 100 }, () => {
return {
type: "person",
hair: i % 2 ? "blue" : "brown",
active: !!(i % 2),
name: str(),
age: num(),
hobbies: array(str),
address: {
street: str(),
zip: str(),
country: str(),
},
};
});
shortSuite
.add("valid", () => {
People.parse(people);
})
.on("cycle", (e) => {
console.log(`${shortSuite.name}: ${e.target}`);
});
exports.default = {
suites: [shortSuite],
};

5
shared/node_modules/zod/lib/benchmarks/string.d.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
import Benchmark from "benchmark";
declare const _default: {
suites: Benchmark.Suite[];
};
export default _default;

55
shared/node_modules/zod/lib/benchmarks/string.js generated vendored Normal file
View File

@@ -0,0 +1,55 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const benchmark_1 = __importDefault(require("benchmark"));
const index_1 = require("../index");
const SUITE_NAME = "z.string";
const suite = new benchmark_1.default.Suite(SUITE_NAME);
const empty = "";
const short = "short";
const long = "long".repeat(256);
const manual = (str) => {
if (typeof str !== "string") {
throw new Error("Not a string");
}
return str;
};
const stringSchema = index_1.z.string();
const optionalStringSchema = index_1.z.string().optional();
const optionalNullableStringSchema = index_1.z.string().optional().nullable();
suite
.add("empty string", () => {
stringSchema.parse(empty);
})
.add("short string", () => {
stringSchema.parse(short);
})
.add("long string", () => {
stringSchema.parse(long);
})
.add("optional string", () => {
optionalStringSchema.parse(long);
})
.add("nullable string", () => {
optionalNullableStringSchema.parse(long);
})
.add("nullable (null) string", () => {
optionalNullableStringSchema.parse(null);
})
.add("invalid: null", () => {
try {
stringSchema.parse(null);
}
catch (err) { }
})
.add("manual parser: long", () => {
manual(long);
})
.on("cycle", (e) => {
console.log(`${SUITE_NAME}: ${e.target}`);
});
exports.default = {
suites: [suite],
};

5
shared/node_modules/zod/lib/benchmarks/union.d.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
import Benchmark from "benchmark";
declare const _default: {
suites: Benchmark.Suite[];
};
export default _default;

79
shared/node_modules/zod/lib/benchmarks/union.js generated vendored Normal file
View File

@@ -0,0 +1,79 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const benchmark_1 = __importDefault(require("benchmark"));
const index_1 = require("../index");
const doubleSuite = new benchmark_1.default.Suite("z.union: double");
const manySuite = new benchmark_1.default.Suite("z.union: many");
const aSchema = index_1.z.object({
type: index_1.z.literal("a"),
});
const objA = {
type: "a",
};
const bSchema = index_1.z.object({
type: index_1.z.literal("b"),
});
const objB = {
type: "b",
};
const cSchema = index_1.z.object({
type: index_1.z.literal("c"),
});
const objC = {
type: "c",
};
const dSchema = index_1.z.object({
type: index_1.z.literal("d"),
});
const double = index_1.z.union([aSchema, bSchema]);
const many = index_1.z.union([aSchema, bSchema, cSchema, dSchema]);
doubleSuite
.add("valid: a", () => {
double.parse(objA);
})
.add("valid: b", () => {
double.parse(objB);
})
.add("invalid: null", () => {
try {
double.parse(null);
}
catch (err) { }
})
.add("invalid: wrong shape", () => {
try {
double.parse(objC);
}
catch (err) { }
})
.on("cycle", (e) => {
console.log(`${doubleSuite.name}: ${e.target}`);
});
manySuite
.add("valid: a", () => {
many.parse(objA);
})
.add("valid: c", () => {
many.parse(objC);
})
.add("invalid: null", () => {
try {
many.parse(null);
}
catch (err) { }
})
.add("invalid: wrong shape", () => {
try {
many.parse({ type: "unknown" });
}
catch (err) { }
})
.on("cycle", (e) => {
console.log(`${manySuite.name}: ${e.target}`);
});
exports.default = {
suites: [doubleSuite, manySuite],
};

5
shared/node_modules/zod/lib/errors.d.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
import defaultErrorMap from "./locales/en";
import type { ZodErrorMap } from "./ZodError";
export { defaultErrorMap };
export declare function setErrorMap(map: ZodErrorMap): void;
export declare function getErrorMap(): ZodErrorMap;

17
shared/node_modules/zod/lib/errors.js generated vendored Normal file
View File

@@ -0,0 +1,17 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getErrorMap = exports.setErrorMap = exports.defaultErrorMap = void 0;
const en_1 = __importDefault(require("./locales/en"));
exports.defaultErrorMap = en_1.default;
let overrideErrorMap = en_1.default;
function setErrorMap(map) {
overrideErrorMap = map;
}
exports.setErrorMap = setErrorMap;
function getErrorMap() {
return overrideErrorMap;
}
exports.getErrorMap = getErrorMap;

6
shared/node_modules/zod/lib/external.d.ts generated vendored Normal file
View File

@@ -0,0 +1,6 @@
export * from "./errors";
export * from "./helpers/parseUtil";
export * from "./helpers/typeAliases";
export * from "./helpers/util";
export * from "./types";
export * from "./ZodError";

18
shared/node_modules/zod/lib/external.js generated vendored Normal file
View File

@@ -0,0 +1,18 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./errors"), exports);
__exportStar(require("./helpers/parseUtil"), exports);
__exportStar(require("./helpers/typeAliases"), exports);
__exportStar(require("./helpers/util"), exports);
__exportStar(require("./types"), exports);
__exportStar(require("./ZodError"), exports);

8
shared/node_modules/zod/lib/helpers/enumUtil.d.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
export declare namespace enumUtil {
type UnionToIntersectionFn<T> = (T extends unknown ? (k: () => T) => void : never) extends (k: infer Intersection) => void ? Intersection : never;
type GetUnionLast<T> = UnionToIntersectionFn<T> extends () => infer Last ? Last : never;
type UnionToTuple<T, Tuple extends unknown[] = []> = [T] extends [never] ? Tuple : UnionToTuple<Exclude<T, GetUnionLast<T>>, [GetUnionLast<T>, ...Tuple]>;
type CastToStringTuple<T> = T extends [string, ...string[]] ? T : never;
export type UnionToTupleString<T> = CastToStringTuple<UnionToTuple<T>>;
export {};
}

2
shared/node_modules/zod/lib/helpers/enumUtil.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

9
shared/node_modules/zod/lib/helpers/errorUtil.d.ts generated vendored Normal file
View File

@@ -0,0 +1,9 @@
export declare namespace errorUtil {
type ErrMessage = string | {
message?: string;
};
const errToObj: (message?: ErrMessage | undefined) => {
message?: string | undefined;
};
const toString: (message?: ErrMessage | undefined) => string | undefined;
}

8
shared/node_modules/zod/lib/helpers/errorUtil.js generated vendored Normal file
View File

@@ -0,0 +1,8 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.errorUtil = void 0;
var errorUtil;
(function (errorUtil) {
errorUtil.errToObj = (message) => typeof message === "string" ? { message } : message || {};
errorUtil.toString = (message) => typeof message === "string" ? message : message === null || message === void 0 ? void 0 : message.message;
})(errorUtil = exports.errorUtil || (exports.errorUtil = {}));

78
shared/node_modules/zod/lib/helpers/parseUtil.d.ts generated vendored Normal file
View File

@@ -0,0 +1,78 @@
import type { IssueData, ZodErrorMap, ZodIssue } from "../ZodError";
import type { ZodParsedType } from "./util";
export declare const makeIssue: (params: {
data: any;
path: (string | number)[];
errorMaps: ZodErrorMap[];
issueData: IssueData;
}) => ZodIssue;
export declare type ParseParams = {
path: (string | number)[];
errorMap: ZodErrorMap;
async: boolean;
};
export declare type ParsePathComponent = string | number;
export declare type ParsePath = ParsePathComponent[];
export declare const EMPTY_PATH: ParsePath;
export interface ParseContext {
readonly common: {
readonly issues: ZodIssue[];
readonly contextualErrorMap?: ZodErrorMap;
readonly async: boolean;
};
readonly path: ParsePath;
readonly schemaErrorMap?: ZodErrorMap;
readonly parent: ParseContext | null;
readonly data: any;
readonly parsedType: ZodParsedType;
}
export declare type ParseInput = {
data: any;
path: (string | number)[];
parent: ParseContext;
};
export declare function addIssueToContext(ctx: ParseContext, issueData: IssueData): void;
export declare type ObjectPair = {
key: SyncParseReturnType<any>;
value: SyncParseReturnType<any>;
};
export declare class ParseStatus {
value: "aborted" | "dirty" | "valid";
dirty(): void;
abort(): void;
static mergeArray(status: ParseStatus, results: SyncParseReturnType<any>[]): SyncParseReturnType;
static mergeObjectAsync(status: ParseStatus, pairs: {
key: ParseReturnType<any>;
value: ParseReturnType<any>;
}[]): Promise<SyncParseReturnType<any>>;
static mergeObjectSync(status: ParseStatus, pairs: {
key: SyncParseReturnType<any>;
value: SyncParseReturnType<any>;
alwaysSet?: boolean;
}[]): SyncParseReturnType;
}
export interface ParseResult {
status: "aborted" | "dirty" | "valid";
data: any;
}
export declare type INVALID = {
status: "aborted";
};
export declare const INVALID: INVALID;
export declare type DIRTY<T> = {
status: "dirty";
value: T;
};
export declare const DIRTY: <T>(value: T) => DIRTY<T>;
export declare type OK<T> = {
status: "valid";
value: T;
};
export declare const OK: <T>(value: T) => OK<T>;
export declare type SyncParseReturnType<T = any> = OK<T> | DIRTY<T> | INVALID;
export declare type AsyncParseReturnType<T> = Promise<SyncParseReturnType<T>>;
export declare type ParseReturnType<T> = SyncParseReturnType<T> | AsyncParseReturnType<T>;
export declare const isAborted: (x: ParseReturnType<any>) => x is INVALID;
export declare const isDirty: <T>(x: ParseReturnType<T>) => x is OK<T> | DIRTY<T>;
export declare const isValid: <T>(x: ParseReturnType<T>) => x is OK<T> | DIRTY<T>;
export declare const isAsync: <T>(x: ParseReturnType<T>) => x is AsyncParseReturnType<T>;

114
shared/node_modules/zod/lib/helpers/parseUtil.js generated vendored Normal file
View File

@@ -0,0 +1,114 @@
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isAsync = exports.isValid = exports.isDirty = exports.isAborted = exports.OK = exports.DIRTY = exports.INVALID = exports.ParseStatus = exports.addIssueToContext = exports.EMPTY_PATH = exports.makeIssue = void 0;
const errors_1 = require("../errors");
const en_1 = __importDefault(require("../locales/en"));
const makeIssue = (params) => {
const { data, path, errorMaps, issueData } = params;
const fullPath = [...path, ...(issueData.path || [])];
const fullIssue = {
...issueData,
path: fullPath,
};
let errorMessage = "";
const maps = errorMaps
.filter((m) => !!m)
.slice()
.reverse();
for (const map of maps) {
errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;
}
return {
...issueData,
path: fullPath,
message: issueData.message || errorMessage,
};
};
exports.makeIssue = makeIssue;
exports.EMPTY_PATH = [];
function addIssueToContext(ctx, issueData) {
const issue = (0, exports.makeIssue)({
issueData: issueData,
data: ctx.data,
path: ctx.path,
errorMaps: [
ctx.common.contextualErrorMap,
ctx.schemaErrorMap,
(0, errors_1.getErrorMap)(),
en_1.default,
].filter((x) => !!x),
});
ctx.common.issues.push(issue);
}
exports.addIssueToContext = addIssueToContext;
class ParseStatus {
constructor() {
this.value = "valid";
}
dirty() {
if (this.value === "valid")
this.value = "dirty";
}
abort() {
if (this.value !== "aborted")
this.value = "aborted";
}
static mergeArray(status, results) {
const arrayValue = [];
for (const s of results) {
if (s.status === "aborted")
return exports.INVALID;
if (s.status === "dirty")
status.dirty();
arrayValue.push(s.value);
}
return { status: status.value, value: arrayValue };
}
static async mergeObjectAsync(status, pairs) {
const syncPairs = [];
for (const pair of pairs) {
syncPairs.push({
key: await pair.key,
value: await pair.value,
});
}
return ParseStatus.mergeObjectSync(status, syncPairs);
}
static mergeObjectSync(status, pairs) {
const finalObject = {};
for (const pair of pairs) {
const { key, value } = pair;
if (key.status === "aborted")
return exports.INVALID;
if (value.status === "aborted")
return exports.INVALID;
if (key.status === "dirty")
status.dirty();
if (value.status === "dirty")
status.dirty();
if (typeof value.value !== "undefined" || pair.alwaysSet) {
finalObject[key.value] = value.value;
}
}
return { status: status.value, value: finalObject };
}
}
exports.ParseStatus = ParseStatus;
exports.INVALID = Object.freeze({
status: "aborted",
});
const DIRTY = (value) => ({ status: "dirty", value });
exports.DIRTY = DIRTY;
const OK = (value) => ({ status: "valid", value });
exports.OK = OK;
const isAborted = (x) => x.status === "aborted";
exports.isAborted = isAborted;
const isDirty = (x) => x.status === "dirty";
exports.isDirty = isDirty;
const isValid = (x) => x.status === "valid";
exports.isValid = isValid;
const isAsync = (x) => typeof Promise !== "undefined" && x instanceof Promise;
exports.isAsync = isAsync;

8
shared/node_modules/zod/lib/helpers/partialUtil.d.ts generated vendored Normal file
View File

@@ -0,0 +1,8 @@
import type { ZodArray, ZodNullable, ZodObject, ZodOptional, ZodRawShape, ZodTuple, ZodTupleItems, ZodTypeAny } from "../index";
export declare namespace partialUtil {
type DeepPartial<T extends ZodTypeAny> = T extends ZodObject<ZodRawShape> ? ZodObject<{
[k in keyof T["shape"]]: ZodOptional<DeepPartial<T["shape"][k]>>;
}, T["_def"]["unknownKeys"], T["_def"]["catchall"]> : T extends ZodArray<infer Type, infer Card> ? ZodArray<DeepPartial<Type>, Card> : T extends ZodOptional<infer Type> ? ZodOptional<DeepPartial<Type>> : T extends ZodNullable<infer Type> ? ZodNullable<DeepPartial<Type>> : T extends ZodTuple<infer Items> ? {
[k in keyof Items]: Items[k] extends ZodTypeAny ? DeepPartial<Items[k]> : never;
} extends infer PI ? PI extends ZodTupleItems ? ZodTuple<PI> : never : never : T;
}

2
shared/node_modules/zod/lib/helpers/partialUtil.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

2
shared/node_modules/zod/lib/helpers/typeAliases.d.ts generated vendored Normal file
View File

@@ -0,0 +1,2 @@
export declare type Primitive = string | number | symbol | bigint | boolean | null | undefined;
export declare type Scalars = Primitive | Primitive[];

2
shared/node_modules/zod/lib/helpers/typeAliases.js generated vendored Normal file
View File

@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

68
shared/node_modules/zod/lib/helpers/util.d.ts generated vendored Normal file
View File

@@ -0,0 +1,68 @@
export declare namespace util {
type AssertEqual<T, U> = (<V>() => V extends T ? 1 : 2) extends <V>() => V extends U ? 1 : 2 ? true : false;
export type isAny<T> = 0 extends 1 & T ? true : false;
export const assertEqual: <A, B>(val: AssertEqual<A, B>) => AssertEqual<A, B>;
export function assertIs<T>(_arg: T): void;
export function assertNever(_x: never): never;
export type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type OmitKeys<T, K extends string> = Pick<T, Exclude<keyof T, K>>;
export type MakePartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
export const arrayToEnum: <T extends string, U extends [T, ...T[]]>(items: U) => { [k in U[number]]: k; };
export const getValidEnumValues: (obj: any) => any[];
export const objectValues: (obj: any) => any[];
export const objectKeys: ObjectConstructor["keys"];
export const find: <T>(arr: T[], checker: (arg: T) => any) => T | undefined;
export type identity<T> = objectUtil.identity<T>;
export type flatten<T> = objectUtil.flatten<T>;
export type noUndefined<T> = T extends undefined ? never : T;
export const isInteger: NumberConstructor["isInteger"];
export function joinValues<T extends any[]>(array: T, separator?: string): string;
export const jsonStringifyReplacer: (_: string, value: any) => any;
export {};
}
export declare namespace objectUtil {
export type MergeShapes<U, V> = {
[k in Exclude<keyof U, keyof V>]: U[k];
} & V;
type requiredKeys<T extends object> = {
[k in keyof T]: undefined extends T[k] ? never : k;
}[keyof T];
export type addQuestionMarks<T extends object, R extends keyof T = requiredKeys<T>> = Pick<Required<T>, R> & Partial<T>;
export type identity<T> = T;
export type flatten<T> = identity<{
[k in keyof T]: T[k];
}>;
export type noNeverKeys<T> = {
[k in keyof T]: [T[k]] extends [never] ? never : k;
}[keyof T];
export type noNever<T> = identity<{
[k in noNeverKeys<T>]: k extends keyof T ? T[k] : never;
}>;
export const mergeShapes: <U, T>(first: U, second: T) => T & U;
export type extendShape<A, B> = flatten<Omit<A, keyof B> & B>;
export {};
}
export declare const ZodParsedType: {
function: "function";
number: "number";
string: "string";
nan: "nan";
integer: "integer";
float: "float";
boolean: "boolean";
date: "date";
bigint: "bigint";
symbol: "symbol";
undefined: "undefined";
null: "null";
array: "array";
object: "object";
unknown: "unknown";
promise: "promise";
void: "void";
never: "never";
map: "map";
set: "set";
};
export declare type ZodParsedType = keyof typeof ZodParsedType;
export declare const getParsedType: (data: any) => ZodParsedType;

142
shared/node_modules/zod/lib/helpers/util.js generated vendored Normal file
View File

@@ -0,0 +1,142 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getParsedType = exports.ZodParsedType = exports.objectUtil = exports.util = void 0;
var util;
(function (util) {
util.assertEqual = (val) => val;
function assertIs(_arg) { }
util.assertIs = assertIs;
function assertNever(_x) {
throw new Error();
}
util.assertNever = assertNever;
util.arrayToEnum = (items) => {
const obj = {};
for (const item of items) {
obj[item] = item;
}
return obj;
};
util.getValidEnumValues = (obj) => {
const validKeys = util.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== "number");
const filtered = {};
for (const k of validKeys) {
filtered[k] = obj[k];
}
return util.objectValues(filtered);
};
util.objectValues = (obj) => {
return util.objectKeys(obj).map(function (e) {
return obj[e];
});
};
util.objectKeys = typeof Object.keys === "function"
? (obj) => Object.keys(obj)
: (object) => {
const keys = [];
for (const key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
keys.push(key);
}
}
return keys;
};
util.find = (arr, checker) => {
for (const item of arr) {
if (checker(item))
return item;
}
return undefined;
};
util.isInteger = typeof Number.isInteger === "function"
? (val) => Number.isInteger(val)
: (val) => typeof val === "number" && isFinite(val) && Math.floor(val) === val;
function joinValues(array, separator = " | ") {
return array
.map((val) => (typeof val === "string" ? `'${val}'` : val))
.join(separator);
}
util.joinValues = joinValues;
util.jsonStringifyReplacer = (_, value) => {
if (typeof value === "bigint") {
return value.toString();
}
return value;
};
})(util = exports.util || (exports.util = {}));
var objectUtil;
(function (objectUtil) {
objectUtil.mergeShapes = (first, second) => {
return {
...first,
...second,
};
};
})(objectUtil = exports.objectUtil || (exports.objectUtil = {}));
exports.ZodParsedType = util.arrayToEnum([
"string",
"nan",
"number",
"integer",
"float",
"boolean",
"date",
"bigint",
"symbol",
"function",
"undefined",
"null",
"array",
"object",
"unknown",
"promise",
"void",
"never",
"map",
"set",
]);
const getParsedType = (data) => {
const t = typeof data;
switch (t) {
case "undefined":
return exports.ZodParsedType.undefined;
case "string":
return exports.ZodParsedType.string;
case "number":
return isNaN(data) ? exports.ZodParsedType.nan : exports.ZodParsedType.number;
case "boolean":
return exports.ZodParsedType.boolean;
case "function":
return exports.ZodParsedType.function;
case "bigint":
return exports.ZodParsedType.bigint;
case "symbol":
return exports.ZodParsedType.symbol;
case "object":
if (Array.isArray(data)) {
return exports.ZodParsedType.array;
}
if (data === null) {
return exports.ZodParsedType.null;
}
if (data.then &&
typeof data.then === "function" &&
data.catch &&
typeof data.catch === "function") {
return exports.ZodParsedType.promise;
}
if (typeof Map !== "undefined" && data instanceof Map) {
return exports.ZodParsedType.map;
}
if (typeof Set !== "undefined" && data instanceof Set) {
return exports.ZodParsedType.set;
}
if (typeof Date !== "undefined" && data instanceof Date) {
return exports.ZodParsedType.date;
}
return exports.ZodParsedType.object;
default:
return exports.ZodParsedType.unknown;
}
};
exports.getParsedType = getParsedType;

4
shared/node_modules/zod/lib/index.d.ts generated vendored Normal file
View File

@@ -0,0 +1,4 @@
import * as z from "./external";
export * from "./external";
export { z };
export default z;

29
shared/node_modules/zod/lib/index.js generated vendored Normal file
View File

@@ -0,0 +1,29 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.z = void 0;
const z = __importStar(require("./external"));
exports.z = z;
__exportStar(require("./external"), exports);
exports.default = z;

3953
shared/node_modules/zod/lib/index.mjs generated vendored Normal file

File diff suppressed because it is too large Load Diff

4066
shared/node_modules/zod/lib/index.umd.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

3
shared/node_modules/zod/lib/locales/en.d.ts generated vendored Normal file
View File

@@ -0,0 +1,3 @@
import { ZodErrorMap } from "../ZodError";
declare const errorMap: ZodErrorMap;
export default errorMap;

129
shared/node_modules/zod/lib/locales/en.js generated vendored Normal file
View File

@@ -0,0 +1,129 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const util_1 = require("../helpers/util");
const ZodError_1 = require("../ZodError");
const errorMap = (issue, _ctx) => {
let message;
switch (issue.code) {
case ZodError_1.ZodIssueCode.invalid_type:
if (issue.received === util_1.ZodParsedType.undefined) {
message = "Required";
}
else {
message = `Expected ${issue.expected}, received ${issue.received}`;
}
break;
case ZodError_1.ZodIssueCode.invalid_literal:
message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util_1.util.jsonStringifyReplacer)}`;
break;
case ZodError_1.ZodIssueCode.unrecognized_keys:
message = `Unrecognized key(s) in object: ${util_1.util.joinValues(issue.keys, ", ")}`;
break;
case ZodError_1.ZodIssueCode.invalid_union:
message = `Invalid input`;
break;
case ZodError_1.ZodIssueCode.invalid_union_discriminator:
message = `Invalid discriminator value. Expected ${util_1.util.joinValues(issue.options)}`;
break;
case ZodError_1.ZodIssueCode.invalid_enum_value:
message = `Invalid enum value. Expected ${util_1.util.joinValues(issue.options)}, received '${issue.received}'`;
break;
case ZodError_1.ZodIssueCode.invalid_arguments:
message = `Invalid function arguments`;
break;
case ZodError_1.ZodIssueCode.invalid_return_type:
message = `Invalid function return type`;
break;
case ZodError_1.ZodIssueCode.invalid_date:
message = `Invalid date`;
break;
case ZodError_1.ZodIssueCode.invalid_string:
if (typeof issue.validation === "object") {
if ("includes" in issue.validation) {
message = `Invalid input: must include "${issue.validation.includes}"`;
if (typeof issue.validation.position === "number") {
message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`;
}
}
else if ("startsWith" in issue.validation) {
message = `Invalid input: must start with "${issue.validation.startsWith}"`;
}
else if ("endsWith" in issue.validation) {
message = `Invalid input: must end with "${issue.validation.endsWith}"`;
}
else {
util_1.util.assertNever(issue.validation);
}
}
else if (issue.validation !== "regex") {
message = `Invalid ${issue.validation}`;
}
else {
message = "Invalid";
}
break;
case ZodError_1.ZodIssueCode.too_small:
if (issue.type === "array")
message = `Array must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`;
else if (issue.type === "string")
message = `String must contain ${issue.exact ? "exactly" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;
else if (issue.type === "number")
message = `Number must be ${issue.exact
? `exactly equal to `
: issue.inclusive
? `greater than or equal to `
: `greater than `}${issue.minimum}`;
else if (issue.type === "date")
message = `Date must be ${issue.exact
? `exactly equal to `
: issue.inclusive
? `greater than or equal to `
: `greater than `}${new Date(Number(issue.minimum))}`;
else
message = "Invalid input";
break;
case ZodError_1.ZodIssueCode.too_big:
if (issue.type === "array")
message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`;
else if (issue.type === "string")
message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`;
else if (issue.type === "number")
message = `Number must be ${issue.exact
? `exactly`
: issue.inclusive
? `less than or equal to`
: `less than`} ${issue.maximum}`;
else if (issue.type === "bigint")
message = `BigInt must be ${issue.exact
? `exactly`
: issue.inclusive
? `less than or equal to`
: `less than`} ${issue.maximum}`;
else if (issue.type === "date")
message = `Date must be ${issue.exact
? `exactly`
: issue.inclusive
? `smaller than or equal to`
: `smaller than`} ${new Date(Number(issue.maximum))}`;
else
message = "Invalid input";
break;
case ZodError_1.ZodIssueCode.custom:
message = `Invalid input`;
break;
case ZodError_1.ZodIssueCode.invalid_intersection_types:
message = `Intersection results could not be merged`;
break;
case ZodError_1.ZodIssueCode.not_multiple_of:
message = `Number must be a multiple of ${issue.multipleOf}`;
break;
case ZodError_1.ZodIssueCode.not_finite:
message = "Number must be finite";
break;
default:
message = _ctx.defaultError;
util_1.util.assertNever(issue);
}
return { message };
};
exports.default = errorMap;

1012
shared/node_modules/zod/lib/types.d.ts generated vendored Normal file

File diff suppressed because it is too large Load Diff

3252
shared/node_modules/zod/lib/types.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

105
shared/node_modules/zod/package.json generated vendored Normal file
View File

@@ -0,0 +1,105 @@
{
"name": "zod",
"version": "3.21.4",
"author": "Colin McDonnell <colin@colinhacks.com>",
"repository": {
"type": "git",
"url": "https://github.com/colinhacks/zod"
},
"main": "./lib/index.js",
"module": "./lib/index.mjs",
"devDependencies": {
"@rollup/plugin-typescript": "^8.2.0",
"@types/benchmark": "^2.1.0",
"@types/jest": "^29.2.2",
"@types/node": "14",
"@typescript-eslint/eslint-plugin": "^5.15.0",
"@typescript-eslint/parser": "^5.15.0",
"benchmark": "^2.1.4",
"dependency-cruiser": "^9.19.0",
"eslint": "^8.11.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-ban": "^1.6.0",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-simple-import-sort": "^7.0.0",
"eslint-plugin-unused-imports": "^2.0.0",
"husky": "^7.0.4",
"jest": "^29.3.1",
"lint-staged": "^12.3.7",
"nodemon": "^2.0.15",
"prettier": "^2.6.0",
"pretty-quick": "^3.1.3",
"rollup": "^2.70.1",
"ts-jest": "^29.0.3",
"ts-morph": "^14.0.0",
"ts-node": "^10.9.1",
"tslib": "^2.3.1",
"tsx": "^3.8.0",
"typescript": "~4.5.0"
},
"exports": {
".": {
"require": "./lib/index.js",
"import": "./lib/index.mjs",
"types": "./index.d.ts"
},
"./package.json": "./package.json",
"./locales/*": "./lib/locales/*"
},
"bugs": {
"url": "https://github.com/colinhacks/zod/issues"
},
"description": "TypeScript-first schema declaration and validation library with static type inference",
"files": [
"/lib",
"/index.d.ts"
],
"funding": "https://github.com/sponsors/colinhacks",
"homepage": "https://zod.dev",
"keywords": [
"typescript",
"schema",
"validation",
"type",
"inference"
],
"license": "MIT",
"lint-staged": {
"src/*.ts": [
"eslint --cache --fix",
"prettier --ignore-unknown --write"
]
},
"scripts": {
"prettier:check": "prettier --check src/**/*.ts deno/lib/**/*.ts --no-error-on-unmatched-pattern",
"prettier:fix": "prettier --write src/**/*.ts deno/lib/**/*.ts --ignore-unknown --no-error-on-unmatched-pattern",
"lint:check": "eslint --cache --ext .ts ./src",
"lint:fix": "eslint --cache --fix --ext .ts ./src",
"check": "yarn lint:check && yarn prettier:check",
"fix": "yarn lint:fix && yarn prettier:fix",
"clean": "rm -rf lib/* deno/lib/*",
"build": "yarn run clean && npm run build:cjs && npm run build:esm && npm run build:deno",
"build:deno": "node ./deno/build.mjs && cp ./README.md ./deno/lib",
"build:esm": "rollup --config rollup.config.js",
"build:cjs": "tsc -p tsconfig.cjs.json",
"build:types": "tsc -p tsconfig.types.json",
"build:test": "tsc -p tsconfig.test.json",
"rollup": "rollup --config rollup.config.js",
"test:watch": "jest --watch",
"test": "jest --coverage",
"test:deno": "cd deno && deno test",
"prepublishOnly": "npm run test && npm run build && npm run build:deno",
"play": "nodemon -e ts -w . -x tsx playground.ts",
"depcruise": "depcruise -c .dependency-cruiser.js src",
"benchmark": "tsx src/benchmarks/index.ts",
"prepare": "husky install"
},
"sideEffects": false,
"support": {
"backing": {
"npm-funding": true
}
},
"types": "./index.d.ts",
"dependencies": {}
}

28
shared/package-lock.json generated Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "photos-shared",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "photos-shared",
"dependencies": {
"zod": "^3.21.4"
}
},
"node_modules/zod": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
},
"dependencies": {
"zod": {
"version": "3.21.4",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.21.4.tgz",
"integrity": "sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw=="
}
}
}

6
shared/package.json Normal file
View File

@@ -0,0 +1,6 @@
{
"name": "photos-shared",
"dependencies": {
"zod": "^3.21.4"
}
}

View File

@@ -1,85 +1,124 @@
interface IAPIErrorResponse { import { z } from "zod";
data: null;
error: string; export const APIErrorResponse = z.object({
data: z.null(),
error: z.string(),
});
export type TAPIErrorResponse = z.infer<typeof APIErrorResponse>;
function CreateAPISuccessResponse<T extends z.ZodTypeAny>(obj: T) {
return z.object({
error: z.literal(false),
data: obj,
});
} }
interface IAPISuccessResponse<T> { function CreateAPIResponse<T extends z.ZodTypeAny>(obj: T) {
error: false; return z.union([APIErrorResponse, CreateAPISuccessResponse(obj)]);
data: T;
} }
export type IAPIResponse<T> = IAPIErrorResponse | IAPISuccessResponse<T>; export const PhotoJSON = z.object({
id: z.number(),
user: z.number(),
hash: z.string(),
size: z.string(),
format: z.string(),
createdAt: z.number(),
editedAt: z.number(),
shotAt: z.number(),
uploaded: z.boolean(),
});
export type TPhotoJSON = z.infer<typeof PhotoJSON>;
export interface IPhotoJSON { export const PhotoReqJSON = PhotoJSON.extend({
id: number; accessToken: z.string(),
user: number; });
hash: string; export type TPhotoReqJSON = z.infer<typeof PhotoReqJSON>;
size: string;
format: string;
createdAt: number;
editedAt: number;
shotAt: number;
uploaded: boolean;
}
export interface IPhotoReqJSON extends IPhotoJSON { export const PhotoShowToken = z.string();
accessToken: string; export type TPhotoShowToken = z.infer<typeof PhotoShowToken>;
}
export type IPhotoShowToken = string; export const PhotosGetShowTokenByIDRespBody = CreateAPIResponse(PhotoShowToken);
export type IPhotosGetShowTokenByID = IAPIResponse<IPhotoShowToken>; export type TPhotosGetShowTokenByIDRespBody = z.infer<
typeof PhotosGetShowTokenByIDRespBody
>;
export interface IPhotosNewPostBody { export const PhotosNewPostBody = z.object({
hash: string | undefined; hash: z.string(),
size: string | undefined; size: z.string(),
format: string | undefined; format: z.string(),
} });
export type TPhotosNewPostBody = z.infer<typeof PhotosNewPostBody>;
export interface IPhotosDeleteBody { export const PhotosDeleteBody = z.object({
photos: IPhotoReqJSON[]; photos: z.array(PhotoReqJSON),
} });
export type TPhotosDeleteBody = z.infer<typeof PhotosDeleteBody>;
export const IPhotosListPagination = 50; export const PhotosListPagination = 50;
export type IPhotosNewRespBody = IAPIResponse<IPhotoReqJSON>; export const PhotosNewRespBody = CreateAPIResponse(PhotoReqJSON);
export type IPhotosUploadRespBody = IAPIResponse<IPhotoReqJSON>; export type TPhotosNewRespBody = z.infer<typeof PhotosNewRespBody>;
export type IPhotosListRespBody = IAPIResponse<IPhotoReqJSON[]>;
export type IPhotosByIDGetRespBody = IAPIResponse<IPhotoReqJSON>;
export type IPhotoByIDDeleteRespBody = IAPIResponse<boolean>;
export type IPhotosDeleteRespBody = IAPIResponse<boolean>;
export interface IUserJSON { export const PhotosUploadRespBody = CreateAPIResponse(PhotoReqJSON);
id: number; export type TPhotosUploadRespBody = z.infer<typeof PhotosUploadRespBody>;
username: string;
isAdmin: boolean;
}
export interface IUserJWT extends IUserJSON { export const PhotosListRespBody = CreateAPIResponse(z.array(PhotoReqJSON));
ext: number; export type TPhotosListRespBody = z.infer<typeof PhotosListRespBody>;
iat: number;
}
export interface IUserAuthJSON extends IUserJSON { export const PhotosByIDGetRespBody = CreateAPIResponse(PhotoReqJSON);
jwt: string; export type TPhotosByIDGetRespBody = z.infer<typeof PhotosByIDGetRespBody>;
}
export interface IUserSignupBody { export const PhotoByIDDeleteRespBody = CreateAPIResponse(z.boolean());
username: string | undefined; export type TPhotoByIDDeleteRespBody = z.infer<typeof PhotoByIDDeleteRespBody>;
password: string | undefined;
email: string | undefined;
}
export type IUserSignupRespBody = IAPIResponse<IUserAuthJSON>; export const PhotosDeleteRespBody = CreateAPIResponse(z.boolean());
export type TPhotosDeleteRespBody = z.infer<typeof PhotosDeleteRespBody>;
export type IUserGetRespBody = IAPIResponse<IUserAuthJSON>; export const UserJSON = z.object({
export type IUserLoginRespBody = IAPIResponse<IUserAuthJSON>; id: z.number(),
username: z.string(),
isAdmin: z.boolean(),
});
export type TUserJSON = z.infer<typeof UserJSON>;
export interface IUserEditBody { export const UserJWT = UserJSON.extend({
password: string | undefined; ext: z.number(),
} iat: z.number(),
});
export type TUserJWT = z.infer<typeof UserJWT>;
export type IUserEditRespBody = IAPIResponse<IUserAuthJSON>; export const UserAuthJSON = UserJSON.extend({
export interface IUserLoginBody { jwt: z.string(),
username: string | undefined; });
password: string | undefined; export type TUserAuthJSON = z.infer<typeof UserAuthJSON>;
}
export const UserSignupBody = z.object({
username: z.string(),
password: z.string(),
email: z.string(),
});
export type TUserSignupBody = z.infer<typeof UserSignupBody>;
export const UserSignupRespBody = CreateAPIResponse(UserAuthJSON);
export type TUserSignupRespBody = z.infer<typeof UserSignupRespBody>;
export const UserGetRespBody = CreateAPIResponse(UserAuthJSON);
export type TUserGetRespBody = z.infer<typeof UserGetRespBody>;
export const UserLoginRespBody = CreateAPIResponse(UserAuthJSON);
export type TUserLoginRespBody = z.infer<typeof UserLoginRespBody>;
export const UserEditBody = z.object({
password: z.optional(z.string()),
});
export type TUserEditBody = z.infer<typeof UserEditBody>;
export const UserEditRespBody = CreateAPIResponse(UserAuthJSON);
export type TUserEditRespBody = z.infer<typeof UserEditRespBody>;
export const UserLoginBody = z.object({
username: z.string(),
password: z.string(),
});
export type TUserLoginBody = z.infer<typeof UserLoginBody>;