diff --git a/.env b/.env
index 1f5f0f141e9c1391bcd5b0325fbb516f0fa4bbfb..43bf5fd412feeda40517d5f5e722333b92271442 100644
--- a/.env
+++ b/.env
@@ -4,7 +4,6 @@ PORT=8080
 LOGTAIL_TOKEN=
 HTTP_TIMEOUT_SEC=2
 LOG_FLUSH_INTERVAL_MS=1000
-DB_STRING="host=localhost user=ocw password=ocw dbname=ocw-db port=5433 sslmode=disable TimeZone=Asia/Shanghai"
 SMTP_USERNAME="noreply@ocw.id"
-SMTP_SERVER=localhost
-SMTP_PORT=1025
\ No newline at end of file
+SMTP_PORT=1025
+FE_BASE_URL="http://localhost:3000"
diff --git a/.env.docker b/.env.docker
index 11f1edfc100a7fcebca3466da30fcdd21feff42d..2f87fea919b87db0f51f52306a1a8c21e13e3aaf 100644
--- a/.env.docker
+++ b/.env.docker
@@ -1,3 +1,14 @@
 POSTGRES_USER=ocw
 POSTGRES_PASSWORD=ocw
 POSTGRES_DB=ocw-db
+ENV=DEVELOPMENT
+LISTEN_ADDR=0.0.0.0
+PORT=8080
+LOGTAIL_TOKEN=
+HTTP_TIMEOUT_SEC=2
+LOG_FLUSH_INTERVAL_MS=1000
+DB_STRING="host=database user=ocw password=ocw dbname=ocw-db port=5432 sslmode=disable TimeZone=Asia/Shanghai"
+SMTP_USERNAME="noreply@ocw.id"
+SMTP_SERVER=mailhog
+SMTP_PORT=1025
+REDIS_STRING="redis"
\ No newline at end of file
diff --git a/.env.local.example b/.env.local.example
new file mode 100644
index 0000000000000000000000000000000000000000..c251113ad3df4ab91c35cd7202eb9b5158ecc145
--- /dev/null
+++ b/.env.local.example
@@ -0,0 +1,11 @@
+# Sesuaikan dengan environment masing-masing
+BUCKET_ENDPOINT="minio:9000"
+BUCKET_SECRET_KEY="SUTT"
+BUCKET_ACCESS_ID="ACCESS"
+BUKET_TOKEN_KEY=""
+BUCKET_USE_SSL=false
+BUCKET_NAME="ocw"
+
+SMTP_SERVER=localhost
+DB_STRING="host=localhost user=ocw password=ocw dbname=ocw-db port=5433 sslmode=disable TimeZone=Asia/Shanghai"
+REDIS_STRING="localhost"
diff --git a/.gitignore b/.gitignore
index 7e2965e3e35f23ac242d20ffece83456a71de7ef..d3eeb070587bb63e0a455b491181f8fc408c8df1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
 bin/
 tmp/
 wire_gen.go
+__debug_bin*
+.env.local
diff --git a/Dockerfile.dev b/Dockerfile.dev
index 8def8850bfd96d51a7f1c72944556ba5823eb648..03526f3e75455e51850f7f1186b2d2b05f140392 100644
--- a/Dockerfile.dev
+++ b/Dockerfile.dev
@@ -6,6 +6,9 @@ RUN go install github.com/google/wire/cmd/wire@latest
 RUN go install github.com/swaggo/swag/cmd/swag@latest
 
 COPY . /app
+
+RUN git config --global --add safe.directory /app
+
 WORKDIR /app
 
 EXPOSE 8080
diff --git a/Makefile b/Makefile
index 65b15586a8cec0a52bff462637f75ad06310cc3e..961e18808da80a896920915b35bbc96e27f321d0 100644
--- a/Makefile
+++ b/Makefile
@@ -13,8 +13,8 @@ run: dependency
 build: dependency
 	@go build -o=bin/server.app .
 
-watch:
-	@air --build.cmd="make build" --build.bin="./bin/server.app" --build.exclude_dir="bin,tmp,docs" --build.exclude_file="wire_gen.go"
+watch: dependency
+	@air --build.cmd="make build" --build.bin="./bin/server.app" --build.exclude_dir="bin,tmp,docs" --build.exclude_file="wire_gen.go" --build.kill_delay="0s"
 
 test: test-dependency
 	@go test ./test/... -v
diff --git a/docker-compose.yml b/docker-compose.yml
index 7ccabe6b9f336b3be6a3a55f4c8d11c8c2127306..3eccf485e7a93a9172dcdfca800f8bcf2b732655 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -21,7 +21,7 @@ services:
       - .:/app
     ports:
       - 8888:8080
-    env_file: .env
+    env_file: .env.docker
     depends_on:
       - database
       - minio
diff --git a/docs/docs.go b/docs/docs.go
index 6fece1a6822d55bcc98a20a155a4039e5fb1a903..aad76131fffdf4511f3699ffc3dafc59aa1bc6a5 100644
--- a/docs/docs.go
+++ b/docs/docs.go
@@ -56,6 +56,9 @@ const docTemplate = `{
             },
             "post": {
                 "description": "Add a user to database",
+                "consumes": [
+                    "application/json"
+                ],
                 "produces": [
                     "application/json"
                 ],
@@ -63,6 +66,17 @@ const docTemplate = `{
                     "admin"
                 ],
                 "summary": "Add User",
+                "parameters": [
+                    {
+                        "description": "Admin Add User Payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/admin.AdminAddUserPayload"
+                        }
+                    }
+                ],
                 "responses": {
                     "200": {
                         "description": "OK",
@@ -71,16 +85,18 @@ const docTemplate = `{
                         }
                     }
                 }
-            },
-            "delete": {
-                "description": "Delete a user from database",
+            }
+        },
+        "/admin/user/{email}": {
+            "get": {
+                "description": "Get a user from database",
                 "produces": [
                     "application/json"
                 ],
                 "tags": [
                     "admin"
                 ],
-                "summary": "Delete User By Id",
+                "summary": "Get User By Email",
                 "responses": {
                     "200": {
                         "description": "OK",
@@ -90,15 +106,18 @@ const docTemplate = `{
                     }
                 }
             },
-            "patch": {
-                "description": "Update a user from database",
+            "delete": {
+                "description": "Delete a user from database",
+                "consumes": [
+                    "application/json"
+                ],
                 "produces": [
                     "application/json"
                 ],
                 "tags": [
                     "admin"
                 ],
-                "summary": "Update User By Id",
+                "summary": "Delete User By Id",
                 "responses": {
                     "200": {
                         "description": "OK",
@@ -107,18 +126,30 @@ const docTemplate = `{
                         }
                     }
                 }
-            }
-        },
-        "/admin/user/{id}": {
-            "get": {
-                "description": "Get a user from database",
+            },
+            "patch": {
+                "description": "Update a user from database",
+                "consumes": [
+                    "application/json"
+                ],
                 "produces": [
                     "application/json"
                 ],
                 "tags": [
                     "admin"
                 ],
-                "summary": "Get User By Email",
+                "summary": "Update User By Id",
+                "parameters": [
+                    {
+                        "description": "Admin Update User Payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/admin.AdminUpdateUserPayload"
+                        }
+                    }
+                ],
                 "responses": {
                     "200": {
                         "description": "OK",
@@ -327,9 +358,273 @@ const docTemplate = `{
                     }
                 }
             }
+        },
+        "/auth/verify": {
+            "post": {
+                "description": "Do Email Verification to user",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "auth"
+                ],
+                "summary": "Do Email Verification",
+                "parameters": [
+                    {
+                        "description": "Register Payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/verification.VerificationRequestPayload"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal Server Error",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        },
+        "/auth/verify/resend": {
+            "post": {
+                "description": "Send Email Verification to user",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "auth"
+                ],
+                "summary": "Send Email Verification",
+                "parameters": [
+                    {
+                        "description": "Register Payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/verification.VerificationSendRequestPayload"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal Server Error",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        },
+        "/reset/confirm": {
+            "put": {
+                "description": "Do confirmation to reset password",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "reset"
+                ],
+                "summary": "Confirm Reset Password",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "Email validation token",
+                        "name": "Authorization",
+                        "in": "header",
+                        "required": true
+                    },
+                    {
+                        "description": "payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/confirm.ConfirmRequestPayload"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Login Success",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        },
+        "/reset/request": {
+            "post": {
+                "description": "Send Reset password token to email",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "reset"
+                ],
+                "summary": "Request Reset Password Token",
+                "parameters": [
+                    {
+                        "description": "payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.RequestRequestPayload"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Login Success",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        },
+        "/reset/validate": {
+            "get": {
+                "description": "Send Reset password token to email",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "reset"
+                ],
+                "summary": "Request Reset Password Token",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "Email validation token",
+                        "name": "Authorization",
+                        "in": "header",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Login Success",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
         }
     },
     "definitions": {
+        "admin.AdminAddUserPayload": {
+            "type": "object",
+            "required": [
+                "email",
+                "name",
+                "role"
+            ],
+            "properties": {
+                "email": {
+                    "description": "User Email",
+                    "type": "string",
+                    "example": "someone@example.com"
+                },
+                "name": {
+                    "description": "User name",
+                    "type": "string",
+                    "example": "someone"
+                },
+                "role": {
+                    "description": "User Role",
+                    "type": "string",
+                    "example": "admin"
+                }
+            }
+        },
+        "admin.AdminUpdateUserPayload": {
+            "type": "object",
+            "required": [
+                "email",
+                "name",
+                "role"
+            ],
+            "properties": {
+                "email": {
+                    "description": "User Email",
+                    "type": "string",
+                    "example": "someone@example.com"
+                },
+                "name": {
+                    "description": "User name",
+                    "type": "string",
+                    "example": "someone"
+                },
+                "role": {
+                    "description": "User Role",
+                    "type": "string",
+                    "example": "admin"
+                }
+            }
+        },
+        "confirm.ConfirmRequestPayload": {
+            "description": "Information that should be available when you confirm a password reset",
+            "type": "object",
+            "required": [
+                "password",
+                "password_validation"
+            ],
+            "properties": {
+                "confirmToken": {
+                    "description": "Web Token that was appended to the link",
+                    "type": "string"
+                },
+                "password": {
+                    "description": "User Password",
+                    "type": "string",
+                    "example": "secret"
+                },
+                "password_validation": {
+                    "description": "User Password Validation, must be same as user",
+                    "type": "string",
+                    "example": "secret"
+                }
+            }
+        },
         "login.LoginRequestPayload": {
             "description": "Information that should be available when do a login process",
             "type": "object",
@@ -406,6 +701,46 @@ const docTemplate = `{
                 }
             }
         },
+        "request.RequestRequestPayload": {
+            "description": "Information that should be available when password reset is requested",
+            "type": "object",
+            "required": [
+                "email"
+            ],
+            "properties": {
+                "email": {
+                    "description": "User Email",
+                    "type": "string",
+                    "example": "someone@example.com"
+                }
+            }
+        },
+        "verification.VerificationRequestPayload": {
+            "type": "object",
+            "required": [
+                "id"
+            ],
+            "properties": {
+                "id": {
+                    "type": "string",
+                    "example": "6ba7b812-9dad-11d1-80b4-00c04fd430c8"
+                }
+            }
+        },
+        "verification.VerificationSendRequestPayload": {
+            "description": "Information that should be passed when request verify",
+            "type": "object",
+            "required": [
+                "email"
+            ],
+            "properties": {
+                "email": {
+                    "description": "User Email",
+                    "type": "string",
+                    "example": "someone@example.com"
+                }
+            }
+        },
         "web.BaseResponse": {
             "type": "object",
             "properties": {
diff --git a/docs/swagger.json b/docs/swagger.json
index 513bd4b6b3f4b57241fb86bbb6eb81d7ed0bd1b1..5e40a0e52c5c837cf1a9c167f1e4ab064fce87f3 100644
--- a/docs/swagger.json
+++ b/docs/swagger.json
@@ -48,6 +48,9 @@
             },
             "post": {
                 "description": "Add a user to database",
+                "consumes": [
+                    "application/json"
+                ],
                 "produces": [
                     "application/json"
                 ],
@@ -55,6 +58,17 @@
                     "admin"
                 ],
                 "summary": "Add User",
+                "parameters": [
+                    {
+                        "description": "Admin Add User Payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/admin.AdminAddUserPayload"
+                        }
+                    }
+                ],
                 "responses": {
                     "200": {
                         "description": "OK",
@@ -63,16 +77,18 @@
                         }
                     }
                 }
-            },
-            "delete": {
-                "description": "Delete a user from database",
+            }
+        },
+        "/admin/user/{email}": {
+            "get": {
+                "description": "Get a user from database",
                 "produces": [
                     "application/json"
                 ],
                 "tags": [
                     "admin"
                 ],
-                "summary": "Delete User By Id",
+                "summary": "Get User By Email",
                 "responses": {
                     "200": {
                         "description": "OK",
@@ -82,15 +98,18 @@
                     }
                 }
             },
-            "patch": {
-                "description": "Update a user from database",
+            "delete": {
+                "description": "Delete a user from database",
+                "consumes": [
+                    "application/json"
+                ],
                 "produces": [
                     "application/json"
                 ],
                 "tags": [
                     "admin"
                 ],
-                "summary": "Update User By Id",
+                "summary": "Delete User By Id",
                 "responses": {
                     "200": {
                         "description": "OK",
@@ -99,18 +118,30 @@
                         }
                     }
                 }
-            }
-        },
-        "/admin/user/{id}": {
-            "get": {
-                "description": "Get a user from database",
+            },
+            "patch": {
+                "description": "Update a user from database",
+                "consumes": [
+                    "application/json"
+                ],
                 "produces": [
                     "application/json"
                 ],
                 "tags": [
                     "admin"
                 ],
-                "summary": "Get User By Email",
+                "summary": "Update User By Id",
+                "parameters": [
+                    {
+                        "description": "Admin Update User Payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/admin.AdminUpdateUserPayload"
+                        }
+                    }
+                ],
                 "responses": {
                     "200": {
                         "description": "OK",
@@ -319,9 +350,273 @@
                     }
                 }
             }
+        },
+        "/auth/verify": {
+            "post": {
+                "description": "Do Email Verification to user",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "auth"
+                ],
+                "summary": "Do Email Verification",
+                "parameters": [
+                    {
+                        "description": "Register Payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/verification.VerificationRequestPayload"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal Server Error",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        },
+        "/auth/verify/resend": {
+            "post": {
+                "description": "Send Email Verification to user",
+                "consumes": [
+                    "application/json"
+                ],
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "auth"
+                ],
+                "summary": "Send Email Verification",
+                "parameters": [
+                    {
+                        "description": "Register Payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/verification.VerificationSendRequestPayload"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "OK",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    },
+                    "400": {
+                        "description": "Bad Request",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    },
+                    "500": {
+                        "description": "Internal Server Error",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        },
+        "/reset/confirm": {
+            "put": {
+                "description": "Do confirmation to reset password",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "reset"
+                ],
+                "summary": "Confirm Reset Password",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "Email validation token",
+                        "name": "Authorization",
+                        "in": "header",
+                        "required": true
+                    },
+                    {
+                        "description": "payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/confirm.ConfirmRequestPayload"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Login Success",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        },
+        "/reset/request": {
+            "post": {
+                "description": "Send Reset password token to email",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "reset"
+                ],
+                "summary": "Request Reset Password Token",
+                "parameters": [
+                    {
+                        "description": "payload",
+                        "name": "data",
+                        "in": "body",
+                        "required": true,
+                        "schema": {
+                            "$ref": "#/definitions/request.RequestRequestPayload"
+                        }
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Login Success",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
+        },
+        "/reset/validate": {
+            "get": {
+                "description": "Send Reset password token to email",
+                "produces": [
+                    "application/json"
+                ],
+                "tags": [
+                    "reset"
+                ],
+                "summary": "Request Reset Password Token",
+                "parameters": [
+                    {
+                        "type": "string",
+                        "description": "Email validation token",
+                        "name": "Authorization",
+                        "in": "header",
+                        "required": true
+                    }
+                ],
+                "responses": {
+                    "200": {
+                        "description": "Login Success",
+                        "schema": {
+                            "$ref": "#/definitions/web.BaseResponse"
+                        }
+                    }
+                }
+            }
         }
     },
     "definitions": {
+        "admin.AdminAddUserPayload": {
+            "type": "object",
+            "required": [
+                "email",
+                "name",
+                "role"
+            ],
+            "properties": {
+                "email": {
+                    "description": "User Email",
+                    "type": "string",
+                    "example": "someone@example.com"
+                },
+                "name": {
+                    "description": "User name",
+                    "type": "string",
+                    "example": "someone"
+                },
+                "role": {
+                    "description": "User Role",
+                    "type": "string",
+                    "example": "admin"
+                }
+            }
+        },
+        "admin.AdminUpdateUserPayload": {
+            "type": "object",
+            "required": [
+                "email",
+                "name",
+                "role"
+            ],
+            "properties": {
+                "email": {
+                    "description": "User Email",
+                    "type": "string",
+                    "example": "someone@example.com"
+                },
+                "name": {
+                    "description": "User name",
+                    "type": "string",
+                    "example": "someone"
+                },
+                "role": {
+                    "description": "User Role",
+                    "type": "string",
+                    "example": "admin"
+                }
+            }
+        },
+        "confirm.ConfirmRequestPayload": {
+            "description": "Information that should be available when you confirm a password reset",
+            "type": "object",
+            "required": [
+                "password",
+                "password_validation"
+            ],
+            "properties": {
+                "confirmToken": {
+                    "description": "Web Token that was appended to the link",
+                    "type": "string"
+                },
+                "password": {
+                    "description": "User Password",
+                    "type": "string",
+                    "example": "secret"
+                },
+                "password_validation": {
+                    "description": "User Password Validation, must be same as user",
+                    "type": "string",
+                    "example": "secret"
+                }
+            }
+        },
         "login.LoginRequestPayload": {
             "description": "Information that should be available when do a login process",
             "type": "object",
@@ -398,6 +693,46 @@
                 }
             }
         },
+        "request.RequestRequestPayload": {
+            "description": "Information that should be available when password reset is requested",
+            "type": "object",
+            "required": [
+                "email"
+            ],
+            "properties": {
+                "email": {
+                    "description": "User Email",
+                    "type": "string",
+                    "example": "someone@example.com"
+                }
+            }
+        },
+        "verification.VerificationRequestPayload": {
+            "type": "object",
+            "required": [
+                "id"
+            ],
+            "properties": {
+                "id": {
+                    "type": "string",
+                    "example": "6ba7b812-9dad-11d1-80b4-00c04fd430c8"
+                }
+            }
+        },
+        "verification.VerificationSendRequestPayload": {
+            "description": "Information that should be passed when request verify",
+            "type": "object",
+            "required": [
+                "email"
+            ],
+            "properties": {
+                "email": {
+                    "description": "User Email",
+                    "type": "string",
+                    "example": "someone@example.com"
+                }
+            }
+        },
         "web.BaseResponse": {
             "type": "object",
             "properties": {
diff --git a/docs/swagger.yaml b/docs/swagger.yaml
index ee76e9375fe026003fafa69a41451de0804766b5..d2280c2b16106e29102e0ed33e5e14d8ac794bc6 100644
--- a/docs/swagger.yaml
+++ b/docs/swagger.yaml
@@ -1,4 +1,61 @@
 definitions:
+  admin.AdminAddUserPayload:
+    properties:
+      email:
+        description: User Email
+        example: someone@example.com
+        type: string
+      name:
+        description: User name
+        example: someone
+        type: string
+      role:
+        description: User Role
+        example: admin
+        type: string
+    required:
+    - email
+    - name
+    - role
+    type: object
+  admin.AdminUpdateUserPayload:
+    properties:
+      email:
+        description: User Email
+        example: someone@example.com
+        type: string
+      name:
+        description: User name
+        example: someone
+        type: string
+      role:
+        description: User Role
+        example: admin
+        type: string
+    required:
+    - email
+    - name
+    - role
+    type: object
+  confirm.ConfirmRequestPayload:
+    description: Information that should be available when you confirm a password
+      reset
+    properties:
+      confirmToken:
+        description: Web Token that was appended to the link
+        type: string
+      password:
+        description: User Password
+        example: secret
+        type: string
+      password_validation:
+        description: User Password Validation, must be same as user
+        example: secret
+        type: string
+    required:
+    - password
+    - password_validation
+    type: object
   login.LoginRequestPayload:
     description: Information that should be available when do a login process
     properties:
@@ -56,6 +113,34 @@ definitions:
     - password
     - password_validation
     type: object
+  request.RequestRequestPayload:
+    description: Information that should be available when password reset is requested
+    properties:
+      email:
+        description: User Email
+        example: someone@example.com
+        type: string
+    required:
+    - email
+    type: object
+  verification.VerificationRequestPayload:
+    properties:
+      id:
+        example: 6ba7b812-9dad-11d1-80b4-00c04fd430c8
+        type: string
+    required:
+    - id
+    type: object
+  verification.VerificationSendRequestPayload:
+    description: Information that should be passed when request verify
+    properties:
+      email:
+        description: User Email
+        example: someone@example.com
+        type: string
+    required:
+    - email
+    type: object
   web.BaseResponse:
     properties:
       data: {}
@@ -87,8 +172,8 @@ paths:
       tags:
       - common
   /admin/user:
-    delete:
-      description: Delete a user from database
+    get:
+      description: Get all users from database
       produces:
       - application/json
       responses:
@@ -96,11 +181,20 @@ paths:
           description: OK
           schema:
             $ref: '#/definitions/web.BaseResponse'
-      summary: Delete User By Id
+      summary: Get All User
       tags:
       - admin
-    get:
-      description: Get all users from database
+    post:
+      consumes:
+      - application/json
+      description: Add a user to database
+      parameters:
+      - description: Admin Add User Payload
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/admin.AdminAddUserPayload'
       produces:
       - application/json
       responses:
@@ -108,11 +202,14 @@ paths:
           description: OK
           schema:
             $ref: '#/definitions/web.BaseResponse'
-      summary: Get All User
+      summary: Add User
       tags:
       - admin
-    patch:
-      description: Update a user from database
+  /admin/user/{email}:
+    delete:
+      consumes:
+      - application/json
+      description: Delete a user from database
       produces:
       - application/json
       responses:
@@ -120,11 +217,11 @@ paths:
           description: OK
           schema:
             $ref: '#/definitions/web.BaseResponse'
-      summary: Update User By Id
+      summary: Delete User By Id
       tags:
       - admin
-    post:
-      description: Add a user to database
+    get:
+      description: Get a user from database
       produces:
       - application/json
       responses:
@@ -132,12 +229,20 @@ paths:
           description: OK
           schema:
             $ref: '#/definitions/web.BaseResponse'
-      summary: Add User
+      summary: Get User By Email
       tags:
       - admin
-  /admin/user/{id}:
-    get:
-      description: Get a user from database
+    patch:
+      consumes:
+      - application/json
+      description: Update a user from database
+      parameters:
+      - description: Admin Update User Payload
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/admin.AdminUpdateUserPayload'
       produces:
       - application/json
       responses:
@@ -145,7 +250,7 @@ paths:
           description: OK
           schema:
             $ref: '#/definitions/web.BaseResponse'
-      summary: Get User By Email
+      summary: Update User By Id
       tags:
       - admin
   /auth/login:
@@ -270,4 +375,128 @@ paths:
       summary: Register New Account
       tags:
       - auth
+  /auth/verify:
+    post:
+      consumes:
+      - application/json
+      description: Do Email Verification to user
+      parameters:
+      - description: Register Payload
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/verification.VerificationRequestPayload'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+        "400":
+          description: Bad Request
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+      summary: Do Email Verification
+      tags:
+      - auth
+  /auth/verify/resend:
+    post:
+      consumes:
+      - application/json
+      description: Send Email Verification to user
+      parameters:
+      - description: Register Payload
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/verification.VerificationSendRequestPayload'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+        "400":
+          description: Bad Request
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+        "500":
+          description: Internal Server Error
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+      summary: Send Email Verification
+      tags:
+      - auth
+  /reset/confirm:
+    put:
+      description: Do confirmation to reset password
+      parameters:
+      - description: Email validation token
+        in: header
+        name: Authorization
+        required: true
+        type: string
+      - description: payload
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/confirm.ConfirmRequestPayload'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: Login Success
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+      summary: Confirm Reset Password
+      tags:
+      - reset
+  /reset/request:
+    post:
+      description: Send Reset password token to email
+      parameters:
+      - description: payload
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/request.RequestRequestPayload'
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: Login Success
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+      summary: Request Reset Password Token
+      tags:
+      - reset
+  /reset/validate:
+    get:
+      description: Send Reset password token to email
+      parameters:
+      - description: Email validation token
+        in: header
+        name: Authorization
+        required: true
+        type: string
+      produces:
+      - application/json
+      responses:
+        "200":
+          description: Login Success
+          schema:
+            $ref: '#/definitions/web.BaseResponse'
+      summary: Request Reset Password Token
+      tags:
+      - reset
 swagger: "2.0"
diff --git a/go.mod b/go.mod
index b69d320688f3db0c6bbdcc1ad19eea994fde74ea..838370778ef7bc486acbbfbcdb8116a3988375ba 100644
--- a/go.mod
+++ b/go.mod
@@ -8,51 +8,56 @@ require (
 	github.com/go-chi/cors v1.2.1
 	github.com/go-playground/validator/v10 v10.11.2
 	github.com/golang-jwt/jwt/v4 v4.4.3
+	github.com/google/uuid v1.3.0
 	github.com/google/wire v0.5.0
-	github.com/joho/godotenv v1.5.0
+	github.com/joho/godotenv v1.5.1
+	github.com/minio/minio-go/v7 v7.0.49
 	github.com/sirupsen/logrus v1.9.0
 	github.com/stretchr/testify v1.8.1
 	github.com/swaggo/http-swagger v1.3.3
 	github.com/swaggo/swag v1.8.10
-	golang.org/x/crypto v0.6.0
-	gorm.io/driver/postgres v1.4.6
-	gorm.io/gorm v1.24.5
+	golang.org/x/crypto v0.7.0
+	gorm.io/driver/postgres v1.5.0
+	gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11
 )
 
 require (
-	github.com/DATA-DOG/go-sqlmock v1.5.0 // indirect
+	github.com/dustin/go-humanize v1.0.1 // indirect
+	github.com/json-iterator/go v1.1.12 // indirect
+	github.com/klauspost/compress v1.16.0 // indirect
+	github.com/klauspost/cpuid/v2 v2.2.4 // indirect
+	github.com/minio/md5-simd v1.1.2 // indirect
+	github.com/minio/sha256-simd v1.0.0 // indirect
+	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/rs/xid v1.4.0 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
+)
+
+require (
+	github.com/DATA-DOG/go-sqlmock v1.5.0
 	github.com/KyleBanks/depth v1.2.1 // indirect
-	github.com/PuerkitoBio/purell v1.1.1 // indirect
-	github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
-	github.com/cosmtrek/air v1.41.0 // indirect
-	github.com/creack/pty v1.1.18 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
-	github.com/fatih/color v1.14.1 // indirect
-	github.com/fsnotify/fsnotify v1.6.0 // indirect
 	github.com/go-openapi/jsonpointer v0.19.6 // indirect
 	github.com/go-openapi/jsonreference v0.20.2 // indirect
 	github.com/go-openapi/spec v0.20.8 // indirect
 	github.com/go-openapi/swag v0.22.3 // indirect
 	github.com/go-playground/locales v0.14.1 // indirect
 	github.com/go-playground/universal-translator v0.18.1 // indirect
+	github.com/gomodule/redigo v1.8.9
 	github.com/jackc/pgpassfile v1.0.0 // indirect
 	github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
-	github.com/jackc/pgx/v5 v5.2.0 // indirect
+	github.com/jackc/pgx/v5 v5.3.1 // indirect
 	github.com/jinzhu/inflection v1.0.0 // indirect
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/leodido/go-urn v1.2.1 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/mattn/go-colorable v0.1.13 // indirect
-	github.com/mattn/go-isatty v0.0.17 // indirect
-	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
-	github.com/stretchr/objx v0.5.0 // indirect
 	github.com/swaggo/files v1.0.0 // indirect
-	golang.org/x/net v0.6.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/text v0.7.0 // indirect
-	golang.org/x/tools v0.5.0 // indirect
-	gopkg.in/yaml.v2 v2.4.0 // indirect
+	golang.org/x/net v0.8.0 // indirect
+	golang.org/x/sys v0.6.0 // indirect
+	golang.org/x/text v0.8.0 // indirect
+	golang.org/x/tools v0.6.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
diff --git a/go.sum b/go.sum
index d771a874f694fecc968e13f8658ba7de1cd1ca6f..4b7ff59fd497a0efd4a6fa5af4f7e7646cb28f7a 100644
--- a/go.sum
+++ b/go.sum
@@ -2,47 +2,32 @@ github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20O
 github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
 github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
-github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
-github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
-github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II=
 github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc=
-github.com/cosmtrek/air v1.41.0 h1:6ck2LbcVvby6cyuwE8ruia41U2nppMZGWOpq+E/EhoU=
-github.com/cosmtrek/air v1.41.0/go.mod h1:+RBGjJt7T2f3I7td8Tvk0XsH/hZ3E1QBLfiWObICO4c=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
-github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
-github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
-github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
+github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
 github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
 github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
 github.com/go-chi/cors v1.2.1 h1:xEC8UT3Rlp2QuWNEr4Fs/c2EAGVKBwy/1vHx3bppil4=
 github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
 github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
-github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
 github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
 github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
 github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
-github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
-github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
 github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
 github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
 github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
-github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
-github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
 github.com/go-openapi/spec v0.20.8 h1:ubHmXNY3FCIOinT8RNrrPfGc9t7I1qhPtdOGoG2AxRU=
 github.com/go-openapi/spec v0.20.8/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
 github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
 github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
 github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
+github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
 github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
 github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
@@ -51,64 +36,78 @@ github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyh
 github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
 github.com/golang-jwt/jwt/v4 v4.4.3 h1:Hxl6lhQFj4AnOX6MLrsCb/+7tCj7DxP7VA+2rDIq5AU=
 github.com/golang-jwt/jwt/v4 v4.4.3/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/gomodule/redigo v1.8.9 h1:Sl3u+2BI/kk+VEatbj0scLdrFhjPmbxOc1myhDP41ws=
+github.com/gomodule/redigo v1.8.9/go.mod h1:7ArFNvsTjH8GMMzB4uy1snslv2BwmginuMs06a1uzZE=
 github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/subcommands v1.0.1/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/google/wire v0.5.0 h1:I7ELFeVBr3yfPIcc8+MWvrjk+3VjbcSzoXm3JVa+jD8=
 github.com/google/wire v0.5.0/go.mod h1:ngWDr9Qvq3yZA10YrxfyGELY/AFWGVpy9c1LTRi1EoU=
-github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
-github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
 github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
 github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
-github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E=
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
 github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
-github.com/jackc/pgx/v5 v5.2.0 h1:NdPpngX0Y6z6XDFKqmFQaE+bCtkqzvQIOt1wvBlAqs8=
-github.com/jackc/pgx/v5 v5.2.0/go.mod h1:Ptn7zmohNsWEsdxRawMzk3gaKma2obW+NWTnKa0S4nk=
-github.com/jackc/puddle/v2 v2.1.2/go.mod h1:2lpufsF5mRHO6SuZkm0fNYxM6SWHfvyFj62KwNzgels=
+github.com/jackc/pgx/v5 v5.3.0/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
+github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU=
+github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8=
+github.com/jackc/puddle/v2 v2.2.0/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
 github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
-github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
 github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
 github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
-github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
-github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
-github.com/joho/godotenv v1.5.0 h1:C/Vohk/9L1RCoS/UW2gfyi2N0EElSW3yb9zwi3PjosE=
-github.com/joho/godotenv v1.5.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/klauspost/compress v1.16.0 h1:iULayQNOReoYUe+1qtKOqw9CwJv3aNQu8ivo7lw1HU4=
+github.com/klauspost/compress v1.16.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
+github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
+github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
+github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
 github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
 github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
 github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
 github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
-github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
 github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
-github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
-github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
-github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
-github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
-github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
+github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
+github.com/minio/minio-go/v7 v7.0.49 h1:dE5DfOtnXMXCjr/HWI6zN9vCrY6Sv666qhhiwUMvGV4=
+github.com/minio/minio-go/v7 v7.0.49/go.mod h1:UI34MvQEiob3Cf/gGExGMmzugkM/tNgbFypNDy5LMVc=
+github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
+github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
-github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
-github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
+github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
+github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
+github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
 github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
-github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
@@ -122,88 +121,69 @@ github.com/swaggo/http-swagger v1.3.3/go.mod h1:sE+4PjD89IxMPm77FnkDz0sdO+p5lbXz
 github.com/swaggo/swag v1.8.10 h1:eExW4bFa52WOjqRzRD58bgWsWfdFJso50lpbeTcmTfo=
 github.com/swaggo/swag v1.8.10/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
-go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.4.0/go.mod h1:3quD/ATkf6oY+rnes5c3ExXTbLc8mueNue5/DoinL80=
-golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE=
-golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU=
-golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
+golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
+golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
 golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
-golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE=
-golang.org/x/net v0.5.0 h1:GyT4nK/YDHSqa1c4753ouYCDajOYKTja9Xb/OHtgvSw=
-golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws=
-golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
+golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
-golang.org/x/sync v0.0.0-20220923202941-7f9b1623fab7/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
-golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
-golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.6.0 h1:3XmdazWV+ubf7QgHSTWeykHOci5oeekaGJBLkrkaw4k=
-golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
+golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190422233926-fe54fb35175b/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
-golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
-golang.org/x/tools v0.5.0 h1:+bSpV5HIeWkuvgaMfI3UmKRThoTA5ODJTUd8T17NO+4=
-golang.org/x/tools v0.5.0/go.mod h1:N+Kgy78s5I24c24dU8OfWNEotWjutIs8SnJvn5IDq+k=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gorm.io/driver/postgres v1.4.6 h1:1FPESNXqIKG5JmraaH2bfCVlMQ7paLoCreFxDtqzwdc=
-gorm.io/driver/postgres v1.4.6/go.mod h1:UJChCNLFKeBqQRE+HrkFUbKbq9idPXmTOk2u4Wok8S4=
-gorm.io/gorm v1.24.2/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
-gorm.io/gorm v1.24.5 h1:g6OPREKqqlWq4kh/3MCQbZKImeB9e6Xgc4zD+JgNZGE=
-gorm.io/gorm v1.24.5/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
+gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
+gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
+gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11 h1:9qNbmu21nNThCNnF5i2R3kw2aL27U8ZwbzccNjOmW0g=
+gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
diff --git a/handler/admin/addUser.go b/handler/admin/addUser.go
index 7b53f3d3d4977c157d67048d3079c40e46e446fe..02c631be6d59ed66f628b57dfa4ace8191b05249 100644
--- a/handler/admin/addUser.go
+++ b/handler/admin/addUser.go
@@ -2,6 +2,7 @@ package admin
 
 import (
 	"net/http"
+	req "gitlab.informatika.org/ocw/ocw-backend/model/web/admin/addUser"
 )
 
 // Index godoc
@@ -10,9 +11,32 @@ import (
 //	@Summary			Add User
 //	@Description	Add a user to database
 //	@Produce			json
+//	@Accept				json
+//	@Param				data body req.AdminAddUserPayload true "Admin Add User Payload"
 //	@Success			200	{object}	web.BaseResponse
 //	@Router				/admin/user [post]
 func (route AdminHandlerImpl) AddUser(w http.ResponseWriter, r *http.Request){
-	payload := route.WrapperUtil.SuccessResponseWrap(route.AdminService.AddUser())
+	payload := req.AdminAddUserPayload{}
+
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := route.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		route.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := route.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := route.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		route.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	err := route.AdminService.AddUser(payload)
+
+	if err != nil {
+		payload := route.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+		route.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+	
 	route.HttpUtil.WriteSuccessJson(w, payload)
 }
\ No newline at end of file
diff --git a/handler/admin/deleteUser.go b/handler/admin/deleteUser.go
index 0be96ccc00451aaf0dd1490d6b29154a1b2d62dd..9979a47a2279814f9de6c9302c3a555531a659aa 100644
--- a/handler/admin/deleteUser.go
+++ b/handler/admin/deleteUser.go
@@ -2,6 +2,7 @@ package admin
 
 import (
 	"net/http"
+	"path"
 )
 
 // Index godoc
@@ -10,9 +11,23 @@ import (
 //	@Summary			Delete User By Id
 //	@Description	Delete a user from database
 //	@Produce			json
+//	@Accept				json
 //	@Success			200	{object}	web.BaseResponse
-//	@Router				/admin/user [delete]
+//	@Router				/admin/user/{email} [delete]
 func (route AdminHandlerImpl) DeleteUser(w http.ResponseWriter, r *http.Request){
-	payload := route.WrapperUtil.SuccessResponseWrap(route.AdminService.DeleteUser())
-	route.HttpUtil.WriteSuccessJson(w, payload)
+	email := path.Base(r.URL.Path)
+
+	// get user from database
+	err := route.AdminService.DeleteUser(email)
+
+	if err != nil {
+		// error handling
+		payload := route.WrapperUtil.ErrorResponseWrap("error", err.Error())
+		route.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	// return user
+	result := route.WrapperUtil.SuccessResponseWrap(email)
+	route.HttpUtil.WriteJson(w, http.StatusOK, result)
 }
\ No newline at end of file
diff --git a/handler/admin/getUserByEmail.go b/handler/admin/getUserByEmail.go
index 0ad7a59fce2214548fd0aaae8b2443e4b5595ca7..fd21301db325bf6d59eb8df4404f352ba4fd2b29 100644
--- a/handler/admin/getUserByEmail.go
+++ b/handler/admin/getUserByEmail.go
@@ -2,6 +2,7 @@ package admin
 
 import (
 	"net/http"
+	"path"
 )
 
 // Index godoc
@@ -11,8 +12,22 @@ import (
 //	@Description	Get a user from database
 //	@Produce			json
 //	@Success			200	{object}	web.BaseResponse
-//	@Router				/admin/user/{id} [get]
+//	@Router				/admin/user/{email} [get]
 func (route AdminHandlerImpl) GetUserByEmail(w http.ResponseWriter, r *http.Request) {
-	payload := route.WrapperUtil.SuccessResponseWrap(route.AdminService.GetUserByEmail())
-	route.HttpUtil.WriteSuccessJson(w, payload)
+	// email := r.URL.Query().Get("email")
+	email := path.Base(r.URL.Path)
+
+	// get user from database
+	user, err := route.AdminService.GetUserByEmail(email)
+
+	if err != nil {
+		// error handling
+		payload := route.WrapperUtil.ErrorResponseWrap("error", err.Error())
+		route.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	// return user
+	result := route.WrapperUtil.SuccessResponseWrap(user)
+	route.HttpUtil.WriteJson(w, http.StatusOK, result)
 }
diff --git a/handler/admin/updateUser.go b/handler/admin/updateUser.go
index c6531ec04835aabca40eedf74f3f008bc27d017c..877d5c84a3785626e9736ecf1d433d37fdf1df40 100644
--- a/handler/admin/updateUser.go
+++ b/handler/admin/updateUser.go
@@ -2,6 +2,8 @@ package admin
 
 import (
 	"net/http"
+	"path"
+	req "gitlab.informatika.org/ocw/ocw-backend/model/web/admin/updateUser"
 )
 
 // Index godoc
@@ -10,9 +12,35 @@ import (
 //	@Summary			Update User By Id
 //	@Description	Update a user from database
 //	@Produce			json
+//	@Accept				json
+//	@Param				data body req.AdminUpdateUserPayload true "Admin Update User Payload"
 //	@Success			200	{object}	web.BaseResponse
-//	@Router				/admin/user [patch]
+//	@Router				/admin/user/{email} [patch]
 func (route AdminHandlerImpl) UpdateUser(w http.ResponseWriter, r *http.Request){
-	payload := route.WrapperUtil.SuccessResponseWrap(route.AdminService.UpdateUser())
+	email := path.Base(r.URL.Path)
+	// TODO: how to change email
+
+	payload := req.AdminUpdateUserPayload{}
+
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := route.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		route.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := route.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := route.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		route.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	err := route.AdminService.UpdateUser(email, payload)
+
+	if err != nil {
+		payload := route.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+		route.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+	
 	route.HttpUtil.WriteSuccessJson(w, payload)
 }
\ No newline at end of file
diff --git a/handler/auth/handler.go b/handler/auth/handler.go
index adae847f2baec21f127940794b447cffdb9824b3..0799b93d0b06e23856bf1cc49949511d7f8924bf 100644
--- a/handler/auth/handler.go
+++ b/handler/auth/handler.go
@@ -3,6 +3,7 @@ package auth
 import (
 	"gitlab.informatika.org/ocw/ocw-backend/service/auth"
 	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/service/verification"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/httputil"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
 )
@@ -12,4 +13,5 @@ type AuthHandlerImpl struct {
 	httputil.HttpUtil
 	wrapper.WrapperUtil
 	logger.Logger
+	verification.VerificationService
 }
diff --git a/handler/auth/send_verify.go b/handler/auth/send_verify.go
new file mode 100644
index 0000000000000000000000000000000000000000..b2b4a2e70eaca89ac5de5f26a76fb4210d894416
--- /dev/null
+++ b/handler/auth/send_verify.go
@@ -0,0 +1,72 @@
+package auth
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/go-playground/validator/v10"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/verification"
+)
+
+// Index godoc
+//
+//		@Tags					auth
+//		@Summary			Send Email Verification
+//		@Description	Send Email Verification to user
+//		@Produce			json
+//		@Accept				json
+//		@Param				data body verification.VerificationSendRequestPayload true "Register Payload"
+//		@Success			200	{object}	web.BaseResponse
+//	  @Failure			400 {object}  web.BaseResponse
+//	  @Failure			500 {object}  web.BaseResponse
+//		@Router				/auth/verify/resend  [post]
+func (a AuthHandlerImpl) SendEmailVerify(w http.ResponseWriter, r *http.Request) {
+	payload := verification.VerificationSendRequestPayload{}
+	validate := validator.New()
+
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := a.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		a.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := a.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := a.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		a.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	if err := validate.Struct(payload); err != nil {
+		if _, ok := err.(*validator.InvalidValidationError); ok {
+			payload := a.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+			a.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+			return
+		}
+
+		errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors))
+		payload := a.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload)
+		a.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	err := a.AuthService.SendVerifyEmail(payload)
+
+	if err != nil {
+		respErr, ok := err.(web.ResponseError)
+		if ok {
+			payload := a.WrapperUtil.ErrorResponseWrap("you have reach limit of resend verification", respErr)
+			a.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		} else {
+			a.Logger.Error(
+				fmt.Sprintf("[AUTH] some error happened when do email verification: %s", err.Error()),
+			)
+			payload := a.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+			a.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		}
+		return
+	}
+
+	responsePayload := a.WrapperUtil.SuccessResponseWrap(nil)
+	a.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
diff --git a/handler/auth/types.go b/handler/auth/types.go
index 71b4d1eba14c50a290f9c43565da1dd707e73f29..0848fea6ecd2b79b4f2470f432eeb8cbaf3c3d48 100644
--- a/handler/auth/types.go
+++ b/handler/auth/types.go
@@ -6,4 +6,6 @@ type AuthHandler interface {
 	Login(w http.ResponseWriter, r *http.Request)
 	Register(w http.ResponseWriter, r *http.Request)
 	Refresh(w http.ResponseWriter, r *http.Request)
+	EmailVerify(w http.ResponseWriter, r *http.Request)
+	SendEmailVerify(w http.ResponseWriter, r *http.Request)
 }
diff --git a/handler/auth/verify.go b/handler/auth/verify.go
new file mode 100644
index 0000000000000000000000000000000000000000..a118518f67d6d8e4dff329541d93d406f545cbca
--- /dev/null
+++ b/handler/auth/verify.go
@@ -0,0 +1,72 @@
+package auth
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/go-playground/validator/v10"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/verification"
+)
+
+// Index godoc
+//
+//		@Tags					auth
+//		@Summary			Do Email Verification
+//		@Description	Do Email Verification to user
+//		@Produce			json
+//		@Accept				json
+//		@Param				data body verification.VerificationRequestPayload true "Register Payload"
+//		@Success			200	{object}	web.BaseResponse
+//	  @Failure			400 {object}  web.BaseResponse
+//	  @Failure			500 {object}  web.BaseResponse
+//		@Router				/auth/verify  [post]
+func (a AuthHandlerImpl) EmailVerify(w http.ResponseWriter, r *http.Request) {
+	payload := verification.VerificationRequestPayload{}
+	validate := validator.New()
+
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := a.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		a.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := a.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := a.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		a.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	if err := validate.Struct(payload); err != nil {
+		if _, ok := err.(*validator.InvalidValidationError); ok {
+			payload := a.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+			a.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+			return
+		}
+
+		errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors))
+		payload := a.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload)
+		a.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	err := a.VerificationService.DoVerification(payload.Id)
+
+	if err != nil {
+		respErr, ok := err.(web.ResponseError)
+		if ok {
+			payload := a.WrapperUtil.ErrorResponseWrap("you have reach limit of resend verification", respErr)
+			a.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		} else {
+			a.Logger.Error(
+				fmt.Sprintf("[AUTH] some error happened when do email verification: %s", err.Error()),
+			)
+			payload := a.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+			a.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		}
+		return
+	}
+
+	responsePayload := a.WrapperUtil.SuccessResponseWrap(nil)
+	a.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
diff --git a/handler/course/addCourse.go b/handler/course/addCourse.go
new file mode 100644
index 0000000000000000000000000000000000000000..c07f9ce00468bf9589df78b295922432342e307e
--- /dev/null
+++ b/handler/course/addCourse.go
@@ -0,0 +1,86 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/go-playground/validator/v10"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/add"
+)
+
+func (c CourseHandlerImpl) AddCourse(w http.ResponseWriter, r *http.Request) {
+	payload := add.AddCourseRequestPayload{}
+	validate := validator.New()
+
+	// Validate payload
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := c.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		c.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := c.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		c.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	if err := validate.Struct(payload); err != nil {
+		if _, ok := err.(*validator.InvalidValidationError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+			c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+			return
+		}
+
+		errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors))
+		payload := c.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	// Confirm Valid Website Token
+	validateTokenHeader := r.Header.Get("Authorization")
+
+	if validateTokenHeader == "" {
+		payload := c.WrapperUtil.ErrorResponseWrap("token is required", nil)
+		c.HttpUtil.WriteJson(w, http.StatusForbidden, payload)
+		return
+	}
+
+	token := strings.Split(validateTokenHeader, " ")
+
+	if len(token) != 2 {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	if token[0] != "Bearer" {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.AddCourseToken = token[1]
+	err := c.CourseService.AddCourse(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(nil)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/addFaculty.go b/handler/course/addFaculty.go
new file mode 100644
index 0000000000000000000000000000000000000000..a0430da8c4541a971771b86ef0d8cd3a1a002927
--- /dev/null
+++ b/handler/course/addFaculty.go
@@ -0,0 +1,86 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/go-playground/validator/v10"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/faculty/add"
+)
+
+func (c CourseHandlerImpl) AddFaculty(w http.ResponseWriter, r *http.Request) {
+	payload := add.AddFacultyRequestPayload{}
+	validate := validator.New()
+
+	// Validate payload
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := c.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		c.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := c.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		c.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	if err := validate.Struct(payload); err != nil {
+		if _, ok := err.(*validator.InvalidValidationError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+			c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+			return
+		}
+
+		errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors))
+		payload := c.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	// Confirm Valid Website Token
+	validateTokenHeader := r.Header.Get("Authorization")
+
+	if validateTokenHeader == "" {
+		payload := c.WrapperUtil.ErrorResponseWrap("token is required", nil)
+		c.HttpUtil.WriteJson(w, http.StatusForbidden, payload)
+		return
+	}
+
+	token := strings.Split(validateTokenHeader, " ")
+
+	if len(token) != 2 {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	if token[0] != "Bearer" {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.AddFacultyToken = token[1]
+	err := c.CourseService.AddFaculty(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(nil)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/addMajor.go b/handler/course/addMajor.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f3c3ab0faaf177ab6fc12a1da902dac2b8d7ad8
--- /dev/null
+++ b/handler/course/addMajor.go
@@ -0,0 +1,86 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/go-playground/validator/v10"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/major/add"
+)
+
+func (c CourseHandlerImpl) AddMajor(w http.ResponseWriter, r *http.Request) {
+	payload := add.AddMajorRequestPayload{}
+	validate := validator.New()
+
+	// Validate payload
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := c.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		c.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := c.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		c.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	if err := validate.Struct(payload); err != nil {
+		if _, ok := err.(*validator.InvalidValidationError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+			c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+			return
+		}
+
+		errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors))
+		payload := c.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	// Confirm Valid Website Token
+	validateTokenHeader := r.Header.Get("Authorization")
+
+	if validateTokenHeader == "" {
+		payload := c.WrapperUtil.ErrorResponseWrap("token is required", nil)
+		c.HttpUtil.WriteJson(w, http.StatusForbidden, payload)
+		return
+	}
+
+	token := strings.Split(validateTokenHeader, " ")
+
+	if len(token) != 2 {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	if token[0] != "Bearer" {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.AddMajorToken = token[1]
+	err := c.CourseService.AddMajor(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(nil)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/deleteCourse.go b/handler/course/deleteCourse.go
new file mode 100644
index 0000000000000000000000000000000000000000..a06a7a3e41c72742cb6bda478ecb34def0791056
--- /dev/null
+++ b/handler/course/deleteCourse.go
@@ -0,0 +1,57 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/delete"
+)
+
+func (c CourseHandlerImpl) DeleteCourse(w http.ResponseWriter, r *http.Request) {
+	payload := delete.DeleteByStringRequestPayload{}
+	validateTokenHeader := r.Header.Get("Authorization")
+
+	if validateTokenHeader == "" {
+		payload := c.WrapperUtil.ErrorResponseWrap("token is required", nil)
+		c.HttpUtil.WriteJson(w, http.StatusForbidden, payload)
+		return
+	}
+
+	token := strings.Split(validateTokenHeader, " ")
+
+	if len(token) != 2 {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	if token[0] != "Bearer" {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.DeleteCourseToken = token[1]
+	err := c.CourseService.DeleteCourse(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(nil)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+	
+}
\ No newline at end of file
diff --git a/handler/course/getCourse.go b/handler/course/getCourse.go
new file mode 100644
index 0000000000000000000000000000000000000000..290087fda55ac0219d3ad037f5c3a38415453056
--- /dev/null
+++ b/handler/course/getCourse.go
@@ -0,0 +1,35 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/get"
+)
+
+func (c CourseHandlerImpl) GetCourse(w http.ResponseWriter, r *http.Request) {
+	payload := get.GetByStringRequestPayload{}
+	payload.ID = path.Base(r.URL.Path)
+	
+	packet, err := c.CourseService.GetCourse(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/getCourses.go b/handler/course/getCourses.go
new file mode 100644
index 0000000000000000000000000000000000000000..9280e47c7d96abd7f12d5188cc1c11c6ce927bab
--- /dev/null
+++ b/handler/course/getCourses.go
@@ -0,0 +1,30 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+)
+
+func (c CourseHandlerImpl) GetCourses(w http.ResponseWriter, r *http.Request) {
+	packet, err := c.CourseService.GetAllCourse()
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/getCoursesByFaculty.go b/handler/course/getCoursesByFaculty.go
new file mode 100644
index 0000000000000000000000000000000000000000..a59f6a82342b6f3989c4f7b95585c4e59f1b7ff1
--- /dev/null
+++ b/handler/course/getCoursesByFaculty.go
@@ -0,0 +1,44 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/get"
+)
+
+func (c CourseHandlerImpl) GetCoursesByFaculty(w http.ResponseWriter, r *http.Request) {
+	payload := get.GetByUUIDRequestPayload{}
+	id, err := uuid.Parse(path.Base(r.URL.Path))
+	
+	if err != nil {
+		// invalid uuid
+		payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.ID = id
+	packet, err := c.CourseService.GetAllCourseByFaculty(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/getCoursesByMajor.go b/handler/course/getCoursesByMajor.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f8413871825b2bcc11ad1579f3c82d56b294f58
--- /dev/null
+++ b/handler/course/getCoursesByMajor.go
@@ -0,0 +1,45 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/get"
+)
+
+func (c CourseHandlerImpl) GetCoursesByMajor(w http.ResponseWriter, r *http.Request) {
+	payload := get.GetByUUIDRequestPayload{}
+	id, err := uuid.Parse(path.Base(r.URL.Path))
+	
+	if err != nil {
+		// invalid uuid
+		payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.ID = id
+	packet, err := c.CourseService.GetAllCourseByMajor(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+
+}
\ No newline at end of file
diff --git a/handler/course/getFaculties.go b/handler/course/getFaculties.go
new file mode 100644
index 0000000000000000000000000000000000000000..65473e5b47aa33f10c47f696dd4dd974896773d2
--- /dev/null
+++ b/handler/course/getFaculties.go
@@ -0,0 +1,31 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+)
+
+func (c CourseHandlerImpl) GetFaculties(w http.ResponseWriter, r *http.Request) {
+	packet, err := c.CourseService.GetAllFaculty()
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+	
+}
\ No newline at end of file
diff --git a/handler/course/getFaculty.go b/handler/course/getFaculty.go
new file mode 100644
index 0000000000000000000000000000000000000000..e50d77b17d5ec0482e1b96ee3a9901dc7b58b43b
--- /dev/null
+++ b/handler/course/getFaculty.go
@@ -0,0 +1,44 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/get"
+)
+
+func (c CourseHandlerImpl) GetFaculty(w http.ResponseWriter, r *http.Request) {
+	payload := get.GetByUUIDRequestPayload{}
+	id, err := uuid.Parse(path.Base(r.URL.Path))
+	
+	if err != nil {
+		// invalid uuid
+		payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.ID = id
+	packet, err := c.CourseService.GetFaculty(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/getMajor.go b/handler/course/getMajor.go
new file mode 100644
index 0000000000000000000000000000000000000000..8413ce7f474dc9a6e4259ea3a65fed1ee770cd02
--- /dev/null
+++ b/handler/course/getMajor.go
@@ -0,0 +1,44 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/get"
+)
+
+func (c CourseHandlerImpl) GetMajor(w http.ResponseWriter, r *http.Request) {
+	payload := get.GetByUUIDRequestPayload{}
+	id, err := uuid.Parse(path.Base(r.URL.Path))
+	
+	if err != nil {
+		// invalid uuid
+		payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.ID = id
+	packet, err := c.CourseService.GetMajor(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/getMajors.go b/handler/course/getMajors.go
new file mode 100644
index 0000000000000000000000000000000000000000..05466ce40e6e430b284a0e98f905fd0c14fe3869
--- /dev/null
+++ b/handler/course/getMajors.go
@@ -0,0 +1,31 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+)
+
+func (c CourseHandlerImpl) GetMajors(w http.ResponseWriter, r *http.Request) {
+	packet, err := c.CourseService.GetAllMajor()
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+	
+}
\ No newline at end of file
diff --git a/handler/course/getMajorsByFaculty.go b/handler/course/getMajorsByFaculty.go
new file mode 100644
index 0000000000000000000000000000000000000000..abdedb8b72337808f4f092806784ddcf3bdefd5b
--- /dev/null
+++ b/handler/course/getMajorsByFaculty.go
@@ -0,0 +1,44 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"path"
+
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/get"
+)
+
+func (c CourseHandlerImpl) GetMajorsByFaculty(w http.ResponseWriter, r *http.Request) {
+	payload := get.GetByUUIDRequestPayload{}
+	id, err := uuid.Parse(path.Base(r.URL.Path))
+	
+	if err != nil {
+		// invalid uuid
+		payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.ID = id
+	packet, err := c.CourseService.GetAllMajorByFaculty(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(packet)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/handler.go b/handler/course/handler.go
new file mode 100644
index 0000000000000000000000000000000000000000..b2251fa09b188ffc5b92ba57c9929c7c9ab98104
--- /dev/null
+++ b/handler/course/handler.go
@@ -0,0 +1,17 @@
+package course
+
+import (
+	r "gitlab.informatika.org/ocw/ocw-backend/repository/course"
+	"gitlab.informatika.org/ocw/ocw-backend/service/course"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/httputil"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
+)
+
+type CourseHandlerImpl struct {
+	r.CourseRepository
+	course.CourseService
+	httputil.HttpUtil
+	wrapper.WrapperUtil
+	logger.Logger
+}
diff --git a/handler/course/types.go b/handler/course/types.go
new file mode 100644
index 0000000000000000000000000000000000000000..2398dbaaa5e9c788dbc6ae6b11ac5b1e860280a5
--- /dev/null
+++ b/handler/course/types.go
@@ -0,0 +1,29 @@
+package course
+
+import "net/http"
+
+type CourseHandler interface {
+	// Get
+	GetCourses(w http.ResponseWriter, r *http.Request)
+	GetCourse(w http.ResponseWriter, r *http.Request)
+	GetMajors(w http.ResponseWriter, r *http.Request)
+	GetMajor(w http.ResponseWriter, r *http.Request)
+	GetCoursesByMajor(w http.ResponseWriter, r *http.Request)
+	GetFaculty(w http.ResponseWriter, r *http.Request)
+	GetFaculties(w http.ResponseWriter, r *http.Request)
+	GetCoursesByFaculty(w http.ResponseWriter, r *http.Request)
+	GetMajorsByFaculty(w http.ResponseWriter, r *http.Request)
+
+	// Add (Put)
+	AddCourse(w http.ResponseWriter, r *http.Request)
+	AddMajor(w http.ResponseWriter, r *http.Request)
+	AddFaculty(w http.ResponseWriter, r *http.Request)
+
+	// Update
+	UpdateCourse(w http.ResponseWriter, r *http.Request)
+	UpdateMajor(w http.ResponseWriter, r *http.Request)
+	UpdateFaculty(w http.ResponseWriter, r *http.Request)
+
+	// Delete
+	DeleteCourse(w http.ResponseWriter, r *http.Request)
+}
diff --git a/handler/course/updateCourse.go b/handler/course/updateCourse.go
new file mode 100644
index 0000000000000000000000000000000000000000..92dfef946154ecfd72775d44be63a3bb31bdb505
--- /dev/null
+++ b/handler/course/updateCourse.go
@@ -0,0 +1,86 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/go-playground/validator/v10"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/update"
+)
+
+func (c CourseHandlerImpl) UpdateCourse(w http.ResponseWriter, r *http.Request) {
+	payload := update.UpdateCourseRequestPayload{}
+	validate := validator.New()
+
+	// Validate payload
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := c.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		c.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := c.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		c.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	if err := validate.Struct(payload); err != nil {
+		if _, ok := err.(*validator.InvalidValidationError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+			c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+			return
+		}
+
+		errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors))
+		payload := c.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	// Confirm Valid Website Token
+	validateTokenHeader := r.Header.Get("Authorization")
+
+	if validateTokenHeader == "" {
+		payload := c.WrapperUtil.ErrorResponseWrap("token is required", nil)
+		c.HttpUtil.WriteJson(w, http.StatusForbidden, payload)
+		return
+	}
+
+	token := strings.Split(validateTokenHeader, " ")
+
+	if len(token) != 2 {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	if token[0] != "Bearer" {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.UpdateCourseToken = token[1]
+	err := c.CourseService.UpdateCourse(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(nil)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/updateFaculty.go b/handler/course/updateFaculty.go
new file mode 100644
index 0000000000000000000000000000000000000000..e79c7e51b717479b76ed11e0ff17a80a0f0d52ec
--- /dev/null
+++ b/handler/course/updateFaculty.go
@@ -0,0 +1,86 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/go-playground/validator/v10"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/faculty/update"
+)
+
+func (c CourseHandlerImpl) UpdateFaculty(w http.ResponseWriter, r *http.Request) {
+	payload := update.UpdateFacultyRequestPayload{}
+	validate := validator.New()
+
+	// Validate payload
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := c.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		c.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := c.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		c.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	if err := validate.Struct(payload); err != nil {
+		if _, ok := err.(*validator.InvalidValidationError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+			c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+			return
+		}
+
+		errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors))
+		payload := c.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	// Confirm Valid Website Token
+	validateTokenHeader := r.Header.Get("Authorization")
+
+	if validateTokenHeader == "" {
+		payload := c.WrapperUtil.ErrorResponseWrap("token is required", nil)
+		c.HttpUtil.WriteJson(w, http.StatusForbidden, payload)
+		return
+	}
+
+	token := strings.Split(validateTokenHeader, " ")
+
+	if len(token) != 2 {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	if token[0] != "Bearer" {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.UpdateFacultyToken = token[1]
+	err := c.CourseService.UpdateFaculty(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(nil)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/course/updateMajor.go b/handler/course/updateMajor.go
new file mode 100644
index 0000000000000000000000000000000000000000..b302b6e638f80206d3756f2af292b86373f2658a
--- /dev/null
+++ b/handler/course/updateMajor.go
@@ -0,0 +1,86 @@
+package course
+
+import (
+	"fmt"
+	"net/http"
+	"strings"
+
+	"github.com/go-playground/validator/v10"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/major/update"
+)
+
+func (c CourseHandlerImpl) UpdateMajor(w http.ResponseWriter, r *http.Request) {
+	payload := update.UpdateMajorRequestPayload{}
+	validate := validator.New()
+
+	// Validate payload
+	if r.Header.Get("Content-Type") != "application/json" {
+		payload := c.WrapperUtil.ErrorResponseWrap("this service only receive json input", nil)
+		c.HttpUtil.WriteJson(w, http.StatusUnsupportedMediaType, payload)
+		return
+	}
+
+	if err := c.HttpUtil.ParseJson(r, &payload); err != nil {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid json input", err.Error())
+		c.HttpUtil.WriteJson(w, http.StatusUnprocessableEntity, payload)
+		return
+	}
+
+	if err := validate.Struct(payload); err != nil {
+		if _, ok := err.(*validator.InvalidValidationError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+			c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+			return
+		}
+
+		errPayload := web.NewResponseErrorFromValidator(err.(validator.ValidationErrors))
+		payload := c.WrapperUtil.ErrorResponseWrap(errPayload.Error(), errPayload)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	// Confirm Valid Website Token
+	validateTokenHeader := r.Header.Get("Authorization")
+
+	if validateTokenHeader == "" {
+		payload := c.WrapperUtil.ErrorResponseWrap("token is required", nil)
+		c.HttpUtil.WriteJson(w, http.StatusForbidden, payload)
+		return
+	}
+
+	token := strings.Split(validateTokenHeader, " ")
+
+	if len(token) != 2 {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	if token[0] != "Bearer" {
+		payload := c.WrapperUtil.ErrorResponseWrap("invalid token", nil)
+		c.HttpUtil.WriteJson(w, http.StatusBadRequest, payload)
+		return
+	}
+
+	payload.UpdateMajorToken = token[1]
+	err := c.CourseService.UpdateMajor(payload)
+
+	if err != nil {
+		if errData, ok := err.(web.ResponseError); ok {
+			payload := c.WrapperUtil.ErrorResponseWrap(errData.Error(), errData)
+			c.HttpUtil.WriteJson(w, http.StatusUnauthorized, payload)
+			return
+		}
+
+		c.Logger.Error(
+			fmt.Sprintf("[RESET] some error happened when validating URL: %s", err.Error()),
+		)
+		payload := c.WrapperUtil.ErrorResponseWrap("internal server error", nil)
+		c.HttpUtil.WriteJson(w, http.StatusInternalServerError, payload)
+		return
+	}
+
+	responsePayload := c.WrapperUtil.SuccessResponseWrap(nil)
+	c.HttpUtil.WriteSuccessJson(w, responsePayload)
+}
\ No newline at end of file
diff --git a/handler/di.go b/handler/di.go
index 835b134c53081d8bfb44e80ddda91bb03f7f7eeb..0f3eae80a5bd38faa6a1e07e63e13a9c6c41e11e 100644
--- a/handler/di.go
+++ b/handler/di.go
@@ -5,6 +5,7 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/handler/admin"
 	"gitlab.informatika.org/ocw/ocw-backend/handler/auth"
 	"gitlab.informatika.org/ocw/ocw-backend/handler/common"
+	"gitlab.informatika.org/ocw/ocw-backend/handler/course"
 	"gitlab.informatika.org/ocw/ocw-backend/handler/reset"
 	"gitlab.informatika.org/ocw/ocw-backend/handler/swagger"
 )
@@ -29,4 +30,8 @@ var HandlerSet = wire.NewSet(
 	// Reset
 	wire.Struct(new(reset.ResetHandlerImpl), "*"),
 	wire.Bind(new(reset.ResetHandler), new(*reset.ResetHandlerImpl)),
+
+	// Course
+	wire.Struct(new(course.CourseHandlerImpl), "*"),
+	wire.Bind(new(course.CourseHandler), new(*course.CourseHandlerImpl)),
 )
diff --git a/handler/reset/confirm.go b/handler/reset/confirm.go
index e68ba0568c58bacb594068f183d79c7d5a20f94b..da9420190d78f8c5938860b8bfca3e9375c90c16 100644
--- a/handler/reset/confirm.go
+++ b/handler/reset/confirm.go
@@ -10,6 +10,16 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/reset/confirm"
 )
 
+// Index godoc
+//
+//	@Tags					reset
+//	@Summary			Confirm Reset Password
+//	@Description	Do confirmation to reset password
+//	@Produce			json
+//	@Param			  Authorization header string true "Email validation token"
+//	@Param				data body confirm.ConfirmRequestPayload true "payload"
+//	@Success			200	{object}	web.BaseResponse "Login Success"
+//	@Router				/reset/confirm [put]
 func (rs ResetHandlerImpl) Confirm(w http.ResponseWriter, r *http.Request) {
 	payload := confirm.ConfirmRequestPayload{}
 	validate := validator.New()
@@ -88,4 +98,4 @@ func (rs ResetHandlerImpl) Confirm(w http.ResponseWriter, r *http.Request) {
 
 	responsePayload := rs.WrapperUtil.SuccessResponseWrap(nil)
 	rs.HttpUtil.WriteSuccessJson(w, responsePayload)
-}
\ No newline at end of file
+}
diff --git a/handler/reset/request.go b/handler/reset/request.go
index f2dc0d450e58ca4c26d33f574a403e3e3269c221..e118a0261b97d7bd0dbec2cde86644293d786313 100644
--- a/handler/reset/request.go
+++ b/handler/reset/request.go
@@ -10,6 +10,15 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/reset/request"
 )
 
+// Index godoc
+//
+//	@Tags					reset
+//	@Summary			Request Reset Password Token
+//	@Description	Send Reset password token to email
+//	@Produce			json
+//	@Param				data body request.RequestRequestPayload true "payload"
+//	@Success			200	{object}	web.BaseResponse "Login Success"
+//	@Router				/reset/request [post]
 func (rs ResetHandlerImpl) Request(w http.ResponseWriter, r *http.Request) {
 	payload := request.RequestRequestPayload{}
 	validate := validator.New()
@@ -62,4 +71,4 @@ func (rs ResetHandlerImpl) Request(w http.ResponseWriter, r *http.Request) {
 
 	responsePayload := rs.WrapperUtil.SuccessResponseWrap(nil)
 	rs.HttpUtil.WriteSuccessJson(w, responsePayload)
-}
\ No newline at end of file
+}
diff --git a/handler/reset/validate.go b/handler/reset/validate.go
index 19d48dbfdd2e9cf42e5c874779a1f03c8db100f9..1bfa4dec36c2c1f3f2d2ff94110c80070906c3db 100644
--- a/handler/reset/validate.go
+++ b/handler/reset/validate.go
@@ -9,6 +9,15 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/reset/validate"
 )
 
+// Index godoc
+//
+//	@Tags					reset
+//	@Summary			Request Reset Password Token
+//	@Description	Send Reset password token to email
+//	@Produce			json
+//	@Param			  Authorization header string true "Email validation token"
+//	@Success			200	{object}	web.BaseResponse "Login Success"
+//	@Router				/reset/validate [get]
 func (rs ResetHandlerImpl) Validate(w http.ResponseWriter, r *http.Request) {
 	payload := validate.ValidateRequestPayload{}
 	validateTokenHeader := r.Header.Get("Authorization")
@@ -54,4 +63,4 @@ func (rs ResetHandlerImpl) Validate(w http.ResponseWriter, r *http.Request) {
 	responsePayload := rs.WrapperUtil.SuccessResponseWrap(nil)
 	rs.HttpUtil.WriteSuccessJson(w, responsePayload)
 
-}
\ No newline at end of file
+}
diff --git a/middleware/cors/cors.go b/middleware/cors/cors.go
index 7be187fccb50edb243cd4f9d01b6a9041ebdd170..bfd86033b6d3150080088e3e9fc3999f09d2a5b5 100644
--- a/middleware/cors/cors.go
+++ b/middleware/cors/cors.go
@@ -10,7 +10,7 @@ type CorsMiddleware struct{}
 
 var corsHandler = cors.Handler(cors.Options{
 	AllowedOrigins:   []string{"http://*", "https://*"},
-	AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
+	AllowedMethods:   []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"},
 	AllowedHeaders:   []string{"*"},
 	ExposedHeaders:   []string{"Link"},
 	AllowCredentials: false,
diff --git a/middleware/di.go b/middleware/di.go
index 3e73df4ca24a398c120b42551be10f5b5d1f030c..d83e52f49c9e67f0ad90f29e745108cc6ecb6121 100644
--- a/middleware/di.go
+++ b/middleware/di.go
@@ -4,6 +4,7 @@ import (
 	"github.com/google/wire"
 	"gitlab.informatika.org/ocw/ocw-backend/middleware/cleanpath"
 	"gitlab.informatika.org/ocw/ocw-backend/middleware/cors"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/guard"
 	"gitlab.informatika.org/ocw/ocw-backend/middleware/log"
 	"gitlab.informatika.org/ocw/ocw-backend/middleware/recoverer"
 	"gitlab.informatika.org/ocw/ocw-backend/middleware/trailslash"
@@ -24,6 +25,8 @@ var middlewareCollectionSet = wire.NewSet(
 
 	// Trailslash
 	wire.Struct(new(trailslash.TrailSlashMiddleware), "*"),
+
+	guard.NewBuilder,
 )
 
 var MiddlewareSet = wire.NewSet(
diff --git a/middleware/guard/builder.go b/middleware/guard/builder.go
new file mode 100644
index 0000000000000000000000000000000000000000..75850ebf45a8ef08662c76311156cd2a067a4ff3
--- /dev/null
+++ b/middleware/guard/builder.go
@@ -0,0 +1,43 @@
+package guard
+
+import (
+	"net/http"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/token"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
+)
+
+type GuardBuilder struct {
+	GuardMiddleware
+}
+
+func NewBuilder(
+	token token.TokenUtil,
+	logger logger.Logger,
+	wrapper wrapper.WrapperUtil,
+) *GuardBuilder {
+	return &GuardBuilder{
+		GuardMiddleware{
+			Token:       token,
+			Role:        []user.UserRole{},
+			Logger:      logger,
+			WrapperUtil: wrapper,
+		},
+	}
+}
+
+func (g *GuardBuilder) AddRole(role ...user.UserRole) *GuardBuilder {
+	g.GuardMiddleware.Role = role
+	return g
+}
+
+func (g *GuardBuilder) Build() func(http.Handler) http.Handler {
+	return g.GuardMiddleware.Handle
+}
+
+func (g *GuardBuilder) BuildSimple(role user.UserRole) func(http.Handler) http.Handler {
+	g.AddRole(role)
+	return g.Build()
+}
diff --git a/middleware/guard/guard.go b/middleware/guard/guard.go
new file mode 100644
index 0000000000000000000000000000000000000000..3b71984a870188559f7345793d786ccfce0f1e6d
--- /dev/null
+++ b/middleware/guard/guard.go
@@ -0,0 +1,93 @@
+package guard
+
+import (
+	"context"
+	"encoding/json"
+	"net/http"
+	"strings"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	authToken "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/token"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
+)
+
+type GuardMiddleware struct {
+	Token  token.TokenUtil
+	Role   []user.UserRole
+	Logger logger.Logger
+	wrapper.WrapperUtil
+}
+
+type ContextKey string
+
+const UserContext ContextKey = "user_claim"
+
+func (g GuardMiddleware) Handle(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+
+		if len(g.Role) > 0 {
+			authorization := r.Header.Get("Authorization")
+
+			tokenSplit := strings.Split(authorization, " ")
+
+			if len(tokenSplit) != 2 {
+				g.Logger.Info("Unauthorized access detected")
+
+				w.WriteHeader(http.StatusBadRequest)
+				payload := g.WrapperUtil.ErrorResponseWrap("authorization is required", nil)
+
+				parser := json.NewEncoder(w)
+				parser.Encode(payload)
+				return
+			}
+
+			if tokenSplit[0] != "Bearer" {
+				w.WriteHeader(http.StatusUnprocessableEntity)
+				payload := g.WrapperUtil.ErrorResponseWrap("authorization must be bearer token", nil)
+
+				parser := json.NewEncoder(w)
+				parser.Encode(payload)
+				return
+			}
+
+			tokenString := tokenSplit[1]
+			claim, err := g.Token.Validate(tokenString, authToken.Access)
+
+			if err != nil {
+				g.Logger.Info("Invalid token request")
+				parser := json.NewEncoder(w)
+
+				w.WriteHeader(http.StatusUnauthorized)
+				payload := g.WrapperUtil.ErrorResponseWrap(err.Error(), nil)
+				parser.Encode(payload)
+				return
+			}
+
+			isAuthorized := false
+
+			for _, user := range g.Role {
+				if user == claim.Role {
+					isAuthorized = true
+				}
+			}
+
+			if !isAuthorized {
+				g.Logger.Info("Unauthorized user access")
+				parser := json.NewEncoder(w)
+
+				w.WriteHeader(http.StatusForbidden)
+				payload := g.WrapperUtil.ErrorResponseWrap("current user role is prohibited to access this resources", nil)
+				parser.Encode(payload)
+				return
+			}
+
+			ctx := context.WithValue(r.Context(), UserContext, claim)
+			next.ServeHTTP(w, r.WithContext(ctx))
+			return
+		}
+
+		next.ServeHTTP(w, r)
+	})
+}
diff --git a/model/domain/cache/cache.go b/model/domain/cache/cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc0d3361389b8dc39be807a61dbe54d890fa5577
--- /dev/null
+++ b/model/domain/cache/cache.go
@@ -0,0 +1,60 @@
+package cache
+
+import "fmt"
+
+type String struct {
+	Key             Key
+	Value           string
+	ExpiryInMinutes int
+}
+
+type Hash struct {
+	Key             Key
+	Values          []Value
+	ExpiryInMinutes int
+}
+
+type Key struct {
+	Hash string
+	Id   string
+}
+
+type Value struct {
+	Field string
+	Store string
+}
+
+func (c *Hash) AppendValue(value Value) *Hash {
+	c.Values = append(c.Values, value)
+	return c
+}
+
+func NewKey(hash string, id string) *Key {
+	return &Key{hash, id}
+}
+
+func (k Key) String() string {
+	return fmt.Sprintf("%s:%s", k.Hash, k.Id)
+}
+
+func NewValue(field string, store string) *Value {
+	return &Value{field, store}
+}
+
+func NewString(key Key, value string, expiryInMinutes int) *String {
+	return &String{key, value, expiryInMinutes}
+}
+
+func NewHash(key Key, initValue Value, expiryInMinutes int) *Hash {
+	return &Hash{key, []Value{initValue}, expiryInMinutes}
+}
+
+func (c *Hash) Slice() []interface{} {
+	slice := make([]interface{}, len(c.Values)*2+1)
+	slice[0] = c.Key.String()
+	for i := range make([]int, len(c.Values)) {
+		slice[i*2+1] = c.Values[i].Field
+		slice[i*2+2] = c.Values[i].Store
+	}
+	return slice
+}
diff --git a/model/domain/course/course.go b/model/domain/course/course.go
new file mode 100644
index 0000000000000000000000000000000000000000..3d11918d8bea39471ab99306e5dfda5f5d9de3a1
--- /dev/null
+++ b/model/domain/course/course.go
@@ -0,0 +1,41 @@
+package course
+
+import "github.com/google/uuid"
+
+// TODO: Abbreviations should be unique constrainted as identifiers
+type Faculty struct {
+	ID           uuid.UUID `gorm:"primaryKey"`
+	Name         string
+	Abbreviation string
+}
+
+type Major struct {
+	ID           uuid.UUID `gorm:"primaryKey;type:uuid"`
+	Name         string
+	Fac_id       uuid.UUID `gorm:"type:uuid"`
+	Faculty      Faculty   `gorm:"foreignKey:Fac_id"`
+	Abbreviation string
+}
+
+type Course struct {
+	ID           string `gorm:"primaryKey"`
+	Name         string
+	Major_id     uuid.UUID `gorm:"type:uuid"`
+	Major        Major     `gorm:"foreignKey:Major_id"`
+	Description  string
+	Email        string
+	Abbreviation string
+	Lecturer     string
+}
+
+func (Faculty) TableName() string {
+	return "faculty"
+}
+
+func (Major) TableName() string {
+	return "major"
+}
+
+func (Course) TableName() string {
+	return "course"
+}
diff --git a/model/domain/material/content.go b/model/domain/material/content.go
new file mode 100644
index 0000000000000000000000000000000000000000..0e181045ba1141a363e07b196bf985c9fc0f4adf
--- /dev/null
+++ b/model/domain/material/content.go
@@ -0,0 +1,14 @@
+package material
+
+import "github.com/google/uuid"
+
+type Content struct {
+	Id         uuid.UUID `gorm:"primaryKey"`
+	Type       MaterialType
+	Link       string
+	MaterialId uuid.UUID
+}
+
+func (Content) TableName() string {
+	return "material_data"
+}
diff --git a/model/domain/material/material.go b/model/domain/material/material.go
new file mode 100644
index 0000000000000000000000000000000000000000..55d6bfbf7d002af41a7edc6bdb50beed800650a0
--- /dev/null
+++ b/model/domain/material/material.go
@@ -0,0 +1,20 @@
+package material
+
+import (
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/course"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+)
+
+type Material struct {
+	Id           uuid.UUID `gorm:"primaryKey"`
+	CourseId     string
+	CreatorEmail string
+	Creator      user.User     `gorm:"foreignKey:CreatorEmail;references:Email"`
+	Course       course.Course `gorm:"foreignKey:CourseId;references:Id"`
+	Contents     []Content     `gorm:"foreignKey:MaterialId;references:Id"`
+}
+
+func (Material) TableName() string {
+	return "material"
+}
diff --git a/model/domain/material/material_type.go b/model/domain/material/material_type.go
new file mode 100644
index 0000000000000000000000000000000000000000..10e8abdc730e658f28f99da007bb755fc6896795
--- /dev/null
+++ b/model/domain/material/material_type.go
@@ -0,0 +1,71 @@
+package material
+
+import (
+	"database/sql/driver"
+	"encoding/json"
+	"errors"
+	"fmt"
+)
+
+type MaterialType int
+
+const (
+	Video MaterialType = iota
+	Handout
+	External
+)
+
+var roleMapping = map[MaterialType]string{
+	Video:    "video",
+	Handout:  "handout",
+	External: "external",
+}
+
+func (ur *MaterialType) Scan(value interface{}) error {
+	val := value.(string)
+
+	for key, label := range roleMapping {
+		if label == val {
+			*ur = key
+			return nil
+		}
+	}
+
+	return fmt.Errorf("invalid user role")
+}
+
+func (u MaterialType) Value() (driver.Value, error) {
+	value, ok := roleMapping[u]
+
+	if !ok {
+		return nil, fmt.Errorf("invalid user role")
+	}
+
+	return value, nil
+}
+
+func (u *MaterialType) UnmarshalJSON(b []byte) error {
+	var s string
+	if err := json.Unmarshal(b, &s); err != nil {
+		return err
+	}
+
+	for key, label := range roleMapping {
+		if label == s {
+			*u = key
+			return nil
+		}
+	}
+
+	return fmt.Errorf("unkown role, given %s", s)
+}
+
+func (u MaterialType) MarshalJSON() ([]byte, error) {
+	s, ok := roleMapping[u]
+
+	if !ok {
+		return nil, errors.New("unkown user role")
+	}
+
+	return json.Marshal(s)
+}
diff --git a/model/domain/quiz/options.go b/model/domain/quiz/options.go
new file mode 100644
index 0000000000000000000000000000000000000000..de062c3cc531f34b4572016248e2085a2d1f1d99
--- /dev/null
+++ b/model/domain/quiz/options.go
@@ -0,0 +1,14 @@
+package quiz
+
+import "github.com/google/uuid"
+
+type AnswerOption struct {
+	Id            uuid.UUID `gorm:"primaryKey"`
+	QuizProblemId uuid.UUID `gorm:"primaryKey"`
+	Statement     string
+	IsAnswer      bool
+}
+
+func (AnswerOption) TableName() string {
+	return "quiz_choice_answer"
+}
diff --git a/model/domain/quiz/problem_type.go b/model/domain/quiz/problem_type.go
new file mode 100644
index 0000000000000000000000000000000000000000..c03621526c635dc524f1caee1518665931b80d10
--- /dev/null
+++ b/model/domain/quiz/problem_type.go
@@ -0,0 +1,67 @@
+package quiz
+
+import (
+	"database/sql/driver"
+	"encoding/json"
+	"errors"
+	"fmt"
+)
+
+type ProblemType int
+
+const (
+	Choice ProblemType = iota
+)
+
+var roleMapping = map[ProblemType]string{
+	Choice: "choice",
+}
+
+func (ur *ProblemType) Scan(value interface{}) error {
+	val := value.(string)
+
+	for key, label := range roleMapping {
+		if label == val {
+			*ur = key
+			return nil
+		}
+	}
+
+	return fmt.Errorf("invalid user role")
+}
+
+func (u ProblemType) Value() (driver.Value, error) {
+	value, ok := roleMapping[u]
+
+	if !ok {
+		return nil, fmt.Errorf("invalid user role")
+	}
+
+	return value, nil
+}
+
+func (u *ProblemType) UnmarshalJSON(b []byte) error {
+	var s string
+	if err := json.Unmarshal(b, &s); err != nil {
+		return err
+	}
+
+	for key, label := range roleMapping {
+		if label == s {
+			*u = key
+			return nil
+		}
+	}
+
+	return fmt.Errorf("unkown role, given %s", s)
+}
+
+func (u ProblemType) MarshalJSON() ([]byte, error) {
+	s, ok := roleMapping[u]
+
+	if !ok {
+		return nil, errors.New("unkown user role")
+	}
+
+	return json.Marshal(s)
+}
diff --git a/model/domain/quiz/quiz.go b/model/domain/quiz/quiz.go
new file mode 100644
index 0000000000000000000000000000000000000000..6caade417ae857ec2a78fdf8c13b5ef007a8529a
--- /dev/null
+++ b/model/domain/quiz/quiz.go
@@ -0,0 +1,21 @@
+package quiz
+
+import (
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/course"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+)
+
+type Quiz struct {
+	Id           uuid.UUID `gorm:"primaryKey"`
+	Name         string
+	CourseId     string
+	CreatorEmail string
+	Creator      user.User     `gorm:"foreignKey:CreatorEmail;references:Email"`
+	Course       course.Course `gorm:"foreignKey:CourseId;references:Id"`
+	Problems     []QuizProblem `gorm:"foreignKey:QuizId;references:Id"`
+}
+
+func (Quiz) TableName() string {
+	return "quiz"
+}
diff --git a/model/domain/quiz/quiz_problem.go b/model/domain/quiz/quiz_problem.go
new file mode 100644
index 0000000000000000000000000000000000000000..3ce4195465d2fd477567f18858296d949c60020c
--- /dev/null
+++ b/model/domain/quiz/quiz_problem.go
@@ -0,0 +1,15 @@
+package quiz
+
+import "github.com/google/uuid"
+
+type QuizProblem struct {
+	Id        uuid.UUID `gorm:"primaryKey"`
+	Statement string
+	Type      ProblemType
+	QuizId    uuid.UUID
+	Options   []AnswerOption `gorm:"foreignKey:QuizProblemId;references:Id"`
+}
+
+func (QuizProblem) TableName() string {
+	return "quiz_problem"
+}
diff --git a/model/domain/quiz/take.go b/model/domain/quiz/take.go
new file mode 100644
index 0000000000000000000000000000000000000000..c66263f60f1e4009019e5dcd428e7c905784f243
--- /dev/null
+++ b/model/domain/quiz/take.go
@@ -0,0 +1,24 @@
+package quiz
+
+import (
+	"os/user"
+	"time"
+
+	"github.com/google/uuid"
+)
+
+type QuizTake struct {
+	Id            uuid.UUID `gorm:"primaryKey"`
+	QuizId        uuid.UUID
+	Email         string
+	StartTime     time.Time
+	IsFinished    bool
+	Score         int
+	Quiz          `gorm:"foreignKey:QuizId;references:Id"`
+	user.User     `gorm:"foreignKey:Email;references:Email"`
+	ChoiceAnswers []TakeChoiceAnswer `gorm:"foreignKey:QuizTakeId;references:Id"`
+}
+
+func (QuizTake) TableName() string {
+	return "quiz_take"
+}
diff --git a/model/domain/quiz/take_choice_answer.go b/model/domain/quiz/take_choice_answer.go
new file mode 100644
index 0000000000000000000000000000000000000000..25e76746d8951251d3dbdee694986205ce364d53
--- /dev/null
+++ b/model/domain/quiz/take_choice_answer.go
@@ -0,0 +1,14 @@
+package quiz
+
+import "github.com/google/uuid"
+
+type TakeChoiceAnswer struct {
+	QuizTakeId    uuid.UUID `gorm:"primaryKey"`
+	AnswerChoice  uuid.UUID
+	QuizProblemId uuid.UUID `gorm:"primaryKey"`
+	AnswerOption  `gorm:"foreignKey:AnswerChoice,QuizProblemId;references:Id,QuizProblemId"`
+}
+
+func (TakeChoiceAnswer) TableName() string {
+	return "quiz_take_choice_answer"
+}
diff --git a/model/domain/user/verification.go b/model/domain/user/verification.go
new file mode 100644
index 0000000000000000000000000000000000000000..1d0d04925c079c1f18de8fdb125595aa6ee937f7
--- /dev/null
+++ b/model/domain/user/verification.go
@@ -0,0 +1,7 @@
+package user
+
+type EmailData struct {
+	Email   string
+	BaseUrl string
+	Token   string
+}
diff --git a/model/web/admin/addUser/request.go b/model/web/admin/addUser/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..c08f743abc16843b3c0ca60c3725ac24d348db05
--- /dev/null
+++ b/model/web/admin/addUser/request.go
@@ -0,0 +1,17 @@
+package admin
+
+// AdminAddUserPayload Request Payload
+// @Description Information that should be available when admin add user
+
+// TODO: find a way to make default password for new user
+
+type AdminAddUserPayload struct {
+	// User name
+	Name string `json:"name" validate:"required" example:"someone"`
+
+	// User Email
+	Email string `json:"email" validate:"required,email" example:"someone@example.com"`
+
+	// User Role
+	Role string `json:"role" validate:"required" example:"admin"`
+}
diff --git a/model/web/admin/updateUser/request.go b/model/web/admin/updateUser/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..437d35fa7e7f5f415fea0ab49ec84f09c246ff7e
--- /dev/null
+++ b/model/web/admin/updateUser/request.go
@@ -0,0 +1,15 @@
+package admin
+
+// AdminUpdateUserPayload Request Payload
+// @Description Information that should be available when admin update user
+
+type AdminUpdateUserPayload struct {
+	// User name
+	Name string `json:"name" validate:"required" example:"someone"`
+
+	// User Email
+	Email string `json:"email" validate:"required,email" example:"someone@example.com"`
+
+	// User Role
+	Role string `json:"role" validate:"required" example:"admin"`
+}
diff --git a/model/web/auth/verification/request.go b/model/web/auth/verification/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..dfddab4ba7adb0585a2147daa85ecfbfce77b25e
--- /dev/null
+++ b/model/web/auth/verification/request.go
@@ -0,0 +1,12 @@
+package verification
+
+// Email Verification Request Payload
+// @Description Information that should be passed when request verify
+type VerificationSendRequestPayload struct {
+	// User Email
+	Email string `json:"email" validate:"required,email" example:"someone@example.com"`
+}
+
+type VerificationRequestPayload struct {
+	Id string `json:"id" validate:"required" example:"6ba7b812-9dad-11d1-80b4-00c04fd430c8"`
+}
diff --git a/model/web/course/README.md b/model/web/course/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..98c617f8e3fc02fe20a2b095382b35135357a15f
--- /dev/null
+++ b/model/web/course/README.md
@@ -0,0 +1,28 @@
+### Routes
+
+- Getting:
+course/major - Get all majors
+course/major/{id} - Get major with id
+course/major/courses/{id} - Get all courses in major with id
+course/faculty - Get all faculties
+course/faculty/{id} - Get faculty with id
+course/faculty/courses/{id} - Get all courses in faculty with id
+course/faculty/majors/{id} - Get all majors in faculty with id
+course - get all courses
+course/{id} - get course with id
+
+- Adding:
+course/major - add a major
+course/faculty - add a faculty
+course - add a course
+
+- Updating:
+course/major/{id} - update a major
+course/faculty/{id} - update a faculty
+course/{id} - update a course
+
+- Deleting:
+course/{id} - delete course with id
+
+### Cannot Delete Majors/Faculties ###
+Notes: ID is interchangeable with abbreviation (has OR relationship)
\ No newline at end of file
diff --git a/model/web/course/add/request.go b/model/web/course/add/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..0b86f3ab6618ef4ec1f7f299b0fd45c81bfb61dc
--- /dev/null
+++ b/model/web/course/add/request.go
@@ -0,0 +1,31 @@
+package add
+
+import "github.com/google/uuid"
+
+// AddCourse Request Payload
+// @Description Information that should be available when you add a course
+type AddCourseRequestPayload struct {
+	// Web Token that was appended to the link
+	AddCourseToken string
+
+	// Course ID
+	ID string `json:"id" validate:"required"`
+
+	// Course Name
+	Name string `json:"name" validate:"required"`
+
+	// Course Major Abbreviation
+	MajAbbr string `json:"majabbr" validate:"required_without=MajorID"`
+
+	// Major Id, will be set by the server
+	MajorID uuid.UUID `json:"major_id"`
+
+	// Course Description (Can be left empty)
+	Description string `json:"description"`
+
+	// Contributor Email
+	Email string `json:"email" validate:"required,email" example:"someone@example.com"`
+
+	// Course Name Abbreviation
+	Abbreviation string `json:"abbreviation" validate:"required"`
+}
diff --git a/model/web/course/delete/request.go b/model/web/course/delete/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..fdfcdbc2809b1e2ad428cae959569dc84703b6d3
--- /dev/null
+++ b/model/web/course/delete/request.go
@@ -0,0 +1,11 @@
+package delete
+
+// DeleteCourse Request Payload
+// @Description Information that should be available when you delete using course id (string)
+type DeleteByStringRequestPayload struct {
+	// Web Token that was appended to the link
+	DeleteCourseToken string
+
+	// Course ID, provided by query
+	ID string
+}
diff --git a/model/web/course/faculty/add/request.go b/model/web/course/faculty/add/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..fb7c2da86ab96c2c326748d03704ef7c62e07d39
--- /dev/null
+++ b/model/web/course/faculty/add/request.go
@@ -0,0 +1,14 @@
+package add
+
+// AddFaculty Request Payload
+// @Description Information that should be available when you add a faculty
+type AddFacultyRequestPayload struct {
+	// Web Token that was appended to the link
+	AddFacultyToken string
+
+	// Faculty Name
+	Name string `json:"name" validate:"required"`
+
+	// Faculty Name Abbreviation
+	Abbreviation string `json:"abbreviation" validate:"required"`
+}
diff --git a/model/web/course/faculty/update/request.go b/model/web/course/faculty/update/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..38f04860abe184ab7e61182c538c8a4b39003885
--- /dev/null
+++ b/model/web/course/faculty/update/request.go
@@ -0,0 +1,19 @@
+package update
+
+import "github.com/google/uuid"
+
+// UpdateFaculty Request Payload
+// @Description Information that should be available when you update a faculty
+type UpdateFacultyRequestPayload struct {
+	// Web Token that was appended to the link
+	UpdateFacultyToken string
+
+	// Faculty ID, Provided by Query
+	ID uuid.UUID
+
+	// Faculty Name
+	Name string `json:"name" validate:"required"`
+
+	// Faculty Name Abbreviation
+	Abbreviation string `json:"abbreviation" validate:"required"`
+}
diff --git a/model/web/course/get/request.go b/model/web/course/get/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..7f1853ea4ad2051e145d01cb86b45f68c7804f44
--- /dev/null
+++ b/model/web/course/get/request.go
@@ -0,0 +1,17 @@
+package get
+
+import "github.com/google/uuid"
+
+// GetCourse Request Payload
+// @Description Information that should be available when you get using course id (string)
+type GetByStringRequestPayload struct {
+	// Course ID, provided by query
+	ID string
+}
+
+// GetCourse Request Payload
+// @Description Information that should be available when you get using major/faculty id (string)
+type GetByUUIDRequestPayload struct {
+	// Major/Faculty ID, provided by query
+	ID uuid.UUID
+}
\ No newline at end of file
diff --git a/model/web/course/major/add/request.go b/model/web/course/major/add/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..fbc207644da74f83915b6ac16456d03b9a8efd1d
--- /dev/null
+++ b/model/web/course/major/add/request.go
@@ -0,0 +1,22 @@
+package add
+
+import "github.com/google/uuid"
+
+// AddMajor Request Payload
+// @Description Information that should be available when you add a major
+type AddMajorRequestPayload struct {
+	// Web Token that was appended to the link
+	AddMajorToken string
+
+	// Major Name
+	Name string `json:"name" validate:"required"`
+
+	// Major Faculty Abbreviation
+	FacAbbr string `json:"facabbr" validate:"required_without=FacultyID"`
+
+	// Faculty Id, will be set by the server
+	FacultyID uuid.UUID `json:"faculty_id"`
+
+	// Major Name Abbreviation
+	Abbreviation string `json:"abbreviation" validate:"required"`
+}
diff --git a/model/web/course/major/update/request.go b/model/web/course/major/update/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..b024427a8fdc6ed1d3851d10be75bec67a40aa71
--- /dev/null
+++ b/model/web/course/major/update/request.go
@@ -0,0 +1,25 @@
+package update
+
+import "github.com/google/uuid"
+
+// UpdateMajor Request Payload
+// @Description Information that should be available when you update a major
+type UpdateMajorRequestPayload struct {
+	// Web Token that was appended to the link
+	UpdateMajorToken string
+
+	// Major ID, provided by query
+	ID uuid.UUID
+
+	// Major Name
+	Name string `json:"name" validate:"required"`
+
+	// Major Faculty Abbreviation
+	FacAbbr string `json:"facabbr" validate:"required_without=FacultyID"`
+
+	// Faculty Id, will be set by the server
+	FacultyID uuid.UUID `json:"faculty_id"`
+
+	// Major Name Abbreviation
+	Abbreviation string `json:"abbreviation" validate:"required"`
+}
diff --git a/model/web/course/update/request.go b/model/web/course/update/request.go
new file mode 100644
index 0000000000000000000000000000000000000000..59d2b4c9f91503df99ca15808f7b73d2412e8e07
--- /dev/null
+++ b/model/web/course/update/request.go
@@ -0,0 +1,34 @@
+package update
+
+import "github.com/google/uuid"
+
+// UpdateCourse Request Payload
+// @Description Information that should be available when you add a course
+type UpdateCourseRequestPayload struct {
+	// Web Token that was appended to the link
+	UpdateCourseToken string
+
+	// Course ID, Provided by query
+	ID string
+
+	// Course Name
+	Name string `json:"name" validate:"required"`
+
+	// Course Major Abbreviation
+	MajAbbr string `json:"majabbr" validate:"required_without=MajorID"`
+
+	// Major Id, will be set by the server
+	MajorID uuid.UUID `json:"major_id"`
+
+	// Course Description (Can be left empty)
+	Description string `json:"description"`
+
+	// Contributor Email
+	Email string `json:"email" validate:"required,email" example:"someone@example.com"`
+
+	// Course Name Abbreviation
+	Abbreviation string `json:"abbreviation" validate:"required"`
+
+	// Course Lecturer
+	Lecturer string `json:"lecturer" validate:"required"`
+}
diff --git a/model/web/error_code.go b/model/web/error_code.go
index e610d20e8224716d3e4ff4ea6daa8c66948eb093..9b29b2d14e9caac4d013f7474592e25ff70b66d5 100644
--- a/model/web/error_code.go
+++ b/model/web/error_code.go
@@ -9,6 +9,10 @@ const (
 	EmailExist         string = "EMAIL_EXIST"
 	EmailNotExist      string = "EMAIL_NOT_EXIST"
 	LinkNotAvailable   string = "LINK_NOT_AVAILABLE"
+	FacultyNotExist    string = "FACULTY_NOT_EXIST"
+	MajorNotExist      string = "MAJOR_NOT_EXIST"
+	CourseNotExist     string = "COURSE_NOT_EXIST"
+	IDExists           string = "ID_ALREADY_EXISTS"
 
 	TokenError string = "TOKEN_ERROR"
 )
diff --git a/provider/di.go b/provider/di.go
index 51e100b166816bee6b5d9c23147f9470831a1b9f..08cd4bf4d7c13a2bbccf9ebae7582afc13f2ed3c 100644
--- a/provider/di.go
+++ b/provider/di.go
@@ -5,6 +5,8 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/provider/db"
 	"gitlab.informatika.org/ocw/ocw-backend/provider/mail"
 	"gitlab.informatika.org/ocw/ocw-backend/provider/mail/smtp"
+	"gitlab.informatika.org/ocw/ocw-backend/provider/redis"
+	"gitlab.informatika.org/ocw/ocw-backend/provider/storage"
 )
 
 var ProviderTestSet = wire.NewSet(
@@ -14,6 +16,14 @@ var ProviderTestSet = wire.NewSet(
 
 	wire.Bind(new(mail.MailQueue), new(*mail.MailQueueImpl)),
 	wire.Bind(new(mail.MailProvider), new(*smtp.SmtpMailProvider)),
+
+	// Redis utility
+	wire.Bind(new(redis.Redis), new(*redis.RedisImpl)),
+	redis.NewRedisEnv,
+
+	// Storage utility
+	wire.Bind(new(storage.Storage), new(*storage.S3Storage)),
+	storage.NewS3,
 )
 
 var ProviderSet = wire.NewSet(
diff --git a/provider/mail/smtp/smtp.go b/provider/mail/smtp/smtp.go
index dd2873a17083a7381fa3009679552d7dbd121590..cc1d4060a460ed00840c8f52072ee2ee7b20234f 100644
--- a/provider/mail/smtp/smtp.go
+++ b/provider/mail/smtp/smtp.go
@@ -13,11 +13,9 @@ type SmtpMailProvider struct {
 }
 
 func New(env *env.Environment) *SmtpMailProvider {
-	auth := smtp.PlainAuth(
-		env.SmtpIdentity,
+	auth := smtp.CRAMMD5Auth(
 		env.SmtpUsername,
 		env.SmtpPassword,
-		env.SmtpServer,
 	)
 
 	return &SmtpMailProvider{
@@ -30,6 +28,7 @@ func (s SmtpMailProvider) Send(to []string, subject string, message string) erro
 	payload := fmt.Sprintf(
 		"To: %s\r\n"+
 			"Subject: %s\r\n"+
+			"Content-Type: text/html; charset=UTF-8\r\n"+
 			"\r\n%s\r\n",
 		to, subject, message,
 	)
diff --git a/provider/redis/cache.go b/provider/redis/cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..9d656ae2093191e41cdd8666e7f20f5b2c185a92
--- /dev/null
+++ b/provider/redis/cache.go
@@ -0,0 +1,84 @@
+package redis
+
+import (
+	"fmt"
+	"os"
+	"runtime/debug"
+	"strings"
+	"time"
+
+	"github.com/gomodule/redigo/redis"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+)
+
+type RedisImpl struct {
+	pool *redis.Pool
+}
+
+func resolver(log logger.Logger) {
+	if rec := recover(); rec != nil {
+		log.Error("Some panic occured when processing request:")
+		log.Error(fmt.Sprint(rec))
+		log.Error("")
+
+		log.Error("Stack Trace:")
+		stacks := strings.Split(string(debug.Stack()), "\n")
+
+		for _, val := range stacks {
+			log.Error(val)
+		}
+
+		os.Exit(-1)
+	}
+}
+
+func NewRedisEnv(
+	env *env.Environment,
+	log logger.Logger,
+) (*RedisImpl, error) {
+	return &RedisImpl{
+		&redis.Pool{
+			MaxIdle:     3,
+			IdleTimeout: 240 * time.Second,
+			Dial: func() (redis.Conn, error) {
+				defer resolver(log)
+				conn, err := redis.Dial("tcp", env.RedisConnection+":"+env.RedisPort)
+
+				if err != nil {
+					log.Warning("failed connect to redis server: tcp " + env.RedisConnection + ":" + env.RedisPort)
+					log.Warning(err.Error())
+
+					return nil, err
+				}
+
+				if env.RedisUseAuth {
+					if _, err := conn.Do("AUTH", env.RedisUsername, env.RedisPassword); err != nil {
+						conn.Close()
+
+						log.Warning("failed connect to redis server: authentication failed")
+
+						return nil, err
+					}
+				}
+
+				return conn, err
+			},
+			TestOnBorrow: func(c redis.Conn, t time.Time) error {
+				if time.Since(t) < time.Minute {
+					return nil
+				}
+				_, err := c.Do("PING")
+
+				if err != nil {
+					log.Warning("redis server is down")
+				}
+
+				return err
+			},
+		}}, nil
+}
+
+func (r RedisImpl) Pool() *redis.Pool {
+	return r.pool
+}
diff --git a/provider/redis/type.go b/provider/redis/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..c2b4e2c661a7ce9851e8963e107c7fd063f04c26
--- /dev/null
+++ b/provider/redis/type.go
@@ -0,0 +1,7 @@
+package redis
+
+import "github.com/gomodule/redigo/redis"
+
+type Redis interface {
+	Pool() *redis.Pool
+}
\ No newline at end of file
diff --git a/provider/storage/manager.go b/provider/storage/manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..cc87edd73acda90734470345e5fd3ea1230a1280
--- /dev/null
+++ b/provider/storage/manager.go
@@ -0,0 +1,11 @@
+package storage
+
+import (
+	"context"
+
+	"github.com/minio/minio-go/v7"
+)
+
+func (s S3Storage) Delete(ctx context.Context, path string) error {
+	return s.minio.RemoveObject(ctx, s.env.BucketName, path, minio.RemoveObjectOptions{})
+}
diff --git a/provider/storage/s3.go b/provider/storage/s3.go
new file mode 100644
index 0000000000000000000000000000000000000000..3f746712f3fa34c1ae77ea87fe4491282e4a6609
--- /dev/null
+++ b/provider/storage/s3.go
@@ -0,0 +1,30 @@
+package storage
+
+import (
+	"github.com/minio/minio-go/v7"
+	"github.com/minio/minio-go/v7/pkg/credentials"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+)
+
+type S3Storage struct {
+	env   *env.Environment
+	minio *minio.Client
+}
+
+func NewS3(
+	env *env.Environment,
+) (*S3Storage, error) {
+	client, err := minio.New(env.BucketEndpoint, &minio.Options{
+		Creds:  credentials.NewStaticV4(env.BucketAccessKey, env.BucketSecretKey, env.BucketTokenKey),
+		Secure: env.BucketUseSSL,
+	})
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &S3Storage{
+		minio: client,
+		env:   env,
+	}, nil
+}
diff --git a/provider/storage/signedlink.go b/provider/storage/signedlink.go
new file mode 100644
index 0000000000000000000000000000000000000000..2ebf9f28b5b59a6b0d6a42c44aeeec46ce6b8ce5
--- /dev/null
+++ b/provider/storage/signedlink.go
@@ -0,0 +1,38 @@
+package storage
+
+import (
+	"context"
+	"net/url"
+	"time"
+)
+
+func (s S3Storage) CreatePutSignedLink(ctx context.Context, path string) (string, error) {
+	url, err := s.minio.PresignedPutObject(
+		ctx,
+		s.env.BucketName,
+		path,
+		time.Duration(s.env.BucketSignedPutDuration)*time.Second,
+	)
+
+	if err != nil {
+		return "", err
+	}
+
+	return url.String(), nil
+}
+
+func (s S3Storage) CreateGetSignedLink(ctx context.Context, path string, reqParam url.Values) (string, error) {
+	url, err := s.minio.PresignedGetObject(
+		ctx,
+		s.env.BucketName,
+		path,
+		time.Duration(s.env.BucketSignedGetDuration)*time.Second,
+		reqParam,
+	)
+
+	if err != nil {
+		return "", err
+	}
+
+	return url.String(), nil
+}
diff --git a/provider/storage/type.go b/provider/storage/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..dc7cdd2f97597d8760d26aa2b75791989a12ff9f
--- /dev/null
+++ b/provider/storage/type.go
@@ -0,0 +1,12 @@
+package storage
+
+import (
+	"context"
+	"net/url"
+)
+
+type Storage interface {
+	CreatePutSignedLink(ctx context.Context, path string) (string, error)
+	CreateGetSignedLink(ctx context.Context, path string, reqParam url.Values) (string, error)
+	Delete(ctx context.Context, path string) error
+}
diff --git a/repository/cache/cache.go b/repository/cache/cache.go
new file mode 100644
index 0000000000000000000000000000000000000000..724e4585ec8e0b3c8f60f2be22f0cf625a0d5908
--- /dev/null
+++ b/repository/cache/cache.go
@@ -0,0 +1,141 @@
+package cache
+
+import (
+	"github.com/gomodule/redigo/redis"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
+	rd "gitlab.informatika.org/ocw/ocw-backend/provider/redis"
+)
+
+type CacheRepositoryImpl struct {
+	pool *redis.Pool
+}
+
+func New(
+	cache rd.Redis,
+) *CacheRepositoryImpl {
+	return &CacheRepositoryImpl{cache.Pool()}
+}
+
+func (c CacheRepositoryImpl) Get(key cache.Key) (string, error) {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	value, err := redis.String(conn.Do("GET", key))
+
+	if err != nil {
+		return "", err
+	}
+
+	return value, nil
+}
+
+func (c CacheRepositoryImpl) GetInteger(key cache.Key) (int64, error) {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	value, err := redis.Int64(conn.Do("GET", key))
+
+	if err != nil {
+		return 0, err
+	}
+
+	return value, err
+}
+
+func (c CacheRepositoryImpl) Incr(key string, expr int64) error {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	value, err := redis.Int64(conn.Do("INCR", key))
+
+	if err != nil {
+		return err
+	}
+
+	if value == 1 && expr > 0 {
+		_, err := conn.Do("EXPIRE", key, expr)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (c CacheRepositoryImpl) Delete(key string) error {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	_, err := conn.Do("DEL", key)
+	return err
+}
+
+func (c CacheRepositoryImpl) Set(str cache.String) error {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	_, err := conn.Do("SET", str.Key, str.Value)
+
+	if err != nil {
+		return err
+	}
+
+	if str.ExpiryInMinutes > 0 {
+		_, err = conn.Do("EXPIRE", str.Key, str.ExpiryInMinutes*60)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
+
+func (c CacheRepositoryImpl) HGet(cache cache.Hash, field string) (string, error) {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	value, err := redis.String(conn.Do("HGET", cache.Key.String(), field))
+
+	if err != nil {
+		return "", err
+	}
+
+	return value, nil
+}
+
+func (c CacheRepositoryImpl) HGetAll(cache cache.Hash) (map[string]string, error) {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	value, err := redis.StringMap(conn.Do("HGETALL", cache.Key.String()))
+
+	if err != nil {
+		return nil, err
+	}
+
+	return value, nil
+}
+
+func (c CacheRepositoryImpl) HSet(cache cache.Hash) error {
+	conn := c.pool.Get()
+	defer conn.Close()
+
+	slice := cache.Slice()
+	_, err := conn.Do("HSET", slice...)
+
+	if err != nil {
+		return err
+	}
+
+	if cache.ExpiryInMinutes > 0 {
+		_, err = conn.Do("EXPIRE", cache.Key, cache.ExpiryInMinutes*60)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	return nil
+}
diff --git a/repository/cache/type.go b/repository/cache/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..d482986d93093b7dc4435cfbd8880af9a8e19b1d
--- /dev/null
+++ b/repository/cache/type.go
@@ -0,0 +1,16 @@
+package cache
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
+)
+
+type CacheRepository interface {
+	Get(key cache.Key) (string, error)
+	GetInteger(key cache.Key) (int64, error)
+	Set(str cache.String) error
+	Delete(key string) error
+	HGet(cache cache.Hash, field string) (string, error)
+	HGetAll(cache cache.Hash) (map[string]string, error)
+	HSet(cache cache.Hash) error
+	Incr(key string, expr int64) error
+}
diff --git a/repository/content/impl.go b/repository/content/impl.go
new file mode 100644
index 0000000000000000000000000000000000000000..ca06fd4007d71037180debdb94bd1aa6566b0d67
--- /dev/null
+++ b/repository/content/impl.go
@@ -0,0 +1,27 @@
+package content
+
+import (
+	"context"
+	"net/url"
+
+	"gitlab.informatika.org/ocw/ocw-backend/provider/storage"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+)
+
+type ContentRepositoryImpl struct {
+	storage.Storage
+	*env.Environment
+}
+
+func (c ContentRepositoryImpl) GenerateNewLink(ctx context.Context, path string) (string, error) {
+	return c.Storage.CreatePutSignedLink(ctx, path)
+}
+
+func (c ContentRepositoryImpl) GetLink(ctx context.Context, path string) (string, error) {
+	res, err := c.Storage.CreateGetSignedLink(ctx, path, url.Values{})
+	return res, err
+}
+
+func (c ContentRepositoryImpl) Delete(ctx context.Context, path string) error {
+	return c.Storage.Delete(ctx, path)
+}
diff --git a/repository/content/type.go b/repository/content/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..84f74a0313cfb98fb9934c42b4c13c38ef38b05f
--- /dev/null
+++ b/repository/content/type.go
@@ -0,0 +1,9 @@
+package content
+
+import "context"
+
+type ContentRepository interface {
+	GenerateNewLink(ctx context.Context, path string) (string, error)
+	GetLink(ctx context.Context, path string) (string, error)
+	Delete(ctx context.Context, path string) error
+}
diff --git a/repository/course/course.go b/repository/course/course.go
new file mode 100644
index 0000000000000000000000000000000000000000..6b5d06ee1121b393081d5f4421b41d825614a844
--- /dev/null
+++ b/repository/course/course.go
@@ -0,0 +1,211 @@
+package course
+
+import (
+	"errors"
+
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/course"
+	"gitlab.informatika.org/ocw/ocw-backend/provider/db"
+	"gorm.io/gorm"
+)
+
+type CourseRepositoryImpl struct {
+	db *gorm.DB
+}
+
+func New(
+	db db.Database,
+) *CourseRepositoryImpl {
+	return &CourseRepositoryImpl{db.Connect()}
+}
+
+func (repo CourseRepositoryImpl) IsCourseExist(id string) (bool, error) {
+	_, err := repo.GetCourse(id)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return false, nil
+		}
+
+		return false, err
+	}
+
+	return true, nil
+}
+
+func (repo CourseRepositoryImpl) IsMajorExist(id uuid.UUID) (bool, error) {
+	_, err := repo.GetMajor(id)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return false, nil
+		}
+
+		return false, err
+	}
+
+	return true, nil
+}
+
+func (repo CourseRepositoryImpl) IsFacultyExist(id uuid.UUID) (bool, error) {
+	_, err := repo.GetFaculty(id)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return false, nil
+		}
+
+		return false, err
+	}
+
+	return true, nil
+}
+
+func (repo CourseRepositoryImpl) AddCourse(course course.Course) error {
+	return repo.db.Create(&course).Error
+}
+
+func (repo CourseRepositoryImpl) AddMajor(major course.Major) error {
+	return repo.db.Create(&major).Error
+}
+
+func (repo CourseRepositoryImpl) AddFaculty(faculty course.Faculty) error {
+	return repo.db.Create(&faculty).Error
+}
+
+func (repo CourseRepositoryImpl) GetCourse(id string) (*course.Course, error) {
+	result := &course.Course{}
+	err := repo.db.First(result, "id = ?", id).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetMajor(id uuid.UUID) (*course.Major, error) {
+	result := &course.Major{}
+	err := repo.db.First(result, "id = ?", id).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetFaculty(id uuid.UUID) (*course.Faculty, error) {
+	result := &course.Faculty{}
+	err := repo.db.First(result, "id = ?", id).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetAllCourse() ([]course.Course, error) {
+	var result []course.Course
+	err := repo.db.Find(&result).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetAllMajor() ([]course.Major, error) {
+	var result []course.Major
+	err := repo.db.Find(&result).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetAllFaculty() ([]course.Faculty, error) {
+	var result []course.Faculty
+	err := repo.db.Find(&result).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetAllCourseByMajor(id uuid.UUID) ([]course.Course, error) {
+	var result []course.Course
+	err := repo.db.InnerJoins("Major", repo.db.Where(&course.Major{ID: id})).Find(&result).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetAllCourseByFaculty(id uuid.UUID) ([]course.Course, error) {
+	var result []course.Course
+	err := repo.db.InnerJoins("Faculty", repo.db.Where(&course.Faculty{ID: id})).InnerJoins("Major").Find(&result).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetAllMajorByFaculty(id uuid.UUID) ([]course.Major, error) {
+	var result []course.Major
+	err := repo.db.InnerJoins("Faculty", repo.db.Where(&course.Faculty{ID: id})).Find(&result).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) UpdateCourse(course course.Course) error {
+	return repo.db.Save(course).Error
+}
+
+func (repo CourseRepositoryImpl) UpdateMajor(major course.Major) error {
+	return repo.db.Save(major).Error
+}
+
+func (repo CourseRepositoryImpl) UpdateFaculty(faculty course.Faculty) error {
+	return repo.db.Save(faculty).Error
+}
+
+func (repo CourseRepositoryImpl) DeleteCourse(id string) error {
+	return repo.db.Where("id = ?", id).Delete(&course.Course{}).Error
+}
+
+func (repo CourseRepositoryImpl) GetMajorByAbbr(abbr string) (*course.Major, error) {
+	result := &course.Major{}
+	err := repo.db.First(result, "abbreviation = ?", abbr).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
+
+func (repo CourseRepositoryImpl) GetFacultyByAbbr(abbr string) (*course.Faculty, error) {
+	result := &course.Faculty{}
+	err := repo.db.First(result, "abbreviation = ?", abbr).Error
+
+	if err != nil {
+		return nil, err
+	}
+
+	return result, nil
+}
\ No newline at end of file
diff --git a/repository/course/type.go b/repository/course/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..252eb470be01f16794cbbb37dc3f85fee1ecfca0
--- /dev/null
+++ b/repository/course/type.go
@@ -0,0 +1,33 @@
+package course
+
+import (
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/course"
+)
+
+type CourseRepository interface {
+	AddCourse(course course.Course) error
+	AddMajor(major course.Major) error
+	AddFaculty(faculty course.Faculty) error
+	GetCourse(id string) (*course.Course, error)
+	GetMajor(id uuid.UUID) (*course.Major, error)
+	GetFaculty(id uuid.UUID) (*course.Faculty, error)
+	GetAllCourse() ([]course.Course, error)
+	GetAllMajor() ([]course.Major, error)
+	GetAllFaculty() ([]course.Faculty, error)
+	GetAllCourseByMajor(id uuid.UUID) ([]course.Course, error)
+	GetAllCourseByFaculty(id uuid.UUID) ([]course.Course, error)
+	GetAllMajorByFaculty(id uuid.UUID) ([]course.Major, error)
+	UpdateCourse(course course.Course) error
+	UpdateMajor(major course.Major) error
+	UpdateFaculty(faculty course.Faculty) error
+	DeleteCourse(id string) error
+	IsCourseExist(id string) (bool, error)
+	IsMajorExist(id uuid.UUID) (bool, error)
+	IsFacultyExist(id uuid.UUID) (bool, error)
+
+	// Internal Method Only
+	
+	GetMajorByAbbr(abbr string) (*course.Major, error)
+	GetFacultyByAbbr(abbr string) (*course.Faculty, error)
+}
diff --git a/repository/di.go b/repository/di.go
index ccec9594f14c22bb4d6444b2edbb26f54b8a90f3..9136c031788ba4f56d8d3bfe4c46f2864b641571 100644
--- a/repository/di.go
+++ b/repository/di.go
@@ -3,12 +3,40 @@ package repository
 import (
 	"github.com/google/wire"
 	"gitlab.informatika.org/ocw/ocw-backend/repository/user"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/course"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/cache"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/content"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/material"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/transaction"
 )
 
 var RepositoryBasicSet = wire.NewSet(
 	// User Repository
 	user.New,
 	wire.Bind(new(user.UserRepository), new(*user.UserRepositoryImpl)),
+
+	// Course Repository
+	course.New,
+	wire.Bind(new(course.CourseRepository), new(*course.CourseRepositoryImpl)),
+
+	// Cache Repository
+	cache.New,
+	wire.Bind(new(cache.CacheRepository), new(*cache.CacheRepositoryImpl)),
+
+	material.NewMaterial,
+	material.NewMaterialContent,
+
+	wire.Struct(new(content.ContentRepositoryImpl), "*"),
+
+	wire.Bind(new(material.MaterialContentRepository), new(*material.MaterialContentRepositoryImpl)),
+	wire.Bind(new(material.MaterialRepository), new(*material.MaterialRepositoryImpl)),
+
+	wire.Bind(new(content.ContentRepository), new(*content.ContentRepositoryImpl)),
+
+	transaction.New,
+	transaction.NewBuilder,
+	wire.Bind(new(transaction.Transaction), new(*transaction.TransactionRepositoryImpl)),
+	wire.Bind(new(transaction.TransactionBuilder), new(*transaction.TransactionBuilderImpl)),
 )
 
 var RepositorySet = wire.NewSet(
diff --git a/repository/material/content.go b/repository/material/content.go
new file mode 100644
index 0000000000000000000000000000000000000000..0f321a1b58f824fecb3bfeb5b5ea5b26c331b434
--- /dev/null
+++ b/repository/material/content.go
@@ -0,0 +1,74 @@
+package material
+
+import (
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/material"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/transaction"
+)
+
+type MaterialContentRepositoryImpl struct {
+	builder transaction.TransactionBuilder
+}
+
+func NewMaterialContent(
+	builder transaction.TransactionBuilder,
+) *MaterialContentRepositoryImpl {
+	return &MaterialContentRepositoryImpl{builder}
+}
+
+func (m MaterialContentRepositoryImpl) New(
+	materialId uuid.UUID,
+	materialType material.MaterialType,
+	link string,
+) (uuid.UUID, error) {
+	return m.NewWithTransaction(
+		m.builder.Build(),
+		materialId,
+		materialType,
+		link,
+	)
+}
+
+func (m MaterialContentRepositoryImpl) NewWithTransaction(
+	tx transaction.Transaction,
+	materialId uuid.UUID,
+	materialType material.MaterialType,
+	link string) (uuid.UUID, error) {
+	contentData := material.Content{
+		MaterialId: materialId,
+		Type:       materialType,
+		Link:       link,
+	}
+
+	if err := tx.GetTransaction().Create(&contentData).Error; err != nil {
+		return uuid.Nil, err
+	}
+
+	return contentData.Id, nil
+}
+
+func (m MaterialContentRepositoryImpl) GetAll(materialId uuid.UUID) ([]material.Content, error) {
+	return m.GetAllWithTransaction(m.builder.Build(), materialId)
+}
+func (m MaterialContentRepositoryImpl) GetAllWithTransaction(tx transaction.Transaction, materialId uuid.UUID) ([]material.Content, error) {
+	result := []material.Content{}
+	err := tx.GetTransaction().Where("material_id = ?", materialId).Find(&result).Error
+
+	return result, err
+}
+
+func (m MaterialContentRepositoryImpl) Delete(contentId uuid.UUID) error {
+	return m.DeleteWithTransaction(m.builder.Build(), contentId)
+}
+
+func (m MaterialContentRepositoryImpl) DeleteWithTransaction(tx transaction.Transaction, contentId uuid.UUID) error {
+	return tx.GetTransaction().Where("id = ?", contentId).Delete(&material.Content{}).Error
+}
+
+func (m MaterialContentRepositoryImpl) UpdateLink(contentId uuid.UUID, link string) error {
+	return m.UpdateLinkWithTransaction(m.builder.Build(), contentId, link)
+}
+
+func (m MaterialContentRepositoryImpl) UpdateLinkWithTransaction(tx transaction.Transaction, contentId uuid.UUID, link string) error {
+	return tx.GetTransaction().Where("id = ?", contentId).Update("link", link).Error
+}
diff --git a/repository/material/material.go b/repository/material/material.go
new file mode 100644
index 0000000000000000000000000000000000000000..a20281eab9223275befc37993d0a0a3383e9dc86
--- /dev/null
+++ b/repository/material/material.go
@@ -0,0 +1,55 @@
+package material
+
+import (
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/material"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/transaction"
+)
+
+type MaterialRepositoryImpl struct {
+	builder transaction.TransactionBuilder
+}
+
+func NewMaterial(
+	builder transaction.TransactionBuilder,
+) *MaterialRepositoryImpl {
+	return &MaterialRepositoryImpl{builder}
+}
+
+func (m MaterialRepositoryImpl) New(courseId string, creatorEmail string) (uuid.UUID, error) {
+	return m.NewWithTransaction(m.builder.Build(), courseId, creatorEmail)
+}
+
+func (m MaterialRepositoryImpl) NewWithTransaction(tx transaction.Transaction, courseId string, creatorEmail string) (uuid.UUID, error) {
+	materialData := &material.Material{
+		CourseId:     courseId,
+		CreatorEmail: creatorEmail,
+	}
+
+	err := tx.GetTransaction().Create(materialData).Error
+
+	if err != nil {
+		return uuid.Nil, err
+	}
+
+	return materialData.Id, nil
+}
+
+func (m MaterialRepositoryImpl) Delete(id uuid.UUID) error {
+	return m.DeleteWithTransaction(m.builder.Build(), id)
+}
+
+func (m MaterialRepositoryImpl) DeleteWithTransaction(tx transaction.Transaction, id uuid.UUID) error {
+	return tx.GetTransaction().Where("id = ?", id).Delete(&material.Material{}).Error
+}
+
+func (m MaterialRepositoryImpl) GetAll(courseId string) ([]material.Material, error) {
+	return m.GetAllWithTransaction(m.builder.Build(), courseId)
+}
+
+func (m MaterialRepositoryImpl) GetAllWithTransaction(tx transaction.Transaction, courseId string) ([]material.Material, error) {
+	result := []material.Material{}
+	err := tx.GetTransaction().Joins("Contents").Where("CourseId = ?", courseId).Find(&result).Error
+
+	return result, err
+}
diff --git a/repository/material/type.go b/repository/material/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..8343c4df1757d7f4f585efd43fe3cc18e4f325d4
--- /dev/null
+++ b/repository/material/type.go
@@ -0,0 +1,29 @@
+package material
+
+import (
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/material"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/transaction"
+)
+
+type MaterialRepository interface {
+	New(courseId string, creatorEmail string) (uuid.UUID, error)
+	Delete(id uuid.UUID) error
+	GetAll(courseId string) ([]material.Material, error)
+
+	NewWithTransaction(tx transaction.Transaction, courseId string, creatorEmail string) (uuid.UUID, error)
+	DeleteWithTransaction(tx transaction.Transaction, id uuid.UUID) error
+	GetAllWithTransaction(tx transaction.Transaction, courseId string) ([]material.Material, error)
+}
+
+type MaterialContentRepository interface {
+	New(materialId uuid.UUID, materialType material.MaterialType, link string) (uuid.UUID, error)
+	GetAll(materialId uuid.UUID) ([]material.Content, error)
+	Delete(contentId uuid.UUID) error
+	UpdateLink(contentId uuid.UUID, link string) error
+
+	NewWithTransaction(tx transaction.Transaction, materialId uuid.UUID, materialType material.MaterialType, link string) (uuid.UUID, error)
+	GetAllWithTransaction(tx transaction.Transaction, materialId uuid.UUID) ([]material.Content, error)
+	DeleteWithTransaction(tx transaction.Transaction, contentId uuid.UUID) error
+	UpdateLinkWithTransaction(tx transaction.Transaction, contentId uuid.UUID, link string) error
+}
diff --git a/repository/transaction/builder.go b/repository/transaction/builder.go
new file mode 100644
index 0000000000000000000000000000000000000000..2ec486b728e0861a6af0b9da4c6ad08716791c38
--- /dev/null
+++ b/repository/transaction/builder.go
@@ -0,0 +1,20 @@
+package transaction
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/provider/db"
+	"gorm.io/gorm"
+)
+
+type TransactionBuilderImpl struct {
+	db *gorm.DB
+}
+
+func NewBuilder(
+	db db.Database,
+) *TransactionBuilderImpl {
+	return &TransactionBuilderImpl{db.Connect()}
+}
+
+func (t TransactionBuilderImpl) Build() Transaction {
+	return New(t.db)
+}
diff --git a/repository/transaction/impl.go b/repository/transaction/impl.go
new file mode 100644
index 0000000000000000000000000000000000000000..ccc27ec1ccb42bc7013516cafdbb4a7b8c83f858
--- /dev/null
+++ b/repository/transaction/impl.go
@@ -0,0 +1,36 @@
+package transaction
+
+import "gorm.io/gorm"
+
+type TransactionRepositoryImpl struct {
+	db *gorm.DB
+}
+
+func New(db *gorm.DB) *TransactionRepositoryImpl {
+	return &TransactionRepositoryImpl{db}
+}
+
+func (t *TransactionRepositoryImpl) Begin() {
+	t.db = t.db.Begin()
+}
+
+func (t *TransactionRepositoryImpl) GetTransaction() *gorm.DB {
+	return t.db
+}
+
+func (t *TransactionRepositoryImpl) Commit() {
+	t.db.Commit()
+}
+
+func (t *TransactionRepositoryImpl) Rollback() {
+	t.db.Rollback()
+}
+
+func (t *TransactionRepositoryImpl) Auto(status *bool) {
+	if status != nil && *status {
+		t.Commit()
+		return
+	}
+
+	t.Rollback()
+}
diff --git a/repository/transaction/type.go b/repository/transaction/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..de73f66293ff955b7c9191973a6e3d9e91021262
--- /dev/null
+++ b/repository/transaction/type.go
@@ -0,0 +1,15 @@
+package transaction
+
+import "gorm.io/gorm"
+
+type Transaction interface {
+	Begin()
+	GetTransaction() *gorm.DB
+	Commit()
+	Rollback()
+	Auto(*bool)
+}
+
+type TransactionBuilder interface {
+	Build() Transaction
+}
diff --git a/repository/user/type.go b/repository/user/type.go
index 77304dccc4c5ee44ac66d1e77af7d4144970b7ed..c276adc2c9ddb96cbccf32a8aeaf09542dfdbac7 100644
--- a/repository/user/type.go
+++ b/repository/user/type.go
@@ -2,6 +2,7 @@ package user
 
 import (
 	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/transaction"
 )
 
 type UserRepository interface {
@@ -11,4 +12,11 @@ type UserRepository interface {
 	Update(user user.User) error
 	Delete(username string) error
 	IsExist(user string) (bool, error)
+
+	AddWithTransaction(tx transaction.Transaction, user user.User) error
+	GetWithTransaction(tx transaction.Transaction, username string) (*user.User, error)
+	GetAllWithTransaction(tx transaction.Transaction) ([]user.User, error)
+	UpdateWithTransaction(tx transaction.Transaction, user user.User) error
+	DeleteWithTransaction(tx transaction.Transaction, username string) error
+	IsExistWithTransaction(tx transaction.Transaction, user string) (bool, error)
 }
diff --git a/repository/user/user.go b/repository/user/user.go
index f68ccb7032f192d474a767cb485002437151260a..4bcd060691690f314921f9c920bd47993c3abe6d 100644
--- a/repository/user/user.go
+++ b/repository/user/user.go
@@ -4,21 +4,25 @@ import (
 	"errors"
 
 	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
-	"gitlab.informatika.org/ocw/ocw-backend/provider/db"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/transaction"
 	"gorm.io/gorm"
 )
 
 type UserRepositoryImpl struct {
-	db *gorm.DB
+	builder transaction.TransactionBuilder
 }
 
 func New(
-	db db.Database,
+	builder transaction.TransactionBuilder,
 ) *UserRepositoryImpl {
-	return &UserRepositoryImpl{db.Connect()}
+	return &UserRepositoryImpl{builder}
 }
 
 func (repo UserRepositoryImpl) IsExist(email string) (bool, error) {
+	return repo.IsExistWithTransaction(repo.builder.Build(), email)
+}
+
+func (repo UserRepositoryImpl) IsExistWithTransaction(tx transaction.Transaction, email string) (bool, error) {
 	_, err := repo.Get(email)
 
 	if err != nil {
@@ -33,12 +37,20 @@ func (repo UserRepositoryImpl) IsExist(email string) (bool, error) {
 }
 
 func (repo UserRepositoryImpl) Add(user user.User) error {
-	return repo.db.Create(&user).Error
+	return repo.AddWithTransaction(repo.builder.Build(), user)
+}
+
+func (repo UserRepositoryImpl) AddWithTransaction(tx transaction.Transaction, user user.User) error {
+	return tx.GetTransaction().Create(&user).Error
 }
 
 func (repo UserRepositoryImpl) Get(email string) (*user.User, error) {
+	return repo.GetWithTransaction(repo.builder.Build(), email)
+}
+
+func (repo UserRepositoryImpl) GetWithTransaction(tx transaction.Transaction, email string) (*user.User, error) {
 	result := &user.User{}
-	err := repo.db.Where("email = ?", email).First(result).Error
+	err := tx.GetTransaction().Where("email = ?", email).First(result).Error
 
 	if err != nil {
 		return nil, err
@@ -48,8 +60,11 @@ func (repo UserRepositoryImpl) Get(email string) (*user.User, error) {
 }
 
 func (repo UserRepositoryImpl) GetAll() ([]user.User, error) {
+	return repo.GetAllWithTransaction(repo.builder.Build())
+}
+func (repo UserRepositoryImpl) GetAllWithTransaction(tx transaction.Transaction) ([]user.User, error) {
 	var result []user.User
-	err := repo.db.Find(&result).Error
+	err := tx.GetTransaction().Find(&result).Error
 
 	if err != nil {
 		return nil, err
@@ -59,9 +74,17 @@ func (repo UserRepositoryImpl) GetAll() ([]user.User, error) {
 }
 
 func (repo UserRepositoryImpl) Update(user user.User) error {
-	return repo.db.Save(user).Error
+	return repo.UpdateWithTransaction(repo.builder.Build(), user)
+}
+
+func (repo UserRepositoryImpl) UpdateWithTransaction(tx transaction.Transaction, user user.User) error {
+	return tx.GetTransaction().Save(user).Error
+}
+
+func (repo UserRepositoryImpl) Delete(email string) error {
+	return repo.DeleteWithTransaction(repo.builder.Build(), email)
 }
 
-func (repo UserRepositoryImpl) Delete(username string) error {
-	return repo.db.Where("username = ?", username).Delete(&user.User{}).Error
+func (repo UserRepositoryImpl) DeleteWithTransaction(tx transaction.Transaction, email string) error {
+	return tx.GetTransaction().Where("email = ?", email).Delete(&user.User{}).Error
 }
diff --git a/routes/admin/route.go b/routes/admin/route.go
index 01f19943524626adb4aea1b73f5a912d4fdee3fa..d1ff589b86faa181e6e671bf7111ab3efed0b239 100644
--- a/routes/admin/route.go
+++ b/routes/admin/route.go
@@ -3,18 +3,23 @@ package admin
 import (
 	"github.com/go-chi/chi/v5"
 	"gitlab.informatika.org/ocw/ocw-backend/handler/admin"
+	"gitlab.informatika.org/ocw/ocw-backend/middleware/guard"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
 )
 
 type AdminRoutes struct {
 	admin.AdminHandler
+	*guard.GuardBuilder
 }
 
 func (adr AdminRoutes) Register(r chi.Router) {
 	r.Route("/admin", func(r chi.Router) {
+		r.Use(adr.GuardBuilder.BuildSimple(user.Admin))
+
 		r.Get("/user", adr.AdminHandler.GetAllUser)
-		r.Get("/user/{id}", adr.AdminHandler.GetUserByEmail)
+		r.Get("/user/{email}", adr.AdminHandler.GetUserByEmail)
 		r.Post("/user", adr.AdminHandler.AddUser)
-		r.Patch("/user/{id}", adr.AdminHandler.UpdateUser)
-		r.Delete("/user/{id}", adr.AdminHandler.DeleteUser)
+		r.Patch("/user/{email}", adr.AdminHandler.UpdateUser)
+		r.Delete("/user/{email}", adr.AdminHandler.DeleteUser)
 	})
 }
diff --git a/routes/auth/route.go b/routes/auth/route.go
index d7ea811b69fd604111f9a013b7821a10ef005619..e81bb261069b4abc287c84ec793901ca42a9529e 100644
--- a/routes/auth/route.go
+++ b/routes/auth/route.go
@@ -14,5 +14,7 @@ func (ar AuthRoutes) Register(r chi.Router) {
 		r.Post("/login", ar.AuthHandler.Login)
 		r.Post("/refresh", ar.AuthHandler.Refresh)
 		r.Post("/register", ar.AuthHandler.Register)
+		r.Post("/verify/resend", ar.AuthHandler.SendEmailVerify)
+		r.Post("/verify", ar.AuthHandler.EmailVerify)
 	})
 }
diff --git a/routes/course/route.go b/routes/course/route.go
new file mode 100644
index 0000000000000000000000000000000000000000..6fb5f6ac2019b24687bbc28d593011846bed1057
--- /dev/null
+++ b/routes/course/route.go
@@ -0,0 +1,38 @@
+package course
+
+import (
+	"github.com/go-chi/chi/v5"
+	"gitlab.informatika.org/ocw/ocw-backend/handler/course"
+)
+
+type CourseRoutes struct {
+	course.CourseHandler
+}
+
+func (c CourseRoutes) Register(r chi.Router) {
+	r.Route("/course", func(r chi.Router) {
+		// Get
+		r.Get("/", c.CourseHandler.GetCourses)
+		r.Get("/{id}", c.CourseHandler.GetCourse)
+		r.Get("/faculty", c.CourseHandler.GetFaculties)
+		r.Get("/faculty/{id}", c.CourseHandler.GetFaculty)
+		r.Get("/faculty/courses/{id}", c.CourseHandler.GetCoursesByFaculty)
+		r.Get("/faculty/majors/{id}", c.CourseHandler.GetMajorsByFaculty)
+		r.Get("/major", c.CourseHandler.GetMajors)
+		r.Get("/major/{id}", c.CourseHandler.GetMajor)
+		r.Get("/major/courses/{id}", c.CourseHandler.GetCoursesByMajor)
+
+		// Add
+		r.Put("/", c.CourseHandler.AddCourse)
+		r.Put("/faculty", c.CourseHandler.AddFaculty)
+		r.Put("/major", c.CourseHandler.AddMajor)
+
+		// Update
+		r.Patch("/{id}", c.CourseHandler.UpdateCourse)
+		r.Patch("/faculty/{id}", c.CourseHandler.UpdateFaculty)
+		r.Patch("/major/{id}", c.CourseHandler.UpdateMajor)
+
+		// Delete
+		r.Delete("/{id}", c.CourseHandler.DeleteCourse)
+	})
+}
diff --git a/routes/di.go b/routes/di.go
index b44b2ed0ae71dda868755ffdf82ecf3c636a7a1e..a51997ed13bc22c420ab390bcd1c46de58b20bce 100644
--- a/routes/di.go
+++ b/routes/di.go
@@ -5,6 +5,7 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/routes/admin"
 	"gitlab.informatika.org/ocw/ocw-backend/routes/auth"
 	"gitlab.informatika.org/ocw/ocw-backend/routes/common"
+	"gitlab.informatika.org/ocw/ocw-backend/routes/course"
 	"gitlab.informatika.org/ocw/ocw-backend/routes/reset"
 	"gitlab.informatika.org/ocw/ocw-backend/routes/swagger"
 )
@@ -15,6 +16,7 @@ var routesCollectionSet = wire.NewSet(
 	wire.Struct(new(auth.AuthRoutes), "*"),
 	wire.Struct(new(admin.AdminRoutes), "*"),
 	wire.Struct(new(reset.ResetRoutes), "*"),
+	wire.Struct(new(course.CourseRoutes), "*"),
 )
 
 var RoutesSet = wire.NewSet(
diff --git a/routes/register.go b/routes/register.go
index e1dd4a461fcdb80acf500d362e3fe89b083c2151..3a55652c12ea690b9bf40c9b5acf22f9a48e2e9f 100644
--- a/routes/register.go
+++ b/routes/register.go
@@ -21,7 +21,7 @@ func (app *AppRouter) Register() []RouteGroup {
 		}
 
 		collections = append(collections, handler)
-	}
+  }
 
 	return collections
 }
diff --git a/routes/reset/route.go b/routes/reset/route.go
index 46b32e0f9f389bfe8f3ff730d2c683225dfcee91..c30cdeb5afa69c522fd8f0906d6acc7c9f1f77d8 100644
--- a/routes/reset/route.go
+++ b/routes/reset/route.go
@@ -12,7 +12,7 @@ type ResetRoutes struct {
 func (rr ResetRoutes) Register(r chi.Router) {
 	r.Route("/reset", func(r chi.Router) {
 		r.Post("/request", rr.ResetHandler.Request)
-		r.Post("/confirm", rr.ResetHandler.Confirm)
-		r.Post("/validate", rr.ResetHandler.Validate)
+		r.Put("/confirm", rr.ResetHandler.Confirm)
+		r.Get("/validate", rr.ResetHandler.Validate)
 	})
 }
diff --git a/routes/routes.go b/routes/routes.go
index 4c358d7a37e29d0c2e62fb603f5d5aa70469f061..da4d5b4a4f8efd320ee9b8151f0b8ae4cf4a3757 100644
--- a/routes/routes.go
+++ b/routes/routes.go
@@ -4,6 +4,7 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/routes/admin"
 	"gitlab.informatika.org/ocw/ocw-backend/routes/auth"
 	"gitlab.informatika.org/ocw/ocw-backend/routes/common"
+	"gitlab.informatika.org/ocw/ocw-backend/routes/course"
 	"gitlab.informatika.org/ocw/ocw-backend/routes/reset"
 	"gitlab.informatika.org/ocw/ocw-backend/routes/swagger"
 
@@ -17,6 +18,7 @@ type AppRouter struct {
 	common.CommonRoutes
 	auth.AuthRoutes
 	reset.ResetRoutes
+	course.CourseRoutes
 
 	// Utility
 	Logger logger.Logger
diff --git a/service/admin/addUser.go b/service/admin/addUser.go
index 644bde9d0547485f4eb3ef45a4876d12327550c6..93e53b0a0cccb8921ec5a00b742c6d1b00df9c93 100644
--- a/service/admin/addUser.go
+++ b/service/admin/addUser.go
@@ -1,16 +1,29 @@
 package admin
 
-// import (
-	// "errors"
-	// "time"
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	req "gitlab.informatika.org/ocw/ocw-backend/model/web/admin/addUser"
+)
 
-	// "github.com/golang-jwt/jwt/v4"
-	// "gitlab.informatika.org/ocw/ocw-backend/model/web"
-	// "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/login"
-	// tokenModel "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
-	// "gorm.io/gorm"
-// )
+func (as AdminServiceImpl) AddUser(payload req.AdminAddUserPayload) error {
+	// change role payload from string to user.UserRole
+	var role user.UserRole
 
-func (AdminServiceImpl) AddUser() string {
-	return "add user"
+	// TODO: move this
+	if (payload.Role == "admin") {
+		role = user.Admin
+	} else if (payload.Role == "contributor") {
+		role = user.Contributor
+	} else if (payload.Role == "member") {
+		role = user.Student
+	}
+
+	err := as.UserRepository.Add(user.User{
+		Email:       payload.Email,
+		Name:        payload.Name,
+		Role:        role,
+		IsActivated: false,
+	})
+
+	return err
 }
\ No newline at end of file
diff --git a/service/admin/deleteUser.go b/service/admin/deleteUser.go
index 0e34cf7227863d1865c5de527b1bfa12806c8573..2ed1a1dc451ed20ce9b9667a0722aa3dbd40cc96 100644
--- a/service/admin/deleteUser.go
+++ b/service/admin/deleteUser.go
@@ -1,16 +1,6 @@
 package admin
 
-// import (
-	// "errors"
-	// "time"
-
-	// "github.com/golang-jwt/jwt/v4"
-	// "gitlab.informatika.org/ocw/ocw-backend/model/web"
-	// "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/login"
-	// tokenModel "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
-	// "gorm.io/gorm"
-// )
-
-func (AdminServiceImpl) DeleteUser() string {
-	return "delete user"
-}
+func (as AdminServiceImpl) DeleteUser(email string) error {
+	err := as.UserRepository.Delete(email)
+	return err
+}
\ No newline at end of file
diff --git a/service/admin/getUserByEmail.go b/service/admin/getUserByEmail.go
index e577536e5acccc895a4861a6845d9cd3b13018d8..3c43845eec62c4fac3ae33486233c8aa118bbe87 100644
--- a/service/admin/getUserByEmail.go
+++ b/service/admin/getUserByEmail.go
@@ -1,16 +1,11 @@
 package admin
 
-// import (
-	// "errors"
-	// "time"
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+)
 
-	// "github.com/golang-jwt/jwt/v4"
-	// "gitlab.informatika.org/ocw/ocw-backend/model/web"
-	// "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/login"
-	// tokenModel "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
-	// "gorm.io/gorm"
-// )
-
-func (AdminServiceImpl) GetUserByEmail() string {
-	return "get user by email"
+func (as AdminServiceImpl) GetUserByEmail(email string) (*user.User, error) {
+	var users *user.User
+	users, nil := as.UserRepository.Get(email)
+	return users, nil
 }
\ No newline at end of file
diff --git a/service/admin/impl.go b/service/admin/impl.go
index 7bc0dd3364dc3f46e47d282c647bb26380d4a31c..7c1dc75819f6423ab8a5a15678526df81f0de402 100644
--- a/service/admin/impl.go
+++ b/service/admin/impl.go
@@ -6,4 +6,4 @@ import (
 
 type AdminServiceImpl struct {
 	UserRepository user.UserRepository
-}
\ No newline at end of file
+}
diff --git a/service/admin/type.go b/service/admin/type.go
index d624c56274802d9fc9523aa092d21f1fed14a314..228b5674efdb4758be6fc0c80585eb698c9a95f2 100644
--- a/service/admin/type.go
+++ b/service/admin/type.go
@@ -2,12 +2,14 @@ package admin
 
 import (
 	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	addUser "gitlab.informatika.org/ocw/ocw-backend/model/web/admin/addUser"
+	updateUser "gitlab.informatika.org/ocw/ocw-backend/model/web/admin/updateUser"
 )
 
 type AdminService interface {
 	GetAllUser() ([]user.User, error)
-	GetUserByEmail() string
-	AddUser() string
-	UpdateUser() string
-	DeleteUser() string
+	GetUserByEmail(email string) (*user.User, error)
+	AddUser(payload addUser.AdminAddUserPayload) error
+	UpdateUser(email string, payload updateUser.AdminUpdateUserPayload) error
+	DeleteUser(email string) error
 }
diff --git a/service/admin/updateUser.go b/service/admin/updateUser.go
index e3bce672ae924615b9eb4df9ebda6d12c8afad00..859f7d878195d925aa03852c29155887a099ad79 100644
--- a/service/admin/updateUser.go
+++ b/service/admin/updateUser.go
@@ -1,16 +1,29 @@
 package admin
 
-// import (
-	// "errors"
-	// "time"
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	req "gitlab.informatika.org/ocw/ocw-backend/model/web/admin/updateUser"
+)
 
-	// "github.com/golang-jwt/jwt/v4"
-	// "gitlab.informatika.org/ocw/ocw-backend/model/web"
-	// "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/login"
-	// tokenModel "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
-	// "gorm.io/gorm"
-// )
+func (as AdminServiceImpl) UpdateUser(email string, payload req.AdminUpdateUserPayload) error {
+	// change role payload from string to user.UserRole
+	var role user.UserRole
 
-func (AdminServiceImpl) UpdateUser() string {
-	return "update user"
+	// TODO: move this
+	if (payload.Role == "admin") {
+		role = user.Admin
+	} else if (payload.Role == "contributor") {
+		role = user.Contributor
+	} else if (payload.Role == "member") {
+		role = user.Student
+	}
+
+	err := as.UserRepository.Update(user.User{
+		Email:       payload.Email,
+		Name:        payload.Name,
+		Role:        role, // TODO: Change this
+		IsActivated: false,
+	})
+
+	return err
 }
\ No newline at end of file
diff --git a/service/auth/register.go b/service/auth/register.go
index 6eb423b39900bb2502234c2558b0414a4e50f10c..b7941c50ba99c941bd7810ad82f3c3862f9ef26c 100644
--- a/service/auth/register.go
+++ b/service/auth/register.go
@@ -6,9 +6,15 @@ import (
 )
 
 func (auth AuthServiceImpl) Register(payload register.RegisterRequestPayload) error {
-	err := auth.UserRepository.Add(user.User{
+	hashedPassword, err := auth.Hash(payload.Password)
+
+	if err != nil {
+		return err
+	}
+
+	err = auth.UserRepository.Add(user.User{
 		Email:       payload.Email,
-		Password:    payload.Password,
+		Password:    hashedPassword,
 		Name:        payload.Name,
 		Role:        user.Student,
 		IsActivated: false,
diff --git a/service/auth/type.go b/service/auth/type.go
index 2503726fe0562e9b2b26544dabae60017d13c386..f2eda67ef0abd2fa3a89f9a8249c8bd88a89af0b 100644
--- a/service/auth/type.go
+++ b/service/auth/type.go
@@ -4,10 +4,12 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/login"
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/refresh"
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/register"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/verification"
 )
 
 type AuthService interface {
 	Login(payload login.LoginRequestPayload) (*login.LoginResponsePayload, error)
 	Refresh(payload refresh.RefreshRequestPayload) (*refresh.RefreshResponsePayload, error)
 	Register(payload register.RegisterRequestPayload) error
+	SendVerifyEmail(payload verification.VerificationSendRequestPayload) error
 }
diff --git a/service/auth/verify.go b/service/auth/verify.go
new file mode 100644
index 0000000000000000000000000000000000000000..339078a7b6838b6264a9acb5c03b820063de0e47
--- /dev/null
+++ b/service/auth/verify.go
@@ -0,0 +1,7 @@
+package auth
+
+import "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/verification"
+
+func (auth AuthServiceImpl) SendVerifyEmail(payload verification.VerificationSendRequestPayload) error {
+	return auth.VerificationService.SendVerifyMail(payload.Email)
+}
diff --git a/service/course/add.go b/service/course/add.go
new file mode 100644
index 0000000000000000000000000000000000000000..531d0ec4410bfa1fc3b87de40826ed94d93f3c91
--- /dev/null
+++ b/service/course/add.go
@@ -0,0 +1,141 @@
+package course
+
+import (
+	"errors"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/course"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
+	cadd "gitlab.informatika.org/ocw/ocw-backend/model/web/course/add"
+	fadd "gitlab.informatika.org/ocw/ocw-backend/model/web/course/faculty/add"
+	madd "gitlab.informatika.org/ocw/ocw-backend/model/web/course/major/add"
+	"gorm.io/gorm"
+)
+
+func (c CourseServiceImpl) AddCourse(payload cadd.AddCourseRequestPayload) error {
+	// Validate Role
+	claim, err := c.TokenUtil.Validate(payload.AddCourseToken, token.Access)
+
+	// Invalid Token
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Unauthorized Role
+	if claim.Role == user.Student {
+		return web.NewResponseErrorFromError(err, web.UnauthorizedAccess)
+	}
+
+	// If payload uses major abbreviation, set id
+	if payload.MajAbbr != "" {
+		major, err := c.CourseRepository.GetMajorByAbbr(payload.MajAbbr)
+
+		if err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				return web.NewResponseErrorFromError(err, web.CourseNotExist)
+			}
+			// Some Uncaught error
+			return err
+		}
+
+		payload.MajorID = major.ID
+	}
+
+	exist, err := c.CourseRepository.IsCourseExist(payload.ID)
+
+	if err != nil {
+		// Some uncaught error
+		return err
+	}
+
+	if exist {
+		return web.NewResponseError("Course ID Already Exists", web.IDExists)
+	}
+
+	err = c.CourseRepository.AddCourse(course.Course{
+		ID:           payload.ID,
+		Name:         payload.Name,
+		Major_id:     payload.MajorID,
+		Description:  payload.Description,
+		Email:        payload.Email,
+		Abbreviation: payload.Abbreviation,
+	})
+
+	if err != nil {
+		// Some uncaught error
+		return err
+	}
+
+	return nil
+}
+
+func (c CourseServiceImpl) AddMajor(payload madd.AddMajorRequestPayload) error {
+	// Validate Role
+	claim, err := c.TokenUtil.Validate(payload.AddMajorToken, token.Access)
+
+	// Invalid Token
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Unauthorized Role
+	if claim.Role != user.Admin {
+		return web.NewResponseErrorFromError(err, web.UnauthorizedAccess)
+	}
+
+	// If payload uses faculty abbreviation, set id
+	if payload.FacAbbr != "" {
+		faculty, err := c.CourseRepository.GetFacultyByAbbr(payload.FacAbbr)
+
+		if err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				return web.NewResponseErrorFromError(err, web.CourseNotExist)
+			}
+			// Some Uncaught error
+			return err
+		}
+
+		payload.FacultyID = faculty.ID
+	}
+
+	err = c.CourseRepository.AddMajor(course.Major{
+		Name:         payload.Name,
+		Fac_id:       payload.FacultyID,
+		Abbreviation: payload.Abbreviation,
+	})
+
+	if err != nil {
+		// Some uncaught error
+		return err
+	}
+
+	return nil
+}
+
+func (c CourseServiceImpl) AddFaculty(payload fadd.AddFacultyRequestPayload) error {
+	// Validate Role
+	claim, err := c.TokenUtil.Validate(payload.AddFacultyToken, token.Access)
+
+	// Invalid Token
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Unauthorized Role
+	if claim.Role != user.Admin {
+		return web.NewResponseErrorFromError(err, web.UnauthorizedAccess)
+	}
+	
+	err = c.CourseRepository.AddFaculty(course.Faculty{
+		Name:         payload.Name,
+		Abbreviation: payload.Abbreviation,
+	})
+
+	if err != nil {
+		// Some uncaught error
+		return err
+	}
+
+	return nil
+}
diff --git a/service/course/delete.go b/service/course/delete.go
new file mode 100644
index 0000000000000000000000000000000000000000..a8bda5bc6baf2331980b2b7056282b34b2b272d8
--- /dev/null
+++ b/service/course/delete.go
@@ -0,0 +1,34 @@
+package course
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/delete"
+)
+
+// TODO: Authorization Checks
+
+func (c CourseServiceImpl) DeleteCourse(payload delete.DeleteByStringRequestPayload) error {
+	// Validate Role
+	claim, err := c.TokenUtil.Validate(payload.DeleteCourseToken, token.Access)
+
+	// Invalid Token
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Unauthorized Role
+	if claim.Role == user.Student {
+		return web.NewResponseErrorFromError(err, web.UnauthorizedAccess)
+	}
+
+	err = c.CourseRepository.DeleteCourse(payload.ID)
+
+	if err != nil {
+		// Uncaught error
+		return err
+	}
+
+	return nil
+}
\ No newline at end of file
diff --git a/service/course/get.go b/service/course/get.go
new file mode 100644
index 0000000000000000000000000000000000000000..001217d32d6604eae10f6150b1519935e2ffbe7a
--- /dev/null
+++ b/service/course/get.go
@@ -0,0 +1,137 @@
+package course
+
+import (
+	"errors"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/course"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/get"
+	"gorm.io/gorm"
+)
+
+func (c CourseServiceImpl) GetCourse(payload get.GetByStringRequestPayload) (*course.Course, error) {
+	packet, err := c.CourseRepository.GetCourse(payload.ID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.CourseNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
+
+func (c CourseServiceImpl) GetMajor(payload get.GetByUUIDRequestPayload) (*course.Major, error) {
+	packet, err := c.CourseRepository.GetMajor(payload.ID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.MajorNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
+
+func (c CourseServiceImpl) GetFaculty(payload get.GetByUUIDRequestPayload) (*course.Faculty, error) {
+	packet, err := c.CourseRepository.GetFaculty(payload.ID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.FacultyNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
+
+func (c CourseServiceImpl) GetAllCourse() ([]course.Course, error) {
+	packet, err := c.CourseRepository.GetAllCourse()
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.CourseNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
+
+func (c CourseServiceImpl) GetAllMajor() ([]course.Major, error) {
+	packet, err := c.CourseRepository.GetAllMajor()
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.MajorNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
+
+func (c CourseServiceImpl) GetAllFaculty() ([]course.Faculty, error) {
+	packet, err := c.CourseRepository.GetAllFaculty()
+
+	if err != nil {
+		// This should not happen unless data is unpopulated, faculty is the root dependency so there should be at least one
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.FacultyNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
+
+func (c CourseServiceImpl) GetAllCourseByMajor(payload get.GetByUUIDRequestPayload) ([]course.Course, error) {
+	packet, err := c.CourseRepository.GetAllCourseByMajor(payload.ID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.CourseNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
+
+func (c CourseServiceImpl) GetAllCourseByFaculty(payload get.GetByUUIDRequestPayload) ([]course.Course, error) {
+	packet, err := c.CourseRepository.GetAllCourseByFaculty(payload.ID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.CourseNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
+
+func (c CourseServiceImpl) GetAllMajorByFaculty(payload get.GetByUUIDRequestPayload) ([]course.Major, error) {
+	packet, err := c.CourseRepository.GetAllMajorByFaculty(payload.ID)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return nil, web.NewResponseErrorFromError(err, web.MajorNotExist)
+		}
+		// Some Uncaught error
+		return nil, err
+	}
+
+	return packet, nil
+}
\ No newline at end of file
diff --git a/service/course/impl.go b/service/course/impl.go
new file mode 100644
index 0000000000000000000000000000000000000000..355158f0bd0e78ad3747685f44b1d09d6f04e3c8
--- /dev/null
+++ b/service/course/impl.go
@@ -0,0 +1,19 @@
+package course
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/repository/cache"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/course"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/user"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/token"
+)
+
+type CourseServiceImpl struct {
+	user.UserRepository
+	cache.CacheRepository
+	course.CourseRepository
+	*env.Environment
+	token.TokenUtil
+	logger.Logger
+}
diff --git a/service/course/type.go b/service/course/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..33f45503ef7a54876001a7d15f15464c9a6cdfe4
--- /dev/null
+++ b/service/course/type.go
@@ -0,0 +1,33 @@
+package course
+
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/course"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/get"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/course/delete"
+	cadd "gitlab.informatika.org/ocw/ocw-backend/model/web/course/add"
+	madd "gitlab.informatika.org/ocw/ocw-backend/model/web/course/major/add"
+	fadd "gitlab.informatika.org/ocw/ocw-backend/model/web/course/faculty/add"
+	cupdate "gitlab.informatika.org/ocw/ocw-backend/model/web/course/update"
+	mupdate "gitlab.informatika.org/ocw/ocw-backend/model/web/course/major/update"
+	fupdate "gitlab.informatika.org/ocw/ocw-backend/model/web/course/faculty/update"
+
+)
+
+type CourseService interface {
+	AddCourse(payload cadd.AddCourseRequestPayload) error
+	AddMajor(payload madd.AddMajorRequestPayload) error
+	AddFaculty(payload fadd.AddFacultyRequestPayload) error
+	GetCourse(payload get.GetByStringRequestPayload) (*course.Course, error)
+	GetMajor(payload get.GetByUUIDRequestPayload) (*course.Major, error)
+	GetFaculty(payload get.GetByUUIDRequestPayload) (*course.Faculty, error)
+	GetAllCourse() ([]course.Course, error)
+	GetAllMajor() ([]course.Major, error)
+	GetAllFaculty() ([]course.Faculty, error)
+	GetAllCourseByMajor(payload get.GetByUUIDRequestPayload) ([]course.Course, error)
+	GetAllCourseByFaculty(payload get.GetByUUIDRequestPayload) ([]course.Course, error)
+	GetAllMajorByFaculty(payload get.GetByUUIDRequestPayload) ([]course.Major, error)
+	UpdateCourse(payload cupdate.UpdateCourseRequestPayload) error
+	UpdateMajor(payload mupdate.UpdateMajorRequestPayload) error
+	UpdateFaculty(payload fupdate.UpdateFacultyRequestPayload) error
+	DeleteCourse(payload delete.DeleteByStringRequestPayload) error
+}
diff --git a/service/course/update.go b/service/course/update.go
new file mode 100644
index 0000000000000000000000000000000000000000..a08d0b3573c456651ab7287c4e9c9f160285edd3
--- /dev/null
+++ b/service/course/update.go
@@ -0,0 +1,145 @@
+package course
+
+import (
+	"errors"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/course"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
+	fupdate "gitlab.informatika.org/ocw/ocw-backend/model/web/course/faculty/update"
+	mupdate "gitlab.informatika.org/ocw/ocw-backend/model/web/course/major/update"
+	cupdate "gitlab.informatika.org/ocw/ocw-backend/model/web/course/update"
+	"gorm.io/gorm"
+)
+
+// TODO: Authorization Checks
+
+func (c CourseServiceImpl) UpdateCourse(payload cupdate.UpdateCourseRequestPayload) error {
+	// Validate Role
+	claim, err := c.TokenUtil.Validate(payload.UpdateCourseToken, token.Access)
+
+	// Invalid Token
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Unauthorized Role
+	if claim.Role == user.Student {
+		return web.NewResponseErrorFromError(err, web.UnauthorizedAccess)
+	}
+
+	// If payload uses major abbreviation, set id
+	if payload.MajAbbr != "" {
+		major, err := c.CourseRepository.GetMajorByAbbr(payload.MajAbbr)
+
+		if err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				return web.NewResponseErrorFromError(err, web.CourseNotExist)
+			}
+			// Some Uncaught error
+			return err
+		}
+
+		payload.MajorID = major.ID
+	}
+
+	err = c.CourseRepository.UpdateCourse(course.Course{
+		ID: payload.ID,
+		Name: payload.Name,
+		Major_id: payload.MajorID,
+		Description: payload.Description,
+		Email: payload.Email,
+		Abbreviation: payload.Abbreviation,
+		Lecturer: payload.Lecturer,
+	})
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return web.NewResponseErrorFromError(err, web.CourseNotExist)
+		}
+		// Uncaught error
+		return err
+	}
+
+	return nil
+}
+
+func (c CourseServiceImpl) UpdateMajor(payload mupdate.UpdateMajorRequestPayload) error {
+	
+	// Validate Role
+	claim, err := c.TokenUtil.Validate(payload.UpdateMajorToken, token.Access)
+
+	// Invalid Token
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Unauthorized Role
+	if claim.Role != user.Admin {
+		return web.NewResponseErrorFromError(err, web.UnauthorizedAccess)
+	}
+
+	// If payload uses faculty abbreviation, set id
+	if payload.FacAbbr != "" {
+		faculty, err := c.CourseRepository.GetFacultyByAbbr(payload.FacAbbr)
+
+		if err != nil {
+			if errors.Is(err, gorm.ErrRecordNotFound) {
+				return web.NewResponseErrorFromError(err, web.CourseNotExist)
+			}
+			// Some Uncaught error
+			return err
+		}
+
+		payload.FacultyID = faculty.ID
+	}
+	
+	err = c.CourseRepository.UpdateMajor(course.Major{
+		ID: payload.ID,
+		Name: payload.Name,
+		Fac_id: payload.FacultyID,
+		Abbreviation: payload.Abbreviation,
+	})
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return web.NewResponseErrorFromError(err, web.MajorNotExist)
+		}
+		// Uncaught error
+		return err
+	}
+
+	return nil
+}
+
+func (c CourseServiceImpl) UpdateFaculty(payload fupdate.UpdateFacultyRequestPayload) error {
+	// Validate Role
+	claim, err := c.TokenUtil.Validate(payload.UpdateFacultyToken, token.Access)
+
+	// Invalid Token
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Unauthorized Role
+	if claim.Role != user.Admin {
+		return web.NewResponseErrorFromError(err, web.UnauthorizedAccess)
+	}
+	
+	err = c.CourseRepository.UpdateFaculty(course.Faculty{
+		ID: payload.ID,
+		Name: payload.Name,
+		Abbreviation: payload.Abbreviation,
+	})
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return web.NewResponseErrorFromError(err, web.FacultyNotExist)
+		}
+		// Uncaught error
+		return err
+	}
+
+	return nil
+}
diff --git a/service/di.go b/service/di.go
index 763b0502928cbdacbe020c1c3d1e4ba64e590989..6cdb051667f746ccdc3ff0244fd29bdf19f69561 100644
--- a/service/di.go
+++ b/service/di.go
@@ -2,14 +2,16 @@ package service
 
 import (
 	"github.com/google/wire"
-	"gitlab.informatika.org/ocw/ocw-backend/service/auth"
 	"gitlab.informatika.org/ocw/ocw-backend/service/admin"
+	"gitlab.informatika.org/ocw/ocw-backend/service/auth"
 	"gitlab.informatika.org/ocw/ocw-backend/service/common"
 	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
 	"gitlab.informatika.org/ocw/ocw-backend/service/logger/hooks"
+	"gitlab.informatika.org/ocw/ocw-backend/service/material"
 	"gitlab.informatika.org/ocw/ocw-backend/service/reporter"
 	"gitlab.informatika.org/ocw/ocw-backend/service/reset"
 	"gitlab.informatika.org/ocw/ocw-backend/service/verification"
+	"gitlab.informatika.org/ocw/ocw-backend/service/course"
 )
 
 var ServiceTestSet = wire.NewSet(
@@ -48,6 +50,20 @@ var ServiceTestSet = wire.NewSet(
 		wire.Struct(new(verification.VerificationServiceImpl), "*"),
 		wire.Bind(new(verification.VerificationService), new(*verification.VerificationServiceImpl)),
 	),
+
+	// course service
+	wire.NewSet(
+		wire.Struct(new(course.CourseServiceImpl), "*"),
+		wire.Bind(new(course.CourseService), new(*course.CourseServiceImpl)),
+	),
+
+	// Material Service
+	wire.NewSet(
+		wire.Struct(new(material.MaterialContentServiceImpl), "*"),
+		wire.Struct(new(material.MaterialServiceImpl), "*"),
+		wire.Bind(new(material.MaterialContentService), new(*material.MaterialContentServiceImpl)),
+		wire.Bind(new(material.MaterialService), new(*material.MaterialServiceImpl)),
+	),
 )
 
 var ServiceSet = wire.NewSet(
diff --git a/service/material/content.go b/service/material/content.go
new file mode 100644
index 0000000000000000000000000000000000000000..5bf0b5638d0e44d4857932fc3d782a6d0a36c45b
--- /dev/null
+++ b/service/material/content.go
@@ -0,0 +1,47 @@
+package material
+
+import (
+	"github.com/google/uuid"
+	materialDomain "gitlab.informatika.org/ocw/ocw-backend/model/domain/material"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/material"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/transaction"
+)
+
+type MaterialContentServiceImpl struct {
+	transaction.TransactionBuilder
+	material.MaterialContentRepository
+}
+
+func (m MaterialContentServiceImpl) AddContent(materialId uuid.UUID, user user.User, contents []materialDomain.Content) error {
+	isSuccess := false
+	tx := m.Build()
+
+	tx.Begin()
+	defer tx.Auto(&isSuccess)
+
+	// TODO : Check user aman ga nambah konten
+
+	for _, content := range contents {
+		_, err := m.MaterialContentRepository.NewWithTransaction(tx, materialId, content.Type, content.Link)
+
+		if err != nil {
+			return err
+		}
+	}
+
+	isSuccess = true
+	return nil
+}
+
+func (m MaterialContentServiceImpl) DeleteContent(
+	materialId uuid.UUID, user user.User, contentId uuid.UUID,
+) error {
+	// TODO: check user aman ga delete konten
+	return m.MaterialContentRepository.Delete(contentId)
+}
+
+func (m MaterialContentServiceImpl) UpdateContentLink(materialId uuid.UUID, user user.User, contentId uuid.UUID, link string) error {
+	// TODO: Check user aman ga update link
+	return m.MaterialContentRepository.UpdateLink(contentId, link)
+}
diff --git a/service/material/impl.go b/service/material/impl.go
new file mode 100644
index 0000000000000000000000000000000000000000..1e7c95ff7014ec943f19982a4b95892baa52e2bf
--- /dev/null
+++ b/service/material/impl.go
@@ -0,0 +1,45 @@
+package material
+
+import (
+	"github.com/google/uuid"
+	materialRepo "gitlab.informatika.org/ocw/ocw-backend/model/domain/material"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/material"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/transaction"
+)
+
+type MaterialServiceImpl struct {
+	transaction.TransactionBuilder
+	material.MaterialContentRepository
+	material.MaterialRepository
+}
+
+func (m MaterialServiceImpl) Create(courseId string, user user.User, contents []materialRepo.Content) (uuid.UUID, error) {
+	isSuccess := false
+	tx := m.TransactionBuilder.Build()
+
+	tx.Begin()
+	defer tx.Auto(&isSuccess)
+
+	id, err := m.MaterialRepository.NewWithTransaction(tx, courseId, user.Email)
+
+	if err != nil {
+		return uuid.Nil, err
+	}
+
+	for _, content := range contents {
+		_, err = m.MaterialContentRepository.NewWithTransaction(tx, id, content.Type, content.Link)
+
+		if err != nil {
+			return uuid.Nil, err
+		}
+	}
+
+	isSuccess = true
+	return id, err
+}
+
+func (m MaterialServiceImpl) Delete(materialId uuid.UUID, user user.User) error {
+	// TODO: Pengecekan user apakah kontributor course bukan
+	return m.MaterialRepository.Delete(materialId)
+}
diff --git a/service/material/type.go b/service/material/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..66fe99b49e176c375812c020389aaf855fb3d7bb
--- /dev/null
+++ b/service/material/type.go
@@ -0,0 +1,18 @@
+package material
+
+import (
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/material"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+)
+
+type MaterialService interface {
+	Create(courseId string, user user.User, contents []material.Content) (uuid.UUID, error)
+	Delete(materialId uuid.UUID, user user.User) error
+}
+
+type MaterialContentService interface {
+	AddContent(materialId uuid.UUID, user user.User, contents []material.Content) error
+	DeleteContent(materialId uuid.UUID, user user.User, contentId uuid.UUID) error
+	UpdateContentLink(materialId uuid.UUID, user user.User, contentId uuid.UUID, link string) error
+}
diff --git a/service/reset/confirm.go b/service/reset/confirm.go
index 426af52f5514eec16fd0a6c4553bd817974177fd..66a42c1eef7843a9888fb9e4a5c0d34c17d9d86b 100644
--- a/service/reset/confirm.go
+++ b/service/reset/confirm.go
@@ -1,11 +1,47 @@
 package reset
 
 import (
-	// "gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/reset/confirm"
 )
 
 func (rs ResetServiceImpl) Confirm(payload confirm.ConfirmRequestPayload) error {
-	// TODO replace dummy
+	// Double Layered Security
+	// Validate Token
+	_, err := rs.TokenUtil.Validate(payload.ConfirmToken, token.Access)
+
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Check if Token is Cached
+	email, err := rs.CacheRepository.Get(*cache.NewKey(rs.RedisPrefixKey+"resetPassword", payload.ConfirmToken))
+
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.LinkNotAvailable)
+	}
+
+	// Reset the password
+	user, err := rs.UserRepository.Get(email)
+
+	if err != nil {
+		return err
+	}
+
+	hashedPassword, err := rs.Hash(payload.Password)
+
+	if err != nil {
+		return err
+	}
+
+	user.Password = hashedPassword
+	err = rs.UserRepository.Update(*user)
+
+	if err != nil {
+		return err
+	}
+
 	return nil
-}
\ No newline at end of file
+}
diff --git a/service/reset/impl.go b/service/reset/impl.go
index b9986e2509f4af7f8c0deb9053bf8d30640e4168..215896549040599572bc40ca19c9301d9c314d8d 100644
--- a/service/reset/impl.go
+++ b/service/reset/impl.go
@@ -1,17 +1,23 @@
 package reset
 
 import (
+	"gitlab.informatika.org/ocw/ocw-backend/provider/mail"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/cache"
 	"gitlab.informatika.org/ocw/ocw-backend/repository/user"
-	"gitlab.informatika.org/ocw/ocw-backend/service/verification"
+	"gitlab.informatika.org/ocw/ocw-backend/service/logger"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/password"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/template"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/token"
 )
 
 type ResetServiceImpl struct {
 	user.UserRepository
+	cache.CacheRepository
 	password.PasswordUtil
 	*env.Environment
 	token.TokenUtil
-	verification.VerificationService
+	logger.Logger
+	mail.MailQueue
+	template.TemplateWritterBuilder
 }
diff --git a/service/reset/mail.go b/service/reset/mail.go
new file mode 100644
index 0000000000000000000000000000000000000000..7de41926aa002e9e4fd233804ed82c056777aa44
--- /dev/null
+++ b/service/reset/mail.go
@@ -0,0 +1,7 @@
+package reset
+
+type mailPayload struct {
+	BaseUrl string
+	Email   string
+	Token   string
+}
diff --git a/service/reset/request.go b/service/reset/request.go
index 05584e6a9bb3dcf0a7a78c4ec5e12e1c4502732d..f3d645f4a08c7f809a117614acde439cca870a08 100644
--- a/service/reset/request.go
+++ b/service/reset/request.go
@@ -1,11 +1,83 @@
 package reset
 
 import (
-	// "gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"errors"
+	"time"
+
+	"github.com/golang-jwt/jwt/v4"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	tokenModel "gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
+	"gitlab.informatika.org/ocw/ocw-backend/provider/mail"
+	"gorm.io/gorm"
+
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/reset/request"
 )
 
 func (rs ResetServiceImpl) Request(payload request.RequestRequestPayload) error {
-	// TODO replace dummy
+	// Fetch user data from email
+	user, err := rs.UserRepository.Get(payload.Email)
+
+	if err != nil {
+		var errorObj error
+
+		switch {
+		case errors.Is(err, gorm.ErrRecordNotFound):
+			errorObj = web.NewResponseError("Email was not found", web.EmailNotExist)
+		default:
+			errorObj = err
+		}
+
+		return errorObj
+	}
+
+	if !user.IsActivated {
+		return web.NewResponseError("user is not activated yet", web.InactiveUser)
+	}
+
+	// Mint JWT Token for 30 minutes
+	resetClaim := tokenModel.UserClaim{
+		Name:  user.Name,
+		Email: user.Email,
+		Role:  user.Role,
+		Type:  tokenModel.Access,
+		RegisteredClaims: jwt.RegisteredClaims{
+			ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Duration(rs.TokenAccessExpired*6) * time.Millisecond)),
+			Issuer:    rs.TokenIssuer,
+			IssuedAt:  jwt.NewNumericDate(time.Now()),
+		},
+	}
+
+	resetToken, err := rs.TokenUtil.Generate(resetClaim, rs.TokenUtil.DefaultMethod())
+	if err != nil {
+		return err
+	}
+
+	// Cache Website on Redis, TTL 30 mins
+	rs.CacheRepository.Set(*cache.NewString(*cache.NewKey(rs.RedisPrefixKey+"resetPassword", resetToken), payload.Email, 30))
+
+	// Send Reset Email
+	mailBuilder, err := rs.TemplateWritterBuilder.Get("reset-password.format.html")
+
+	if err != nil {
+		return err
+	}
+
+	mailData, err := mailBuilder.Write(&mailPayload{
+		BaseUrl: rs.FrontendBaseURL + rs.ResetPasswordPath,
+		Email:   user.Email,
+		Token:   resetToken,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	rs.MailQueue.Send(mail.Mail{
+		To:      []string{user.Email},
+		Subject: "Reset Password",
+		Message: mailData,
+	})
+
 	return nil
-}
\ No newline at end of file
+}
diff --git a/service/reset/validate.go b/service/reset/validate.go
index 7cd09fe9bc83b23a2e4d0bbc0abf95fe1758b5cc..f1ae7028d7e0da998768720f97ff43adbf9f2c98 100644
--- a/service/reset/validate.go
+++ b/service/reset/validate.go
@@ -1,11 +1,27 @@
 package reset
 
 import (
-	// "gitlab.informatika.org/ocw/ocw-backend/model/domain/user"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web/auth/token"
 	"gitlab.informatika.org/ocw/ocw-backend/model/web/reset/validate"
 )
 
 func (rs ResetServiceImpl) Validate(payload validate.ValidateRequestPayload) error {
-	// TODO replace dummy
+	// Double Layered Security
+	// Validate Token
+	_, err := rs.TokenUtil.Validate(payload.ValidateToken, token.Access)
+
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.TokenError)
+	}
+
+	// Check if Token is Cached
+	_, err = rs.CacheRepository.Get(*cache.NewKey(rs.RedisPrefixKey+"resetPassword", payload.ValidateToken))
+
+	if err != nil {
+		return web.NewResponseErrorFromError(err, web.LinkNotAvailable)
+	}
+
 	return nil
-}
\ No newline at end of file
+}
diff --git a/service/verification/impl.go b/service/verification/impl.go
index 94db17485a4173f2ad820bbc100685d06f00d9b5..a74992b4fbf14b5c6e6174b5c00ee944afea812c 100644
--- a/service/verification/impl.go
+++ b/service/verification/impl.go
@@ -2,10 +2,18 @@ package verification
 
 import (
 	"gitlab.informatika.org/ocw/ocw-backend/provider/mail"
+	"gitlab.informatika.org/ocw/ocw-backend/repository/cache"
 	"gitlab.informatika.org/ocw/ocw-backend/repository/user"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/template"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/token"
 )
 
 type VerificationServiceImpl struct {
 	mail.MailQueue
 	user.UserRepository
+	*env.Environment
+	template.TemplateWritterBuilder
+	token.TokenUtil
+	cache.CacheRepository
 }
diff --git a/service/verification/mail.go b/service/verification/mail.go
new file mode 100644
index 0000000000000000000000000000000000000000..6b89f3f7b7a50f41711554ade1ab6f3871bcea19
--- /dev/null
+++ b/service/verification/mail.go
@@ -0,0 +1,7 @@
+package verification
+
+type mailPayload struct {
+	BaseUrl string
+	Email   string
+	Token   string
+}
diff --git a/service/verification/send.go b/service/verification/send.go
index 9f404a803958d11c9f10a121ceacfc7aa87318d6..cfedd802ffa95cfb9aece68168ed30ae4c3ca580 100644
--- a/service/verification/send.go
+++ b/service/verification/send.go
@@ -1,6 +1,72 @@
 package verification
 
+import (
+	"errors"
+	"time"
+
+	"github.com/google/uuid"
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+	"gitlab.informatika.org/ocw/ocw-backend/provider/mail"
+	"gorm.io/gorm"
+)
+
 func (v VerificationServiceImpl) SendVerifyMail(email string) error {
-	// TODO
+	_, err := v.UserRepository.Get(email)
+
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			return web.NewResponseError("user not found", web.EmailNotExist)
+		}
+
+		return err
+	}
+
+	res, err := v.CacheRepository.GetInteger(cache.Key{
+		Id: v.RedisPrefixKey + "verify:cnt:" + email,
+	})
+
+	if err != nil {
+		if err.Error() != "redigo: nil returned" {
+			return err
+		}
+	}
+
+	if res > v.Environment.EmailVerificationMaxRetry {
+		return nil
+	}
+
+	id := uuid.New().String()
+	v.CacheRepository.Incr(v.RedisPrefixKey+"verify:cnt:"+email, v.EmailVerificationRetryInterval*int64(time.Minute))
+	v.CacheRepository.Set(cache.String{
+		Key: cache.Key{
+			Id: v.RedisPrefixKey + "verify:id:" + id,
+		},
+		Value:           email,
+		ExpiryInMinutes: int(v.EmailVerificationExpire) * int(time.Second),
+	})
+
+	mailBuilder, err := v.TemplateWritterBuilder.Get("email-verification.format.html")
+
+	if err != nil {
+		return err
+	}
+
+	mailData, err := mailBuilder.Write(&mailPayload{
+		BaseUrl: v.FrontendBaseURL + v.ResetPasswordPath,
+		Email:   email,
+		Token:   id,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	v.MailQueue.Send(mail.Mail{
+		To:      []string{email},
+		Subject: "Email Verification",
+		Message: mailData,
+	})
+
 	return nil
 }
diff --git a/service/verification/type.go b/service/verification/type.go
index e6df1d1939e37be6abf399db52d68632e0924a4c..d321c706ff491be4398c8abaae780ca02ffe0cda 100644
--- a/service/verification/type.go
+++ b/service/verification/type.go
@@ -2,5 +2,5 @@ package verification
 
 type VerificationService interface {
 	SendVerifyMail(email string) error
-	SetVerification(email string, isVerified bool) error
+	DoVerification(id string) error
 }
diff --git a/service/verification/verify.go b/service/verification/verify.go
index b18ce1ecaa19fd332e495f81a8377b814a010246..4bfd2c115bb5f07811699599415be37bee5151da 100644
--- a/service/verification/verify.go
+++ b/service/verification/verify.go
@@ -1,6 +1,33 @@
 package verification
 
-func (v VerificationServiceImpl) SetVerification(email string, isVerified bool) error {
+import (
+	"gitlab.informatika.org/ocw/ocw-backend/model/domain/cache"
+	"gitlab.informatika.org/ocw/ocw-backend/model/web"
+)
+
+func (v VerificationServiceImpl) DoVerification(id string) error {
 	// TODO
+	email, err := v.CacheRepository.Get(cache.Key{
+		Id: v.RedisPrefixKey + "verify:id:" + id,
+	})
+
+	if err != nil {
+		return err
+	}
+
+	if email == "" {
+		return web.NewResponseErrorf("VERIFY", "id '%s' is not valid", id)
+	}
+
+	data, err := v.UserRepository.Get(email)
+
+	if err != nil {
+		return web.NewResponseErrorf("VERIFY", "username '%s' is not found", email)
+	}
+
+	data.IsActivated = true
+
+	v.UserRepository.Update(*data)
+
 	return nil
 }
diff --git a/test/di.go b/test/di.go
index 9ce05c76ea2a7f5f97645eb7bde26d66fdb121f0..6e9f9ff3ac420b22ee453b42ef68835662842c43 100644
--- a/test/di.go
+++ b/test/di.go
@@ -18,6 +18,7 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/utils"
 
 	"gitlab.informatika.org/ocw/ocw-backend/utils/env"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/template"
 )
 
 func CreateServer(logger logger.Logger, envTest *env.Environment) (*ApiTestPack, error) {
@@ -37,3 +38,11 @@ func CreateServer(logger logger.Logger, envTest *env.Environment) (*ApiTestPack,
 
 	return nil, nil
 }
+
+func CreateTemplateBuilder() template.TemplateWritterBuilder {
+	wire.Build(
+		utils.UtilSetTest,
+	)
+
+	return nil
+}
diff --git a/test/middleware/cors_test.go b/test/middleware/cors_test.go
index 78ec55168dd2b147f6ebab3273c9a838bc4a0f9e..74615aa27d9b04bcb60edf4173e0bf9f83b9eddf 100644
--- a/test/middleware/cors_test.go
+++ b/test/middleware/cors_test.go
@@ -45,7 +45,7 @@ func TestPreflight(t *testing.T) {
 		assert.Contains(t, res.Header.Get("Access-Control-Allow-Methods"), "GET")
 	})
 
-	t.Run("PreflightNotAllowedPatchMethod", func(t *testing.T) {
+	t.Run("PreflightAllowedPatchMethod", func(t *testing.T) {
 		res, _, err := test.ExecuteJSON(test.RequestData{
 			Method:   "OPTIONS",
 			Endpoint: "/ping",
@@ -59,7 +59,7 @@ func TestPreflight(t *testing.T) {
 		assert.Nil(t, err)
 
 		assert.Equal(t, res.StatusCode, http.StatusOK)
-		assert.Equal(t, res.Header.Get("Access-Control-Allow-Origin"), "")
-		assert.Equal(t, res.Header.Get("Access-Control-Allow-Methods"), "")
+		assert.Equal(t, res.Header.Get("Access-Control-Allow-Origin"), "https://inkubatorit.com")
+		assert.Equal(t, res.Header.Get("Access-Control-Allow-Methods"), "PATCH")
 	})
 }
diff --git a/test/utils/template/template_test.go b/test/utils/template/template_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a6e783ba29460022a978183bf452afba00c86c54
--- /dev/null
+++ b/test/utils/template/template_test.go
@@ -0,0 +1,66 @@
+package template
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"gitlab.informatika.org/ocw/ocw-backend/test"
+)
+
+type testTemplate struct {
+	Name string
+	Url  string
+}
+
+func TestTemplate(t *testing.T) {
+	builder := test.CreateTemplateBuilder()
+	template, err := builder.Get("test.html")
+
+	assert.Nil(t, err)
+
+	if err != nil {
+		return
+	}
+
+	t.Run("BuilderSuccess", func(t *testing.T) {
+		data := &testTemplate{
+			Name: "Bayu",
+			Url:  "Hehe",
+		}
+
+		result, err := template.Write(data)
+
+		assert.Nil(t, err)
+		assert.Equal(t, result, "<h1>Hello, Bayu. This is your Hehe</h1>")
+	})
+
+	t.Run("ShouldBeReuseObject", func(t *testing.T) {
+		templateInner, _ := builder.Get("test.html")
+
+		assert.Equal(t, template, templateInner)
+	})
+
+	t.Run("HtmlShouldBeParsed", func(t *testing.T) {
+		data := &testTemplate{
+			Name: "<script>alert('hayoloh')</script>",
+			Url:  "Hehe",
+		}
+
+		result, err := template.Write(data)
+
+		assert.Nil(t, err)
+		assert.NotEqual(t, result, "<h1>Hello, <script>alert('hayoloh')</script>. This is your Hehe</h1>")
+	})
+
+	t.Run("URLShouldBeParsed", func(t *testing.T) {
+		data := &testTemplate{
+			Name: "Bayu",
+			Url:  "http://localhost:8080/?q=' AND 10 AND \"",
+		}
+
+		result, err := template.Write(data)
+
+		assert.Nil(t, err)
+		assert.Equal(t, result, "<h1>Hello, Bayu. This is your http://localhost:8080/?q=&#39; AND 10 AND &#34;</h1>")
+	})
+}
diff --git a/utils/di.go b/utils/di.go
index d6b65b8b8f725bf8c3da03f3ab776ef8aca15e90..3987842c6ebf7c6b1103f237a4c80160b5fd4da2 100644
--- a/utils/di.go
+++ b/utils/di.go
@@ -8,6 +8,7 @@ import (
 	"gitlab.informatika.org/ocw/ocw-backend/utils/log"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/password"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/res"
+	"gitlab.informatika.org/ocw/ocw-backend/utils/template"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/token"
 	"gitlab.informatika.org/ocw/ocw-backend/utils/wrapper"
 )
@@ -40,6 +41,10 @@ var UtilSetTest = wire.NewSet(
 	// Token utility
 	wire.Struct(new(token.TokenUtilImpl), "*"),
 	wire.Bind(new(token.TokenUtil), new(*token.TokenUtilImpl)),
+
+	// Template Writter
+	template.NewBuilder,
+	wire.Bind(new(template.TemplateWritterBuilder), new(*template.TemplateWritterBuilderImpl)),
 )
 
 var UtilSet = wire.NewSet(
diff --git a/utils/env/env.go b/utils/env/env.go
index a55416e52bf462568666b3b4de1afc886d97fa44..f53df6088f224fa4822983591509d93e7f4e93c4 100644
--- a/utils/env/env.go
+++ b/utils/env/env.go
@@ -36,6 +36,33 @@ type Environment struct {
 	SmtpPassword string `env:"SMTP_PASSWORD"`
 	SmtpServer   string `env:"SMTP_SERVER"`
 	SmtpPort     int    `env:"SMTP_PORT" envDefault:"25"`
+
+	FrontendBaseURL   string `env:"FE_BASE_URL"`
+	ResetPasswordPath string `env:"RESET_PASSWORD_PATH" envDefault:"/resetPassword"`
+
+	EmailVerificationPath          string `env:"EMAIL_VERIFICATION_PATH" envDefault:"/verification"`
+	EmailVerificationMaxRetry      int64  `env:"EMAIL_VERIFICATION_MAX_RETRY" envDefault:"5"`
+	EmailVerificationRetryInterval int64  `env:"EMAIL_VERIFICATION_RESET_RETRY_INTERVAL_M" envDefault:"5"`
+	EmailVerificationExpire        int64  `env:"EMAIL_VERIFICATION_EXPIRE_S" envDefault:"300"`
+
+	RedisConnection string `env:"REDIS_STRING"`
+	RedisPort       string `env:"REDIS_PORT" envDefault:"6379"`
+	RedisUsername   string `env:"REDIS_USERNAME"`
+	RedisPassword   string `env:"REDIS_PASSWORD"`
+	RedisUseAuth    bool   `env:"REDIS_USE_AUTH" envDefault:"false"`
+	RedisPrefixKey  string `env:"REDIS_PREFIX_KEY" envDefault:"app:"`
+
+	BucketEndpoint  string `env:"BUCKET_ENDPOINT"`
+	BucketSecretKey string `env:"BUCKET_SECRET_KEY"`
+	BucketAccessKey string `env:"BUCKET_ACCESS_KEY"`
+	BucketTokenKey  string `env:"BUCKET_TOKEN_KEY"`
+	BucketUseSSL    bool   `env:"BUCKET_USE_SSL" envDefault:"true"`
+	BucketName      string `env:"BUCKET_NAME"`
+
+	BucketSignedPutDuration int64 `env:"BUCKET_SIGNED_PUT_DURATION_S" envDefault:"36000"`
+	BucketSignedGetDuration int64 `env:"BUCKET_SIGNED_GET_DURATION_S" envDefault:"1800"`
+
+	BucketMaterialBasePath string `env:"BUCKET_MATERIAL_BASE_PATH" envDefault:"materials/"`
 }
 
 func New() (*Environment, error) {
@@ -57,7 +84,10 @@ func NewEnv() (*Environment, error) {
 }
 
 func NewDotEnv() (*Environment, error) {
-	err := godotenv.Load()
+	err := godotenv.Load(
+		".env",
+		".env.local",
+	)
 
 	if err != nil {
 		return nil, err
diff --git a/utils/res/data/email-verification.format.html b/utils/res/data/email-verification.format.html
new file mode 100644
index 0000000000000000000000000000000000000000..a4a763e42aa997d4024843a5b3302be342e61a66
--- /dev/null
+++ b/utils/res/data/email-verification.format.html
@@ -0,0 +1,298 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+
+<head>
+  <!-- Compiled with Bootstrap Email version: 1.3.1 -->
+  <meta http-equiv="x-ua-compatible" content="ie=edge">
+  <meta name="x-apple-disable-message-reformatting">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <style type="text/css">
+    body,
+    table,
+    td {
+      font-family: Helvetica, Arial, sans-serif !important
+    }
+
+    .ExternalClass {
+      width: 100%
+    }
+
+    .ExternalClass,
+    .ExternalClass p,
+    .ExternalClass span,
+    .ExternalClass font,
+    .ExternalClass td,
+    .ExternalClass div {
+      line-height: 150%
+    }
+
+    a {
+      text-decoration: none
+    }
+
+    * {
+      color: inherit
+    }
+
+    a[x-apple-data-detectors],
+    u+#body a,
+    #MessageViewBody a {
+      color: inherit;
+      text-decoration: none;
+      font-size: inherit;
+      font-family: inherit;
+      font-weight: inherit;
+      line-height: inherit
+    }
+
+    img {
+      -ms-interpolation-mode: bicubic
+    }
+
+    table:not([class^=s-]) {
+      font-family: Helvetica, Arial, sans-serif;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+      border-spacing: 0px;
+      border-collapse: collapse
+    }
+
+    table:not([class^=s-]) td {
+      border-spacing: 0px;
+      border-collapse: collapse
+    }
+
+    @media screen and (max-width: 600px) {
+
+      .w-full,
+      .w-full>tbody>tr>td {
+        width: 100% !important
+      }
+
+      *[class*=s-lg-]>tbody>tr>td {
+        font-size: 0 !important;
+        line-height: 0 !important;
+        height: 0 !important
+      }
+
+      .s-2>tbody>tr>td {
+        font-size: 8px !important;
+        line-height: 8px !important;
+        height: 8px !important
+      }
+
+      .s-5>tbody>tr>td {
+        font-size: 20px !important;
+        line-height: 20px !important;
+        height: 20px !important
+      }
+
+      .s-10>tbody>tr>td {
+        font-size: 40px !important;
+        line-height: 40px !important;
+        height: 40px !important
+      }
+    }
+  </style>
+</head>
+
+<body class="bg-light"
+  style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;"
+  bgcolor="#f7fafc">
+  <table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0"
+    style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;"
+    bgcolor="#f7fafc">
+    <tbody>
+      <tr>
+        <td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
+          <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
+            <tbody>
+              <tr>
+                <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
+                  <!--[if (gte mso 9)|(IE)]>
+                      <table align="center" role="presentation">
+                        <tbody>
+                          <tr>
+                            <td width="600">
+                    <![endif]-->
+                  <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0"
+                    style="width: 100%; max-width: 600px; margin: 0 auto;">
+                    <tbody>
+                      <tr>
+                        <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
+                          <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0"
+                            style="width: 100%;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;"
+                                  align="left" width="100%" height="40">
+                                  &#160;
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="card" role="presentation" border="0" cellpadding="0" cellspacing="0"
+                            style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;"
+                            bgcolor="#ffffff">
+                            <tbody>
+                              <tr>
+                                <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left"
+                                  bgcolor="#ffffff">
+                                  <table class="card-body" role="presentation" border="0" cellpadding="0"
+                                    cellspacing="0" style="width: 100%;">
+                                    <tbody>
+                                      <tr>
+                                        <td
+                                          style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 20px;"
+                                          align="left">
+                                          <h1 class="h3"
+                                            style="padding-top: 0; padding-bottom: 0; font-weight: 500; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;"
+                                            align="left">Email Verifikasi</h1>
+                                          <table class="s-2 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 8px; font-size: 8px; width: 100%; height: 8px; margin: 0;"
+                                                  align="left" width="100%" height="8">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="s-5 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;"
+                                                  align="left" width="100%" height="20">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="hr" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; height: 1px; width: 100%; margin: 0;"
+                                                  align="left">
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="s-5 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;"
+                                                  align="left" width="100%" height="20">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <div class="space-y-3">
+                                            <p class="text-gray-700"
+                                              style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;"
+                                              align="left">
+                                              Anda telah melakukan pendaftaran pada OCW ITB menggunakan email {{ .Email
+                                              }}. Untuk menyelesaikan proses
+                                              pendaftaran, anda dapat menekan tombol dibawah ini.
+                                            </p>
+                                          </div>
+                                          <table class="s-5 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;"
+                                                  align="left" width="100%" height="20">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="hr" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; height: 1px; width: 100%; margin: 0;"
+                                                  align="left">
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="s-5 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;"
+                                                  align="left" width="100%" height="20">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="btn btn-primary" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0"
+                                            style="border-radius: 6px; border-collapse: separate !important;">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 24px; font-size: 16px; border-radius: 6px; margin: 0;"
+                                                  align="center" bgcolor="#0d6efd">
+                                                  <a href="{{ .BaseUrl }}/?token={{ .Token }}" target="_blank"
+                                                    style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: normal; white-space: nowrap; background-color: #0d6efd; padding: 8px 12px; border: 1px solid #0d6efd;">Reset
+                                                    Password</a>
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0"
+                            style="width: 100%;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;"
+                                  align="left" width="100%" height="40">
+                                  &#160;
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </td>
+                      </tr>
+                    </tbody>
+                  </table>
+                  <!--[if (gte mso 9)|(IE)]>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+                    <![endif]-->
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/utils/res/data/reset-password.format.html b/utils/res/data/reset-password.format.html
new file mode 100644
index 0000000000000000000000000000000000000000..16ea4415ab2c267cbc04ca806a87cbcc81ead636
--- /dev/null
+++ b/utils/res/data/reset-password.format.html
@@ -0,0 +1,297 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+
+<head>
+  <!-- Compiled with Bootstrap Email version: 1.3.1 -->
+  <meta http-equiv="x-ua-compatible" content="ie=edge">
+  <meta name="x-apple-disable-message-reformatting">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <meta name="format-detection" content="telephone=no, date=no, address=no, email=no">
+  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+  <style type="text/css">
+    body,
+    table,
+    td {
+      font-family: Helvetica, Arial, sans-serif !important
+    }
+
+    .ExternalClass {
+      width: 100%
+    }
+
+    .ExternalClass,
+    .ExternalClass p,
+    .ExternalClass span,
+    .ExternalClass font,
+    .ExternalClass td,
+    .ExternalClass div {
+      line-height: 150%
+    }
+
+    a {
+      text-decoration: none
+    }
+
+    * {
+      color: inherit
+    }
+
+    a[x-apple-data-detectors],
+    u+#body a,
+    #MessageViewBody a {
+      color: inherit;
+      text-decoration: none;
+      font-size: inherit;
+      font-family: inherit;
+      font-weight: inherit;
+      line-height: inherit
+    }
+
+    img {
+      -ms-interpolation-mode: bicubic
+    }
+
+    table:not([class^=s-]) {
+      font-family: Helvetica, Arial, sans-serif;
+      mso-table-lspace: 0pt;
+      mso-table-rspace: 0pt;
+      border-spacing: 0px;
+      border-collapse: collapse
+    }
+
+    table:not([class^=s-]) td {
+      border-spacing: 0px;
+      border-collapse: collapse
+    }
+
+    @media screen and (max-width: 600px) {
+
+      .w-full,
+      .w-full>tbody>tr>td {
+        width: 100% !important
+      }
+
+      *[class*=s-lg-]>tbody>tr>td {
+        font-size: 0 !important;
+        line-height: 0 !important;
+        height: 0 !important
+      }
+
+      .s-2>tbody>tr>td {
+        font-size: 8px !important;
+        line-height: 8px !important;
+        height: 8px !important
+      }
+
+      .s-5>tbody>tr>td {
+        font-size: 20px !important;
+        line-height: 20px !important;
+        height: 20px !important
+      }
+
+      .s-10>tbody>tr>td {
+        font-size: 40px !important;
+        line-height: 40px !important;
+        height: 40px !important
+      }
+    }
+  </style>
+</head>
+
+<body class="bg-light"
+  style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;"
+  bgcolor="#f7fafc">
+  <table class="bg-light body" valign="top" role="presentation" border="0" cellpadding="0" cellspacing="0"
+    style="outline: 0; width: 100%; min-width: 100%; height: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: Helvetica, Arial, sans-serif; line-height: 24px; font-weight: normal; font-size: 16px; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; color: #000000; margin: 0; padding: 0; border-width: 0;"
+    bgcolor="#f7fafc">
+    <tbody>
+      <tr>
+        <td valign="top" style="line-height: 24px; font-size: 16px; margin: 0;" align="left" bgcolor="#f7fafc">
+          <table class="container" role="presentation" border="0" cellpadding="0" cellspacing="0" style="width: 100%;">
+            <tbody>
+              <tr>
+                <td align="center" style="line-height: 24px; font-size: 16px; margin: 0; padding: 0 16px;">
+                  <!--[if (gte mso 9)|(IE)]>
+                      <table align="center" role="presentation">
+                        <tbody>
+                          <tr>
+                            <td width="600">
+                    <![endif]-->
+                  <table align="center" role="presentation" border="0" cellpadding="0" cellspacing="0"
+                    style="width: 100%; max-width: 600px; margin: 0 auto;">
+                    <tbody>
+                      <tr>
+                        <td style="line-height: 24px; font-size: 16px; margin: 0;" align="left">
+                          <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0"
+                            style="width: 100%;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;"
+                                  align="left" width="100%" height="40">
+                                  &#160;
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="card" role="presentation" border="0" cellpadding="0" cellspacing="0"
+                            style="border-radius: 6px; border-collapse: separate !important; width: 100%; overflow: hidden; border: 1px solid #e2e8f0;"
+                            bgcolor="#ffffff">
+                            <tbody>
+                              <tr>
+                                <td style="line-height: 24px; font-size: 16px; width: 100%; margin: 0;" align="left"
+                                  bgcolor="#ffffff">
+                                  <table class="card-body" role="presentation" border="0" cellpadding="0"
+                                    cellspacing="0" style="width: 100%;">
+                                    <tbody>
+                                      <tr>
+                                        <td
+                                          style="line-height: 24px; font-size: 16px; width: 100%; margin: 0; padding: 20px;"
+                                          align="left">
+                                          <h1 class="h3"
+                                            style="padding-top: 0; padding-bottom: 0; font-weight: 500; vertical-align: baseline; font-size: 28px; line-height: 33.6px; margin: 0;"
+                                            align="left">Reset Password</h1>
+                                          <table class="s-2 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 8px; font-size: 8px; width: 100%; height: 8px; margin: 0;"
+                                                  align="left" width="100%" height="8">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="s-5 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;"
+                                                  align="left" width="100%" height="20">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="hr" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; height: 1px; width: 100%; margin: 0;"
+                                                  align="left">
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="s-5 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;"
+                                                  align="left" width="100%" height="20">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <div class="space-y-3">
+                                            <p class="text-gray-700"
+                                              style="line-height: 24px; font-size: 16px; color: #4a5568; width: 100%; margin: 0;"
+                                              align="left">
+                                              Anda telah mengirimkan reset password untuk email {{ .Email }}. Untuk
+                                              melakukan reset password tersebut, anda dapat menekan tombol dibawah ini.
+                                            </p>
+                                          </div>
+                                          <table class="s-5 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;"
+                                                  align="left" width="100%" height="20">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="hr" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 24px; font-size: 16px; border-top-width: 1px; border-top-color: #e2e8f0; border-top-style: solid; height: 1px; width: 100%; margin: 0;"
+                                                  align="left">
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="s-5 w-full" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0" style="width: 100%;" width="100%">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 20px; font-size: 20px; width: 100%; height: 20px; margin: 0;"
+                                                  align="left" width="100%" height="20">
+                                                  &#160;
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                          <table class="btn btn-primary" role="presentation" border="0" cellpadding="0"
+                                            cellspacing="0"
+                                            style="border-radius: 6px; border-collapse: separate !important;">
+                                            <tbody>
+                                              <tr>
+                                                <td
+                                                  style="line-height: 24px; font-size: 16px; border-radius: 6px; margin: 0;"
+                                                  align="center" bgcolor="#0d6efd">
+                                                  <a href="{{ .BaseUrl }}/{{ .Token }}" target="_blank"
+                                                    style="color: #ffffff; font-size: 16px; font-family: Helvetica, Arial, sans-serif; text-decoration: none; border-radius: 6px; line-height: 20px; display: block; font-weight: normal; white-space: nowrap; background-color: #0d6efd; padding: 8px 12px; border: 1px solid #0d6efd;">Reset
+                                                    Password</a>
+                                                </td>
+                                              </tr>
+                                            </tbody>
+                                          </table>
+                                        </td>
+                                      </tr>
+                                    </tbody>
+                                  </table>
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                          <table class="s-10 w-full" role="presentation" border="0" cellpadding="0" cellspacing="0"
+                            style="width: 100%;" width="100%">
+                            <tbody>
+                              <tr>
+                                <td style="line-height: 40px; font-size: 40px; width: 100%; height: 40px; margin: 0;"
+                                  align="left" width="100%" height="40">
+                                  &#160;
+                                </td>
+                              </tr>
+                            </tbody>
+                          </table>
+                        </td>
+                      </tr>
+                    </tbody>
+                  </table>
+                  <!--[if (gte mso 9)|(IE)]>
+                    </td>
+                  </tr>
+                </tbody>
+              </table>
+                    <![endif]-->
+                </td>
+              </tr>
+            </tbody>
+          </table>
+        </td>
+      </tr>
+    </tbody>
+  </table>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/utils/res/data/test.html b/utils/res/data/test.html
new file mode 100644
index 0000000000000000000000000000000000000000..8d432ede97e9a3ddad9b5fcbbfefaece69f7fdb4
--- /dev/null
+++ b/utils/res/data/test.html
@@ -0,0 +1 @@
+<h1>Hello, {{ .Name }}. This is your {{ .Url }}</h1>
\ No newline at end of file
diff --git a/utils/res/embed.go b/utils/res/embed.go
index 598ad06c4a4d25269165e6676bfb2d127df69fd9..19f072892de5c8faf0a23d205dc00caaa5be0425 100644
--- a/utils/res/embed.go
+++ b/utils/res/embed.go
@@ -2,6 +2,7 @@ package res
 
 import (
 	"embed"
+	"io/fs"
 )
 
 //go:embed data/*
@@ -13,6 +14,9 @@ func (EmbedResources) GetBytesResource(path string) ([]byte, error) {
 	return data.ReadFile("data/" + path)
 }
 
+func (EmbedResources) GetFile(path string) (fs.File, error) {
+	return data.Open("data/" + path)
+}
 
 func (EmbedResources) GetStringResource(path string) (string, error) {
 	content, err := data.ReadFile("data/" + path)
@@ -22,4 +26,4 @@ func (EmbedResources) GetStringResource(path string) (string, error) {
 	}
 
 	return string(content), nil
-}
\ No newline at end of file
+}
diff --git a/utils/res/res.go b/utils/res/res.go
index 387823d3a50c02d2a0f8630ae5e0c273ec5d29b1..f37288d570af4d2cf684bfb634769048d5001290 100644
--- a/utils/res/res.go
+++ b/utils/res/res.go
@@ -1,6 +1,9 @@
 package res
 
+import "io/fs"
+
 type Resource interface {
 	GetBytesResource(path string) ([]byte, error)
 	GetStringResource(path string) (string, error)
+	GetFile(path string) (fs.File, error)
 }
diff --git a/utils/template/builder.go b/utils/template/builder.go
new file mode 100644
index 0000000000000000000000000000000000000000..b2317d5d72e83ff925ca019b8bc80cb860d10e44
--- /dev/null
+++ b/utils/template/builder.go
@@ -0,0 +1,5 @@
+package template
+
+type TemplateWritterBuilder interface {
+	Get(templatePath string) (TemplateWritter, error)
+}
diff --git a/utils/template/builder_impl.go b/utils/template/builder_impl.go
new file mode 100644
index 0000000000000000000000000000000000000000..db5386d28fa19e33eeaedf8319ef5dd343e4cbcd
--- /dev/null
+++ b/utils/template/builder_impl.go
@@ -0,0 +1,30 @@
+package template
+
+import "gitlab.informatika.org/ocw/ocw-backend/utils/res"
+
+type TemplateWritterBuilderImpl struct {
+	res          res.Resource
+	templatePool map[string]TemplateWritter
+}
+
+func NewBuilder(res res.Resource) *TemplateWritterBuilderImpl {
+	return &TemplateWritterBuilderImpl{
+		res:          res,
+		templatePool: map[string]TemplateWritter{},
+	}
+}
+
+func (t *TemplateWritterBuilderImpl) Get(templatePath string) (TemplateWritter, error) {
+	if t.templatePool[templatePath] == nil {
+		template, err := NewTemplateWritterImpl(t.res, templatePath)
+
+		if err != nil {
+			return nil, err
+		}
+
+		t.templatePool[templatePath] = template
+		return template, nil
+	}
+
+	return t.templatePool[templatePath], nil
+}
diff --git a/utils/template/impl.go b/utils/template/impl.go
new file mode 100644
index 0000000000000000000000000000000000000000..36574a484ca5eb9ec816b5de635051ae2af4929d
--- /dev/null
+++ b/utils/template/impl.go
@@ -0,0 +1,39 @@
+package template
+
+import (
+	"bytes"
+	"html/template"
+
+	"gitlab.informatika.org/ocw/ocw-backend/utils/res"
+)
+
+type TemplateWritterImpl struct {
+	template *template.Template
+}
+
+func NewTemplateWritterImpl(res res.Resource, templatePath string) (*TemplateWritterImpl, error) {
+	file, err := res.GetStringResource(templatePath)
+
+	if err != nil {
+		return nil, err
+	}
+
+	templateData, err := template.New(templatePath).Parse(file)
+
+	if err != nil {
+		return nil, err
+	}
+
+	return &TemplateWritterImpl{templateData}, nil
+}
+
+func (m TemplateWritterImpl) Write(data interface{}) (string, error) {
+	buffer := bytes.NewBuffer([]byte{})
+	err := m.template.Execute(buffer, data)
+
+	if err != nil {
+		return "", err
+	}
+
+	return buffer.String(), nil
+}
diff --git a/utils/template/type.go b/utils/template/type.go
new file mode 100644
index 0000000000000000000000000000000000000000..bd38384b0e32e125f951cd891d9040c8c817c84b
--- /dev/null
+++ b/utils/template/type.go
@@ -0,0 +1,5 @@
+package template
+
+type TemplateWritter interface {
+	Write(data interface{}) (string, error)
+}