Skip to content

Commit

Permalink
Merge pull request #2 from pineylilly/feature/user-routes
Browse files Browse the repository at this point in the history
Implement auth controller and service
  • Loading branch information
pineylilly authored Sep 11, 2024
2 parents 371de60 + e48bf7c commit da24745
Show file tree
Hide file tree
Showing 13 changed files with 294 additions and 3 deletions.
Binary file modified bun.lockb
Binary file not shown.
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@
"dev": "bun run --watch src/index.ts"
},
"dependencies": {
"elysia": "latest"
"@elysiajs/cors": "^1.1.1",
"@elysiajs/jwt": "^1.1.1",
"@prisma/client": "5.19.1",
"bcrypt": "^5.1.1",
"elysia": "latest",
"prisma": "^5.19.1"
},
"devDependencies": {
"@types/bcrypt": "^5.0.2",
"bun-types": "latest"
},
"module": "src/index.js"
}
}
17 changes: 17 additions & 0 deletions prisma/migrations/20240909031529_init/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('user', 'admin');

-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"displayName" TEXT NOT NULL,
"role" "Role" NOT NULL DEFAULT 'user',
"avatar" TEXT,

CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
3 changes: 3 additions & 0 deletions prisma/migrations/migration_lock.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"
30 changes: 30 additions & 0 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model User {
id String @id @default(uuid())
email String @unique
password String
displayName String
role Role @default(user)
avatar String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt @default(now())
}

enum Role {
user
admin
}
67 changes: 67 additions & 0 deletions src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import Elysia from "elysia";
import { LoginBody, RegisterBody } from "../dto/auth.dto";
import AuthService from "../services/auth.service";
import { BadRequestError } from "../utils/error";
import jwt from "@elysiajs/jwt";
import { jwtParams } from "../utils/jwtParams";

const authService = new AuthService();

export const authController = new Elysia({ prefix: '/auth' })
.use(
jwt({
name: 'jwt',
secret: process.env.JWT_SECRET!
})
)

// POST /auth/register
.post("/register", async ({ jwt, cookie: { auth }, body }) => {
// Register an user
const user = await authService.register(body)
if (!user) {
throw new BadRequestError("Failed to register user")
}

// Add JWT token to user
auth.set({
value: await jwt.sign(jwtParams(user)),
httpOnly: true,
maxAge: 7 * 86400,
path: '/',
})

return user
}, {
body: RegisterBody
})

// POST /auth/login
.post("/login", async ({ jwt, cookie: { auth }, body }) => {
const user = await authService.login(body)

if (!user) {
throw new BadRequestError("Invalid credentials")
}

// Add JWT token to user
auth.set({
value: await jwt.sign(jwtParams(user)),
httpOnly: true,
maxAge: 7 * 86400,
path: '/',
})

return user
}, {
body: LoginBody
})

// POST /auth/logout
.post("/logout", async ({ cookie: { auth }}) => {
auth.remove()

return {
message: "Logged out"
}
})
3 changes: 3 additions & 0 deletions src/controllers/user.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import Elysia from "elysia";

export const workspaceController = new Elysia({ prefix: '/users' })
33 changes: 33 additions & 0 deletions src/dto/auth.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Static, t } from "elysia";

export const RegisterBody = t.Object({
email: t.String({
format: "email",
minLength: 1,
error: "Email is required",
}),
password: t.String({
minLength: 8,
error: "Password must be at least 8 characters",
}),
displayName: t.String({
minLength: 1,
error: "Display name is required",
}),
})

export type RegisterInput = Static<typeof RegisterBody>

export const LoginBody = t.Object({
email: t.String({
format: "email",
minLength: 1,
error: "Email is required",
}),
password: t.String({
minLength: 8,
error: "Password must be at least 8 characters",
}),
})

export type LoginInput = Static<typeof LoginBody>
44 changes: 43 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,48 @@
import { Elysia } from "elysia";
import cors from "@elysiajs/cors";

const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
import { authController } from "./controllers/auth.controller";
import { BadRequestError } from "./utils/error";
import jwt from "@elysiajs/jwt";

const PORT = 3000;

const app = new Elysia()
.use(cors())
.error({
"BAD_REQUEST": BadRequestError
})
.onError(({ code, error, set }) => {
if (code === "NOT_FOUND") {
set.status = 404;
return {
error: "Not Found 🦊",
};
}
if (code === "VALIDATION") {
set.status = 400;
return {
error: "Bad Request 🦊",
message: error.message,
};
}
if (code === "BAD_REQUEST") {
set.status = 400;
return {
error: "Bad Request 🦊",
message: error.message,
};
}
if (code === "INTERNAL_SERVER_ERROR") {
set.status = 500;
return {
error: "Internal Server Error 🦊",
};
}
})
.get("/", () => "Welcome to User Management Microservice")
.use(authController)
.listen(PORT);

console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
Expand Down
58 changes: 58 additions & 0 deletions src/services/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { LoginInput, RegisterInput } from "../dto/auth.dto";
import bcrypt from "bcrypt";
import { db } from "../utils/db";
import { ValidationError } from "elysia";
import { BadRequestError } from "../utils/error";

export default class AuthService {

async register(data: RegisterInput) {
// Check if email is used
const existedUser = await db.user.findUnique({
where: {
email: data.email
}
})

if (existedUser) {
throw new BadRequestError("Email has been already used");
}

// Hash password
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(data.password, salt);

// Create user
const user = await db.user.create({
data: {
...data,
password: hashedPassword
}
})

return user;
}


async login(data: LoginInput) {
// Find user by email
const user = await db.user.findUnique({
where: {
email: data.email
}
})

if (!user) {
throw new BadRequestError("Invalid credentials");
}

// Compare password
const isValidPassword = await bcrypt.compare(data.password, user.password);
if (!isValidPassword) {
throw new BadRequestError("Invalid credentials");
}

return user;
}

}
18 changes: 18 additions & 0 deletions src/utils/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { PrismaClient } from "@prisma/client"

declare global {
// eslint-disable-next-line no-var
var cachedPrisma: PrismaClient
}

let prisma: PrismaClient
if (process.env.NODE_ENV === "production") {
prisma = new PrismaClient()
} else {
if (!global.cachedPrisma) {
global.cachedPrisma = new PrismaClient()
}
prisma = global.cachedPrisma
}

export const db = prisma
5 changes: 5 additions & 0 deletions src/utils/error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class BadRequestError extends Error {
constructor(public message: string) {
super(message)
}
}
9 changes: 9 additions & 0 deletions src/utils/jwtParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { User } from "@prisma/client";

export function jwtParams(user: User) {
return {
id: user.id,
email: user.email,
role: user.role
}
}

0 comments on commit da24745

Please sign in to comment.