Skip to content

Commit

Permalink
simple update user api
Browse files Browse the repository at this point in the history
  • Loading branch information
thaian1234 committed Aug 22, 2024
1 parent 8ee7b7b commit 19707bb
Show file tree
Hide file tree
Showing 22 changed files with 865 additions and 92 deletions.
Binary file modified bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"db:introspect": "drizzle-kit introspect"
},
"dependencies": {
"@hono/zod-validator": "^0.2.2",
"@hookform/resolvers": "^3.9.0",
"@node-rs/argon2": "^1.8.3",
"@radix-ui/react-label": "^2.1.0",
Expand Down
8 changes: 8 additions & 0 deletions server/api/controllers/types.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Utils } from "../lib/helpers/utils";
import { Env } from "../lib/types/factory.type";
import { Hono } from "hono";
import { Schema } from "zod";

export interface IController {
setupHandlers(): unknown;
}
66 changes: 66 additions & 0 deletions server/api/controllers/user.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ApiResponse } from "../lib/helpers/api-response";
import { Utils } from "../lib/helpers/utils";
import { CreateFactoryType } from "../lib/types/factory.type";
import { HttpStatus } from "../lib/types/http.type";
import { UserValidation } from "../lib/validations/schema.validation";
import { Validator } from "../lib/validations/validator";
import { IUserService, UserService } from "../services/user.service";
import { zValidator } from "@hono/zod-validator";
import { z } from "zod";

import { IController } from "./types.controller";

export interface IUserController extends IController {
setupHandlers(): Utils.MethodReturnType<UserController, "setupHandlers">;
}

export class UserController {
private factory: CreateFactoryType;
private userService: IUserService;
constructor(factory: CreateFactoryType) {
this.factory = factory;
this.userService = new UserService();
}
setupHandlers() {
return this.factory
.createApp()
.patch("/:id", ...this.updateUserHandler());
}
private updateUserHandler() {
const params = z.object({
id: z.string().uuid(),
});
const response = UserValidation.selectSchema.omit({
hasedPassword: true,
});
return this.factory.createHandlers(
zValidator(
"json",
UserValidation.updateSchema,
Validator.handleParseError,
),
zValidator("param", params, Validator.handleParseError),
async (c) => {
const jsonData = c.req.valid("json");
const { id } = c.req.valid("param");
const updatedUser = await this.userService.updateUser(
id,
jsonData,
);
const resp = response.safeParse(updatedUser);
if (!resp.success) {
return ApiResponse.WriteJSON({
c,
msg: "Failed to parse",
status: HttpStatus.BadGateway,
});
}
return ApiResponse.WriteJSON({
c,
data: resp.data,
status: HttpStatus.OK,
});
},
);
}
}
7 changes: 4 additions & 3 deletions server/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { Hono } from "hono";
import { csrf } from "hono/csrf";
import { logger } from "hono/logger";

import { errorHandler } from "./lib/helpers/errors";
import type { Env } from "./lib/types/factory.type";
import { Validator } from "./lib/validations/validator";
import { userRoutes } from "./routes/user.routes";

const app = new Hono<Env>().basePath("/api");

Expand All @@ -12,14 +13,14 @@ app.use(logger());
app.use("*", csrf());

// Error
app.onError(errorHandler);
app.onError(Validator.handleErrorException);
app.notFound((c) => {
// TODO: Handle not found
return c.text("Api not found");
});

// Setup routes
const routes = app;
const routes = app.route("/", userRoutes);

type AppType = typeof routes;

Expand Down
31 changes: 0 additions & 31 deletions server/api/lib/helpers/errors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import { HttpStatus } from "../types/http.type";
import { Context } from "hono";
import { HTTPResponseError } from "hono/types";
import { StatusCode } from "hono/utils/http-status";

import { ApiResponse } from "./api-response";

class AppError extends Error {
constructor(
public statusCode: StatusCode,
Expand Down Expand Up @@ -67,30 +63,3 @@ export class MyError {
HttpStatus.TooManyRequests,
);
}

export const errorHandler = (err: Error | HTTPResponseError, c: Context) => {
switch (true) {
case err instanceof MyError.UnauthenticatedError:
case err instanceof MyError.UnauthorizedError:
case err instanceof MyError.NotFoundError:
case err instanceof MyError.ValidationError:
case err instanceof MyError.ConflictError:
case err instanceof MyError.BadGatewayError:
case err instanceof MyError.InternalServerError:
case err instanceof MyError.TooManyRequestsError:
case err instanceof MyError.ServiceUnavailableError:
return ApiResponse.WriteJSON({
c,
status: err.statusCode,
errors: err.message,
});

default:
console.error("Error failed to handle: ", err);
return ApiResponse.WriteJSON({
c,
status: HttpStatus.InternalServerError,
errors: err.message,
});
}
};
70 changes: 23 additions & 47 deletions server/api/lib/helpers/utils.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,30 @@
import { HttpStatus } from "../types/http.type";
import { hash, verify } from "@node-rs/argon2";
import { Context } from "hono";
import { ZodError } from "zod";

import { ApiResponse } from "./api-response";
export namespace Utils {
export type MethodReturnType<T, K extends keyof T> = T[K] extends (
...args: any[]
) => any
? ReturnType<T[K]>
: never;

export type MethodReturnType<T, K extends keyof T> = T[K] extends (
...args: any[]
) => any
? ReturnType<T[K]>
: never;

export class PasswordUtils {
public static async verifyHash(hash: string, password: string) {
const success = await verify(hash, password, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
});
return success;
}
public static async hashPassword(password: string) {
const passwordHash = await hash(password, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
});
return passwordHash;
}
}

type ResultType<T> = {
success: boolean;
data: T;
error?: ZodError;
};

export class Validator {
public static validatorHandler<T>(result: ResultType<T>, c: Context) {
if (!result.success) {
const zodErr = result.error
? result?.error.flatten().fieldErrors
: "Something went wrong";
return ApiResponse.WriteJSON({
c,
status: HttpStatus.BadRequest,
errors: zodErr,
export class PasswordUtils {
public static async verifyHash(hash: string, password: string) {
const success = await verify(hash, password, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
});
return success;
}
public static async hashPassword(password: string) {
const passwordHash = await hash(password, {
memoryCost: 19456,
timeCost: 2,
outputLen: 32,
parallelism: 1,
});
return passwordHash;
}
}
}
15 changes: 12 additions & 3 deletions server/api/lib/validations/schema.validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,27 @@ export class UserValidation {
email: z.string().email(),
imageUrl: z.string().url(),
username: z.string().min(4).max(20),
}).omit({
createdAt: true,
updatedAt: true,
});
public static selectSchema = this.baseSchema;
public static insertSchema = createInsertSchema(
tableSchemas.userTable,
).merge(this.baseSchema);
public static updateSchema = this.baseSchema.partial().required({
public static updateSchema = this.baseSchema.partial().omit({
id: true,
});
public static deleteSchema = this.baseSchema.pick({
id: true,
});
}
export namespace UserValidation {
export type Insert = z.infer<typeof UserValidation.insertSchema>;
export type Update = z.infer<typeof UserValidation.updateSchema>;
export type Select = z.infer<typeof UserValidation.selectSchema>;
export type Delete = z.infer<typeof UserValidation.deleteSchema>;
}

export class FollowValidation {
private static baseSchema = createSelectSchema(tableSchemas.followTable);
Expand All @@ -31,7 +40,7 @@ export class FollowValidation {
id: true,
});
}

// TODO: Add FollowTypes
export class BlockValidation {
private static baseSchema = createSelectSchema(tableSchemas.blockTable);
public static selectSchema = this.baseSchema;
Expand All @@ -42,4 +51,4 @@ export class BlockValidation {
id: true,
});
}

// TODO: Add BlockTypes
56 changes: 56 additions & 0 deletions server/api/lib/validations/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { ApiResponse } from "../helpers/api-response";
import { MyError } from "../helpers/errors";
import { HttpStatus } from "../types/http.type";
import { Context } from "hono";
import { HTTPResponseError } from "hono/types";
import { ZodError } from "zod";

type ResultType<T> = {
success: boolean;
data: T;
error?: ZodError;
};

export class Validator {
public static handleParseError<T>(result: ResultType<T>, c: Context) {
if (!result.success) {
const zodErr = result.error
? result?.error.flatten().fieldErrors
: "Something went wrong";
return ApiResponse.WriteJSON({
c,
status: HttpStatus.BadRequest,
errors: zodErr,
});
}
}
public static handleErrorException(
err: Error | HTTPResponseError,
c: Context,
) {
switch (true) {
case err instanceof MyError.UnauthenticatedError:
case err instanceof MyError.UnauthorizedError:
case err instanceof MyError.NotFoundError:
case err instanceof MyError.ValidationError:
case err instanceof MyError.ConflictError:
case err instanceof MyError.BadGatewayError:
case err instanceof MyError.InternalServerError:
case err instanceof MyError.TooManyRequestsError:
case err instanceof MyError.ServiceUnavailableError:
case err instanceof MyError.GatewayTimeoutError:
return ApiResponse.WriteJSON({
c,
status: err.statusCode,
errors: err.message,
});
default:
console.error("Failed to handle error: ", err);
return ApiResponse.WriteJSON({
c,
status: HttpStatus.InternalServerError,
errors: err.message,
});
}
}
}
9 changes: 9 additions & 0 deletions server/api/repositories/types.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export interface IWriter<Insert, Update, Select, ID = string> {
create(data: Insert): Promise<Select | undefined>;
update(id: ID, data: Update): Promise<Select | undefined>;
delete(id: ID): Promise<boolean | undefined>;
}
export interface IReader<Select, ID = string> {
findById(id: ID): Promise<Select | undefined>;
findAll(): Promise<Select[]>;
}
Loading

0 comments on commit 19707bb

Please sign in to comment.