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

View File

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

View File

@@ -20,7 +20,6 @@
"class-validator": "^0.14.0",
"exifreader": "^4.13.0",
"hasha": "^5.2.2",
"io-ts": "^2.2.20",
"jsonwebtoken": "^9.0.1",
"koa": "^2.14.2",
"koa-body": "^5.0.0",
@@ -48,12 +47,12 @@
"@types/hasha": "^3.0.1",
"@types/jsonwebtoken": "^9.0.2",
"@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-send": "^4.1.3",
"@types/koa-sslify": "^4.0.3",
"@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/mocha": "^10.0.1",
"@types/mysql": "^2.15.21",
@@ -85,4 +84,4 @@
"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 { devRouter } from "~routes/dev";
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");

View File

@@ -2,7 +2,7 @@ import * as path from "path";
import * as fs from "fs/promises";
import * as mime from "mime-types";
import * as jwt from "jsonwebtoken";
import { IPhotoReqJSON, IPhotoJSON } from "~/shared/types";
import { TPhotoReqJSON, TPhotoJSON } from "~/shared/types";
import {
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)) {
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();
return { ...(await this.toJSON()), accessToken: token };
}

View File

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

View File

@@ -1,10 +1,13 @@
import * as Router from "@koa/router";
import { Photo } from "~entity/Photo";
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 User.remove(await User.find());
ctx.body = { success: true };

View File

@@ -1,19 +1,18 @@
import * as Router from "@koa/router";
import { Photo } from "~entity/Photo";
import {
IPhotoReqJSON,
IPhotosNewRespBody,
IPhotosNewPostBody,
IPhotoByIDDeleteRespBody,
IPhotosUploadRespBody,
IPhotosListRespBody,
IPhotosByIDGetRespBody,
IPhotosDeleteRespBody,
IPhotosDeleteBody,
IPhotosGetShowTokenByID,
IPhotoShowToken,
IAPIResponse,
IPhotosListPagination,
TPhotoReqJSON,
TPhotosNewRespBody,
TPhotoByIDDeleteRespBody,
TPhotosUploadRespBody,
TPhotosListRespBody,
TPhotosByIDGetRespBody,
TPhotosDeleteRespBody,
PhotosListPagination,
PhotosNewPostBody,
PhotoJSON,
TPhotosGetShowTokenByIDRespBody,
PhotosDeleteBody,
} from "~/shared/types";
import send = require("koa-send");
import { getHash, getSize } from "~util";
@@ -21,24 +20,25 @@ import * as jwt from "jsonwebtoken";
import { config } from "~config";
import { ValidationError } from "class-validator";
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) {
ctx.throw(401);
}
const { user } = ctx.state;
const body = ctx.request.body as IPhotosNewPostBody;
const body = PhotosNewPostBody.parse(ctx.request.body);
const { hash, size, format } = body;
if (!(hash && size && format)) {
ctx.throw(400);
return;
}
const photo = new Photo(user, hash, size, format);
const photo = Photo.create({ user, hash, size, format });
try {
await photo.save();
@@ -47,13 +47,12 @@ photosRouter.post("/photos/new", async (ctx) => {
const photo = await Photo.findOne({ hash, size, user });
if (!photo) {
ctx.throw(404);
return;
}
ctx.body = {
error: false,
data: await photo.toReqJSON(),
} as IPhotosNewRespBody;
} as TPhotosNewRespBody;
return;
}
if (
@@ -61,7 +60,6 @@ photosRouter.post("/photos/new", async (ctx) => {
(Array.isArray(e) && e.some((e) => e instanceof ValidationError))
) {
ctx.throw(400);
return;
}
console.log(e);
ctx.throw(500);
@@ -70,10 +68,10 @@ photosRouter.post("/photos/new", async (ctx) => {
ctx.body = {
error: false,
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) {
ctx.throw(401);
}
@@ -84,32 +82,28 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
if (!id) {
ctx.throw(400);
return;
}
const { user } = ctx.state;
const photo = await Photo.findOne({ id: parseInt(id), user });
if (!photo) {
ctx.throw(404);
return;
}
if (!ctx.request.files || Object.keys(ctx.request.files).length === 0) {
ctx.throw(400, "No file");
return;
}
if (photo.uploaded) {
ctx.throw(400, "Already uploaded");
return;
}
if (ctx.request.files) {
const files = ctx.request.files;
if (Object.keys(files).length > 1) {
ctx.throw(400, "Too many files");
return;
}
const file = Object.values(files)[0];
if (Array.isArray(file)) {
throw "more than one file uploaded";
@@ -120,7 +114,6 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
if (photoHash !== photo.hash || photoSize !== photo.size) {
ctx.throw(400, "Wrong photo");
return;
}
try {
@@ -134,14 +127,14 @@ photosRouter.post("/photos/upload/:id", async (ctx) => {
ctx.body = {
error: false,
data: await photo.toReqJSON(),
} as IPhotosUploadRespBody;
} as TPhotosUploadRespBody;
});
/**
export interface IPhotosByIDPatchBody {
export interface TPhotosByIDPatchBody {
}
export type IPhotosByIDPatchRespBody = IAPIResponse<IPhotoReqJSON>;
photosRouter.patch("/photos/byID/:id", async (ctx) => {
export type TPhotosByIDPatchRespBody = IAPIResponse<TPhotoReqJSON>;
photosRouter.patch("/photos/byID/:id", async (ctx: ContextType) => {
if (!ctx.state.user) {
ctx.throw(401);
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) {
ctx.throw(401);
}
@@ -200,8 +193,8 @@ photosRouter.get("/photos/list", async (ctx) => {
skip = parseInt(skip);
}
if (!num || num > IPhotosListPagination) {
num = IPhotosListPagination;
if (!num || num > PhotosListPagination) {
num = PhotosListPagination;
}
const photos = await Photo.find({
@@ -211,17 +204,17 @@ photosRouter.get("/photos/list", async (ctx) => {
order: { shotAt: "DESC" },
});
const photosList: IPhotoReqJSON[] = await Promise.all(
const photosList: TPhotoReqJSON[] = await Promise.all(
photos.map(async (photo) => await photo.toReqJSON()),
);
ctx.body = {
error: false,
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) {
ctx.throw(401);
}
@@ -232,7 +225,6 @@ photosRouter.get("/photos/byID/:id", async (ctx) => {
if (!id) {
ctx.throw(400);
return;
}
const { user } = ctx.state;
@@ -241,16 +233,15 @@ photosRouter.get("/photos/byID/:id", async (ctx) => {
if (!photo) {
ctx.throw(404);
return;
}
ctx.body = {
error: false,
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 {
id: string | undefined;
token: string | undefined;
@@ -258,7 +249,6 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
if (!(id && token)) {
ctx.throw(400);
return;
}
try {
@@ -267,7 +257,7 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
ctx.throw(401);
}
const photoReqJSON = jwt.decode(token) as IPhotoReqJSON;
const photoReqJSON = PhotoJSON.parse(jwt.decode(token));
const { user } = photoReqJSON;
const photo = await Photo.findOne({
@@ -277,7 +267,6 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
if (!photo) {
ctx.throw(404);
return;
}
if (
@@ -292,7 +281,7 @@ photosRouter.get("/photos/showByID/:id/:token", async (ctx) => {
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) {
ctx.throw(401);
}
@@ -303,7 +292,6 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => {
if (!id) {
ctx.throw(400);
return;
}
const { user } = ctx.state;
@@ -312,7 +300,6 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => {
if (!photo) {
ctx.throw(404);
return;
}
if (
@@ -327,7 +314,7 @@ photosRouter.get("/photos/showByID/:id", async (ctx) => {
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) {
ctx.throw(401);
}
@@ -338,7 +325,6 @@ photosRouter.get("/photos/getShowByIDToken/:id", async (ctx) => {
if (!id) {
ctx.throw(400);
return;
}
const { user } = ctx.state;
@@ -346,15 +332,14 @@ photosRouter.get("/photos/getShowByIDToken/:id", async (ctx) => {
const photo = await Photo.findOne({ id: parseInt(id), user });
if (!photo) {
ctx.throw(404);
return;
}
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) {
ctx.throw(401);
}
@@ -365,7 +350,6 @@ photosRouter.delete("/photos/byID/:id", async (ctx) => {
if (!id) {
ctx.throw(400);
return;
}
const { user } = ctx.state;
@@ -374,7 +358,6 @@ photosRouter.delete("/photos/byID/:id", async (ctx) => {
if (!photo) {
ctx.throw(404);
return;
}
await photo.remove();
@@ -382,22 +365,17 @@ photosRouter.delete("/photos/byID/:id", async (ctx) => {
ctx.body = {
error: false,
data: true,
} as IPhotoByIDDeleteRespBody;
} as TPhotoByIDDeleteRespBody;
});
photosRouter.post("/photos/delete", async (ctx) => {
photosRouter.post("/photos/delete", async (ctx: ContextType) => {
if (!ctx.state.user) {
ctx.throw(401);
}
const body = ctx.request.body as IPhotosDeleteBody;
const body = PhotosDeleteBody.parse(ctx.request.body);
const { photos } = body;
if (!photos || !Array.isArray(photos) || photos.length == 0) {
ctx.throw(400);
return;
}
const { user } = ctx.state;
try {
await Photo.delete({
@@ -408,11 +386,11 @@ photosRouter.post("/photos/delete", async (ctx) => {
ctx.body = {
error: false,
data: true,
} as IPhotosDeleteRespBody;
} as TPhotosDeleteRespBody;
} catch (e) {
ctx.body = {
data: null,
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 { User } from "~entity/User";
import {
IUserJWT,
IUserGetRespBody,
IUserEditRespBody,
IUserSignupBody,
IUserSignupRespBody,
IUserLoginRespBody,
IUserEditBody,
IUserLoginBody,
TUserJWT,
TUserGetRespBody,
TUserEditRespBody,
TUserSignupBody,
TUserSignupRespBody,
TUserLoginRespBody,
TUserEditBody,
TUserLoginBody,
UserLoginBody,
UserSignupBody,
UserEditBody,
} 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) {
ctx.throw(401);
}
const jwt = ctx.state.user as IUserJWT;
const jwt = ctx.state.user;
const user = await User.findOne(jwt.id);
if (!user) {
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;
if (!request.body) {
ctx.throw(400);
}
const { username, password } = request.body as IUserLoginBody;
if (!(username && password)) {
ctx.throw(400);
return;
}
const { username, password } = UserLoginBody.parse(request.body);
const user = await User.findOne({ username });
if (!user || !(await user.verifyPassword(password))) {
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;
if (!request.body) {
ctx.throw(400);
}
const { username, password, email } = request.body as IUserSignupBody;
if (!(username && password && email)) {
ctx.throw(400);
return;
}
const { username, password, email } = UserSignupBody.parse(request.body);
const user = new User(username, email);
const users = await User.find();
@@ -91,33 +86,30 @@ userRouter.post("/users/signup", async (ctx) => {
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) {
ctx.throw(401);
}
const jwt = ctx.state.user as IUserJWT;
const jwt = ctx.state.user;
const user = await User.findOne(jwt.id);
const request = ctx.request;
if (!user) {
ctx.throw(401);
return;
}
if (!request.body) {
ctx.throw(400);
return;
}
const { password } = request.body as IUserEditBody;
const { password } = UserEditBody.parse(request.body);
if (!password) {
ctx.throw(400);
return;
}
await user.setPassword(password);
@@ -129,5 +121,5 @@ userRouter.post("/users/edit", async (ctx) => {
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 { app } from "~app";
import { Photo } from "~entity/Photo";
import { IPhotoReqJSON ,
IPhotosDeleteBody,
IPhotosListRespBody,
IPhotosNewPostBody,
import {
TPhotoReqJSON,
TPhotosDeleteBody,
TPhotosListRespBody,
TPhotosNewPostBody,
} from "~shared/types";
import * as fs from "fs/promises";
import { constants as fsConstants } from "fs";
@@ -60,7 +61,7 @@ describe("photos", function () {
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();
@@ -85,9 +86,7 @@ describe("photos", function () {
Authorization: `Bearer ${seed.user2.toJWT()}`,
})
.expect(200);
expect(parseInt(response.header["content-length"])).to.equal(
dogFileSize,
);
expect(parseInt(response.get("content-length"))).to.equal(dogFileSize);
});
it("should delete a photo after file has been deleted", async function () {
@@ -97,12 +96,10 @@ describe("photos", function () {
Authorization: `Bearer ${seed.user2.toJWT()}`,
})
.expect(200);
expect(parseInt(response.header["content-length"])).to.equal(
dogFileSize,
);
expect(parseInt(response.get("content-length"))).to.equal(dogFileSize);
await fs.unlink(await seed.dogPhoto.getReadyPath("original"));
const response2 = await request(callback)
await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}`)
.set({
Authorization: `Bearer ${seed.user2.toJWT()}`,
@@ -122,13 +119,13 @@ describe("photos", function () {
const dogSmallThumbSize = (
await fs.stat(seed.dogPhoto.getThumbPath("512"))
).size;
expect(parseInt(response.header["content-length"])).to.equal(
expect(parseInt(response.get("content-length"))).to.equal(
dogSmallThumbSize,
);
await fs.unlink(await seed.dogPhoto.getReadyPath("512"));
await fs.unlink(await seed.dogPhoto.getReadyPath("original"));
const response2 = await request(callback)
await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}?size=512`)
.set({
Authorization: `Bearer ${seed.user2.toJWT()}`,
@@ -146,7 +143,7 @@ describe("photos", function () {
Authorization: `Bearer ${seed.user2.toJWT()}`,
})
.expect(200);
expect(parseInt(response.header["content-length"])).to.be.lessThan(
expect(parseInt(response.get("content-length"))).to.be.lessThan(
dogFileSize,
);
});
@@ -161,12 +158,12 @@ describe("photos", function () {
const dogSmallThumbSize = (
await fs.stat(seed.dogPhoto.getThumbPath("512"))
).size;
expect(parseInt(response.header["content-length"])).to.equal(
expect(parseInt(response.get("content-length"))).to.equal(
dogSmallThumbSize,
);
await fs.unlink(seed.dogPhoto.getThumbPath("512"));
const response2 = await request(callback)
await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}?size=512`)
.set({
Authorization: `Bearer ${seed.user2.toJWT()}`,
@@ -175,7 +172,7 @@ describe("photos", function () {
const dogSmallThumbSize2 = (
await fs.stat(seed.dogPhoto.getThumbPath("512"))
).size;
expect(parseInt(response.header["content-length"])).to.equal(
expect(parseInt(response.get("content-length"))).to.equal(
dogSmallThumbSize2,
);
});
@@ -188,7 +185,7 @@ describe("photos", function () {
})
.expect(200);
const listRespBody = listResp.body as IPhotosListRespBody;
const listRespBody = listResp.body as TPhotosListRespBody;
if (listRespBody.error !== false) {
expect(listResp.body.error).to.be.false;
@@ -201,7 +198,7 @@ describe("photos", function () {
const listAnyResp = await request(callback)
.get(`/photos/showByID/${photos[0].id}/${photos[0].accessToken}`)
.expect(200);
expect(parseInt(listAnyResp.header["content-length"])).to.be.oneOf([
expect(parseInt(listAnyResp.get("content-length"))).to.be.oneOf([
dogFileSize,
catFileSize,
]);
@@ -219,9 +216,7 @@ describe("photos", function () {
const response = await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}/${token}`)
.expect(200);
expect(parseInt(response.header["content-length"])).to.equal(
dogFileSize,
);
expect(parseInt(response.get("content-length"))).to.equal(dogFileSize);
const tokenSelfSigned = jwt.sign(
await seed.dogPhoto.toReqJSON(),
@@ -234,7 +229,7 @@ describe("photos", function () {
const responseSS = await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}/${tokenSelfSigned}`)
.expect(200);
expect(parseInt(responseSS.header["content-length"])).to.equal(
expect(parseInt(responseSS.get("content-length"))).to.equal(
dogFileSize,
);
});
@@ -248,7 +243,7 @@ describe("photos", function () {
},
);
const response = await request(callback)
await request(callback)
.get(`/photos/showByID/${seed.dogPhoto.id}/${token}`)
.expect(401);
});
@@ -275,17 +270,17 @@ describe("photos", function () {
hash: dogHash,
size: dogSize,
format: dogFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(200);
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);
const dbPhoto = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
user: { id: seed.user1.id },
});
expect(dbPhoto.hash).to.be.equal(dogHash);
@@ -302,7 +297,7 @@ describe("photos", function () {
const dbPhotoUpl = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
user: { id: seed.user1.id },
});
expect(dbPhotoUpl.hash).to.be.equal(dogHash);
expect(await dbPhotoUpl.origFileExists()).to.be.equal(true);
@@ -317,9 +312,7 @@ describe("photos", function () {
})
.expect(200);
expect(parseInt(showResp.header["content-length"])).to.equal(
dogFileSize,
);
expect(parseInt(showResp.get("content-length"))).to.equal(dogFileSize);
});
it("should create, upload and show a png file", async function () {
@@ -333,17 +326,17 @@ describe("photos", function () {
hash: pngHash,
size: pngSize,
format: pngFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(200);
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);
const dbPhoto = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
user: { id: seed.user1.id },
});
expect(dbPhoto.hash).to.be.equal(pngHash);
@@ -360,7 +353,7 @@ describe("photos", function () {
const dbPhotoUpl = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
user: { id: seed.user1.id },
});
expect(dbPhotoUpl.hash).to.be.equal(pngHash);
expect(dbPhotoUpl.format).to.be.equal(pngFormat);
@@ -377,9 +370,7 @@ describe("photos", function () {
})
.expect(200);
expect(parseInt(showResp.header["content-length"])).to.equal(
pngFileSize,
);
expect(parseInt(showResp.get("content-length"))).to.equal(pngFileSize);
});
it("should not create a photo twice", async function () {
@@ -393,12 +384,12 @@ describe("photos", function () {
hash: dogHash,
size: dogSize,
format: dogFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(200);
expect(response.body.error).to.be.false;
const response2 = await request(callback)
await request(callback)
.post("/photos/new")
.set({
Authorization: `Bearer ${seed.user1.toJWT()}`,
@@ -408,7 +399,7 @@ describe("photos", function () {
hash: dogHash,
size: dogSize,
format: dogFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(200);
const dbPhoto = await Photo.find({
@@ -430,17 +421,17 @@ describe("photos", function () {
hash: dogHash,
size: dogSize,
format: dogFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(200);
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);
const dbPhoto = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
user: { id: seed.user1.id },
});
expect(dbPhoto.hash).to.be.equal(dogHash);
@@ -473,9 +464,7 @@ describe("photos", function () {
})
.expect(200);
expect(parseInt(showResp.header["content-length"])).to.equal(
dogFileSize,
);
expect(parseInt(showResp.get("content-length"))).to.equal(dogFileSize);
});
it("should not upload a wrong photo", async function () {
@@ -489,17 +478,17 @@ describe("photos", function () {
hash: dogHash,
size: dogSize,
format: dogFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(200);
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);
const dbPhoto = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
user: { id: seed.user1.id },
});
expect(dbPhoto.hash).to.be.equal(dogHash);
@@ -516,7 +505,7 @@ describe("photos", function () {
expect(await dbPhoto.origFileExists()).to.be.equal(false);
const showResp = await request(callback)
await request(callback)
.get(`/photos/showByID/${photo.id}`)
.set({
Authorization: `Bearer ${seed.user1.toJWT()}`,
@@ -535,17 +524,17 @@ describe("photos", function () {
hash: dogHash,
size: dogSize,
format: dogFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(200);
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);
const dbPhoto = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
user: { id: seed.user1.id },
});
expect(dbPhoto.hash).to.be.equal(dogHash);
expect(await dbPhoto.origFileExists()).to.be.equal(false);
@@ -573,17 +562,17 @@ describe("photos", function () {
hash: dogHash,
size: dogSize,
format: dogFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(200);
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);
const dbPhoto = await Photo.findOneOrFail({
id: photo.id,
user: seed.user1.id as any,
user: { id: seed.user1.id },
});
expect(dbPhoto.hash).to.be.equal(dogHash);
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 () {
const response = await request(callback)
await request(callback)
.post("/photos/new")
.set({
Authorization: `Bearer ${seed.user1.toJWT()}`,
@@ -618,7 +607,7 @@ describe("photos", function () {
hash: "../test",
size: "33333",
format: dogFormat,
} as IPhotosNewPostBody)
} as TPhotosNewPostBody)
.expect(400);
});
@@ -635,13 +624,13 @@ describe("photos", function () {
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");
const dbPhoto = await Photo.findOne({
id: seed.dogPhoto.id,
user: seed.user1.id as any,
user: {id:seed.user1.id} ,
});
expect(dbPhoto.name).to.be.equal("Test1");
@@ -662,7 +651,7 @@ describe("photos", function () {
expect(response.body.error).to.be.false;
const photos = response.body.data as IPhotoReqJSON[];
const photos = response.body.data as TPhotoReqJSON[];
const userPhotos = [
await seed.dogPhoto.toReqJSON(),
await seed.catPhoto.toReqJSON(),
@@ -685,7 +674,7 @@ describe("photos", function () {
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();
@@ -704,7 +693,7 @@ describe("photos", function () {
})
.send({
photos: [await seed.dogPhoto.toReqJSON()],
} as IPhotosDeleteBody)
} as TPhotosDeleteBody)
.expect(200);
expect(response.body.error).to.be.false;
@@ -736,7 +725,7 @@ describe("photos", function () {
await seed.dogPhoto.toReqJSON(),
await seed.catPhoto.toReqJSON(),
],
} as IPhotosDeleteBody)
} as TPhotosDeleteBody)
.expect(200);
expect(response.body.error).to.be.false;

View File

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

View File

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