Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
chanwit-y committed Nov 4, 2024
2 parents 6a575ff + 516de4b commit c866d45
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 74 deletions.
8 changes: 5 additions & 3 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
{
"imports": {
"@supabase/supabase-js": "jsr:@supabase/supabase-js@^2.45.6",
"hono": "npm:hono@^4.6.7",
"hono": "npm:hono@^4.6.9",
"inversify": "npm:inversify@^6.0.3",
"openai": "npm:openai@^4.68.4",
"reflect-metadata": "npm:reflect-metadata@^0.2.2"
"reflect-metadata": "npm:reflect-metadata@^0.2.2",
"zod": "npm:zod@^3.23.8",
"zod-validation-error": "npm:zod-validation-error@^3.4.0"
},
"tasks": {
"start": "deno run --allow-env --allow-net main.ts",
Expand All @@ -13,6 +15,6 @@
"compilerOptions": {
"jsx": "precompile",
"jsxImportSource": "hono/jsx",
"experimentalDecorators": true
"experimentalDecorators": true
}
}
25 changes: 19 additions & 6 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 10 additions & 3 deletions libs/config/container.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { IUserRepository } from "./../mod/auth/repository.ts";
import { Container } from "inversify";
import {
VocabularyRepository,
type IVocabularyRepository,
} from "../repository/vocabulary.repo.ts";
} from "../mod/vocabulary/repository.ts";
import { OpenAIAPI } from "../api/openai.api.ts";
import {
VocabularyService,
type IVocabularyService,
} from "../service/vocabulary.service.ts";
} from "../mod/vocabulary/service.ts";
import { UserRepository } from "../mod/auth/repository.ts";
import { UserService, type IUserService } from "../mod/auth/service.ts";

enum Instances {
OpenAIAPI = "OpenAIAPI",
VocabularyRepository = "VocabularyRepository",
VocabularyService = "VocabularyService",
UserRepository = "UserRepository",
UserService = "UserService",
}

const container = new Container();
Expand All @@ -23,5 +28,7 @@ container.bind(Instances.OpenAIAPI).toConstantValue(new OpenAIAPI());
container
.bind<IVocabularyService>(Instances.VocabularyService)
.to(VocabularyService);
container.bind<IUserRepository>(Instances.UserRepository).to(UserRepository);
container.bind<IUserService>(Instances.UserService).to(UserService);

export {Instances, container}
export { Instances, container };
21 changes: 21 additions & 0 deletions libs/middleware/auth.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { IUserService } from "./../mod/auth/service.ts";
import { MiddlewareHandler } from "hono";
import { getCookie } from "hono/cookie";
import { container, Instances } from "../config/container.ts";

const authMiddleware: MiddlewareHandler = async (c, next) => {
const accessToken = getCookie(c, "access_token");
if (accessToken) {
const srv = container.get<IUserService>(Instances.UserService);
const { data, error } = await srv.getUser(accessToken);
if (data.user) {
c.set("user", { ...data });
}
if (error) {
return c.json({ error: error.message }, 401);
}
}
await next();
};

export default authMiddleware;
90 changes: 90 additions & 0 deletions libs/middleware/zodValidator.middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { Context, Env, MiddlewareHandler, TypedResponse } from "hono";
import type { ZodError, ZodSchema } from "zod";

import { ValidationTargets } from "hono";
import { fromZodError } from "zod-validation-error";

import { validator } from "hono/validator";
import { z } from "zod";

export type Hook<
T,
E extends Env,
P extends string,
O = Record<string | number | symbol, never>
> = (
result: { success: boolean; data: T; error?: ZodError },
// result:
// | { success: true; data: T }
// | { success: false; error: ZodError; data: T },
c: Context<E, P>
) =>
| Response
| Promise<Response>
| void
| Promise<Response | void | TypedResponse<O>>;

type HasUndefined<T> = T extends undefined ? true : false;

export const zValidator = <
T extends ZodSchema,
Target extends keyof ValidationTargets,
E extends Env,
P extends string,
I = z.input<T>,
O = z.output<T>,
V extends {
in: {
[K in Target]: K extends "json"
? I
: {
[x: string]: ValidationTargets[K][string];
};
};
out: { [K in Target]: O };
} = {
in: {
[K in Target]: K extends "json"
? I
: {
[x: string]: ValidationTargets[K][string];
};
};
out: { [K in Target]: O };
}
>(
target: Target,
schema: T,
hook?: Hook<z.infer<T>, E, P>
): MiddlewareHandler<E, P, V> =>
validator(target, async (v, c) => {
const { success, data, error } = await schema.safeParseAsync(v);
if (hook) {
const hookResult = hook({ success, data, error }, c);
if (hookResult) {
if (
(hookResult && hookResult instanceof Response) ||
hookResult instanceof Promise
) {
return hookResult;
}
if ("response" in hookResult) {
return hookResult["response"];
}

if (!success) {
const validationError = fromZodError(error);

return c.json(
{
message: validationError.message,
errors: validationError.details,
},
400
);
}
}
}

return data;
});
32 changes: 32 additions & 0 deletions libs/mod/auth/repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { AuthTokenResponsePassword, SupabaseClient, UserResponse } from "@supabase/supabase-js";

import { injectable } from "inversify";
import { supabase } from "../../db/index.ts";

export interface IUserRepository {
// auth(): Promise<any>;
signIn(username: string, password: string): Promise<AuthTokenResponsePassword>;
getUser(token: string): Promise<UserResponse>;
}

@injectable()
export class UserRepository implements IUserRepository {
private _db: SupabaseClient = supabase;

constructor() {}

// public async auth() {
// return;
// }

public async signIn(username: string, password: string) {
return await this._db.auth.signInWithPassword({
email: username,
password,
});
}

public async getUser(token: string) {
return await this._db.auth.getUser(token);
}
}
31 changes: 31 additions & 0 deletions libs/mod/auth/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { IUserService } from "./service.ts";
import { Hono } from "hono";
import { setCookie } from "hono/cookie";
import { zValidator } from "../../middleware/zodValidator.middleware.ts";
import { z } from "zod";
import { container, Instances } from "../../config/container.ts";

const authRoutes = new Hono().post(
"/sign-in",
zValidator(
"json",
z.object({
email: z.string(),
password: z.string(),
})
),
async (c) => {
const { email, password } = await c.req.valid("json");
const srv = container.get<IUserService>(Instances.UserService);
const { data, error } = await srv.signIn(email, password);

if (error) {
return c.json({ error: error.message }, 500);
}
setCookie(c, "access_token", data.session.access_token);

return c.json(data);
}
);

export default authRoutes;
40 changes: 40 additions & 0 deletions libs/mod/auth/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { AuthTokenResponsePassword, UserResponse } from "@supabase/supabase-js";
import { IUserRepository } from './repository.ts';
import { inject, injectable } from "inversify";

type User = {
username: string;
password: string;
};

export interface IUserService {
signIn(username: string, password: string): Promise<AuthTokenResponsePassword>;
getUser(token: string): Promise<UserResponse>;
}

@injectable()
export class UserService implements IUserService {

private _repo: IUserRepository;

constructor(@inject("UserRepository") repo: IUserRepository) {
this._repo = repo;
}

public async signIn(username: string, password: string) {
return await this._repo.signIn(username, password);
}

public async getUser(token: string) {
return await this._repo.getUser(token);
}

// public async insert(u: User) {
// const { data, error } = await this._db.from("users").insert([u]);
// if (error) {
// console.error(error);
// throw new Error(`Failed to insert user: ${error.message}`);
// }
// return data;
// }
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SupabaseClient } from "@supabase/supabase-js";
import { injectable } from "inversify";
import { Env } from "../config/index.ts";
import { supabase } from "../db/index.ts";
import { Env } from "../../config/index.ts";
import { supabase } from "../../db/index.ts";

import "reflect-metadata";

Expand All @@ -15,6 +15,7 @@ export interface IVocabularyRepository {
findAll(): Promise<unknown>;
findByWord(word: string): Promise<any[]>;
insert(v: TVocabulary): Promise<unknown>;

}

const TableName = "vocabulary";
Expand Down Expand Up @@ -44,13 +45,13 @@ export class VocabularyRepository implements IVocabularyRepository {
}
return data;
}

public async insert(v: TVocabulary) {
const auth = await this._db.auth.signInWithPassword({
email: Env.supabaseUser!,
password: Env.supabasePass!,
});
console.log("auth", auth);
// const auth = await this._db.auth.signInWithPassword({
// email: Env.supabaseUser!,
// password: Env.supabasePass!,
// });

// console.log("auth", auth);
const { data, error } = await this._db.from(TableName).insert([{ ...v }]);
if (error) {
console.error(error);
Expand Down
15 changes: 15 additions & 0 deletions libs/mod/vocabulary/routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Hono } from "hono";
import { container, Instances } from "../../config/container.ts";
import type { IVocabularyService } from "./service.ts";
import authMiddleware from "../../middleware/auth.middleware.ts";

const vocabularyRoutes = new Hono()
.use("*", authMiddleware)
.post("/create", async (c) => {
const body = await c.req.json<{ word: string }>();
const srv = container.get<IVocabularyService>(Instances.VocabularyService);
const res = await srv.insert(body.word);
return c.json(res);
});

export default vocabularyRoutes;
Loading

0 comments on commit c866d45

Please sign in to comment.