Skip to content
This repository was archived by the owner on Jan 6, 2026. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DB_URI=postgres://jbavnilybmiowp:d2651d1455e17c89d721f110c0574591de722b6155d06c378dcb2560336c721c@ec2-44-197-128-108.compute-1.amazonaws.com:5432/d9bg7rlhmeks7s
dbDriver=postgres
25 changes: 25 additions & 0 deletions Dockerfile.production
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Dockerfile.production

FROM registry.semaphoreci.com/golang:1.18 as builder

ENV APP_HOME /go/src/togo

WORKDIR "$APP_HOME"
COPY src/ .

RUN go mod download
RUN go mod verify
RUN go build -o togo

FROM registry.semaphoreci.com/golang:1.18

ENV APP_HOME /go/src/togo
RUN mkdir -p "$APP_HOME"
WORKDIR "$APP_HOME"

COPY src/conf/ conf/
COPY src/views/ views/
COPY --from=builder "$APP_HOME"/togo $APP_HOME

EXPOSE 8080
CMD ["./togo"]
184 changes: 162 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,170 @@
### Requirements
## Todo API

- Implement one single API which accepts a todo task and records it
- There is a maximum **limit of N tasks per user** that can be added **per day**.
- Different users can have **different** maximum daily limit.
- Write integration (functional) tests
- Write unit tests
- Choose a suitable architecture to make your code simple, organizable, and maintainable
- Write a concise README
- How to run your code locally?
- A sample “curl” command to call your API
- How to run your unit tests locally?
- What do you love about your solution?
- What else do you want us to know about however you do not have enough time to complete?
- Link API document: https://basalt-leech-fa6.notion.site/Todo-API-document-fcfd66100f6743b2b585eeb1cf84f49a
- Link API online: https://todo-api-version1.herokuapp.com/
- Link API after install local: localhost:8000

### Notes
### :old_key: Prerequisites
Before you start, ensure you meet the following requirements:

- We're using Golang at Manabie. **However**, we encourage you to use the programming language that you are most comfortable with because we want you to **shine** with all your skills and knowledge.
- You have installed Golang version 1.18.1
- You have installed the Visual Studio Code.
- You have a basic understand of Golang, CLI.

### :page_with_curl: Guide

### How to submit your solution?
#### How to run this project locally

- Fork this repo and show us your development progress via a PR
Open Git bash
Paste folllowing command:

### Interesting facts about Manabie
```
git clone https://github.com/huynhhuuloc129/togo.git
```

- Monthly there are about 2 million lines of code changes (inserted/updated/deleted) committed into our GitHub repositories. To avoid **regression bugs**, we write different kinds of **automated tests** (unit/integration (functionality)/end2end) as parts of the definition of done of our assigned tasks.
- We nurture the cultural values: **knowledge sharing** and **good communication**, therefore good written documents and readable, organizable, and maintainable code are in our blood when we build any features to grow our products.
- We have **collaborative** culture at Manabie. Feel free to ask trieu@manabie.com any questions. We are very happy to answer all of them.
#### How to call API using curl

Thank you for spending time to read and attempt our take-home assessment. We are looking forward to your submission.
#### Install
Open the folder todo with VSCode and using this command to get all the library necessary:
```
go mod tidy
```

#### Usage
After installing enviroment, using those commands on VSCode to use
* Note: ***REPLACE ALL THE TEXT IN <> WITH YOUR INFO***

##### :one: Start server
* The server must be keep open all the time you use the app
```
go run server.go
```
##### :two: Register
Open another CLI to call API:
* To use the API you need to register account first:
```
curl --location --request POST 'localhost:8000/auth/register' \
--data-raw '{
"username": "<yourusername>",
"password": "<yourpassword>"
}'
```
- The response will be something like this:
```json
{
"Username":"<yourusername>",
"Password":"<yourpasswordafterhashed>",
"LimitTask":10
}
```

##### :three: Login
* After that you can login using the same password as register to get the token of that account:
```
curl --location --request POST 'localhost:8000/auth/login' \
--data-raw '{
"username": "<yourusername>",
"password": "<yourpassword>"
}'
```
- The response will be something like this:
```json
{
"Message": "login success",
"Token": "<yourtoken>"
}
```

##### :four: Using task
* You can check your info at:
```
curl --location --request GET 'localhost:8000/users/info' \
--header 'token: <yourtoken>'
```

###### Use the token response to you after login to:
* Get all task
```
curl --location --request GET 'localhost:8000/tasks' \
--header 'token: <yourtoken>'
```
* Get one task by task id
```
curl --location --request GET 'localhost:8000/tasks/<taskid>' \
--header 'token: <yourtoken>'
```
* Create new task
```
curl --location --request POST 'localhost:8000/tasks' \
--header 'token: <yourtoken>' \
--data-raw '{
"Content": "<yourtaskcontent>"
}'
```
* Update an existing task
```
curl --location --request PUT 'localhost:8000/tasks/<taskid>' \
--header 'token: <yourtoken>' \
--data-raw '{
"Content": "<newtaskcontent>"
}'
```
* Delete one task by task id
```
curl --location --request DELETE 'localhost:8000/tasks/<taskid>' \
--header 'token: <yourtoken>'

```
***Beside that you can run your all of tasks and also users command under admin account***
* Login with
```
curl -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "admin"}' "localhost:8000/auth/login"
```
* After logging in, you can modify users and your task:
* Get all users
```
curl --location --request GET 'http://127.0.0.1:8000/users' \
--header 'token: <admintoken>'
```
* Get one task by user id
```
curl --location --request GET 'http://127.0.0.1:8000/users/<userid>' \
--header 'token: <admintoken>'
```
* Create new user
```
curl --location --request POST 'http://127.0.0.1:8000/users' \
--header 'token: <admintoken>' \
--data-raw '{
"username": "<newusername>",
"password": "<newuserpassword>"
}'
```
* Update an existing user
```
curl --location --request PUT 'http://127.0.0.1:8000/users/<userid>' \
--header 'token: <admintoken>' \
--data-raw '{
"username": "<newusername>",
"password": "<newpassword>"
}'
```
* Delete one user by user id
```
curl --location --request DELETE 'http://127.0.0.1:8000/users/<userid>' \
--header 'token: <admintoken>'
```
#### How to run test locally
* Using this command to test all test in project (include unit test and intergration test):
```
go test -v -cover ./...
```
* If you want to test some folder only, you can go to the folder and using command:
```
cd <yourfolder>
go test .
```
#### What special about this solution:
* This project using all dependencies in clouds, include database, even the project it self has been host through heroku for using convenient.
* It has an admin account for full admin control over users but can't seen task to keep it private for each user.
* ***Note: Because this project use a cloud database so some action may take longer than usual***
94 changes: 94 additions & 0 deletions controllers/authController.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package controllers

import (
"encoding/json"
"net/http"
"strings"

"github.com/huynhhuuloc129/todo/jwt"
"github.com/huynhhuuloc129/todo/models"
)

type BaseHandler struct {
BaseCtrl *models.DbConn
}

// NewBaseHandler returns a new BaseHandler
func NewBaseHandler(BC *models.DbConn) *BaseHandler {
return &BaseHandler{
BaseCtrl: BC,
}
}

//response token
type ResponseToken struct {
Message string
Token string
}

// Handle register with method post
func (h *BaseHandler) Register(w http.ResponseWriter, r *http.Request) {
var user, user1 models.NewUser
_ = json.NewDecoder(r.Body).Decode(&user)
user1 = models.NewUser{
Username: user.Username,
Password: user.Password,
}
ok := models.CheckUserInput(user1)
if !ok {
http.Error(w, "registered failed", http.StatusBadRequest)
return
}

if strings.ToLower(user1.Username) != "admin" {
user1.LimitTask = 10
} else {
user1.LimitTask = 0
}
if err := h.BaseCtrl.InsertUser(user1); err != nil { // insert new user to database
http.Error(w, "insert user failed, err: "+err.Error(), http.StatusBadRequest)
return
}

w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(user1); err != nil { // response message and token back to view
http.Error(w, "encode failed, err: "+err.Error(), http.StatusCreated)
return
}
}

// handle login with method post
func (h *BaseHandler) Login(w http.ResponseWriter, r *http.Request) {
var user models.NewUser

_ = json.NewDecoder(r.Body).Decode(&user)
user1, ok := h.BaseCtrl.CheckUserNameExist(user.Username)
if !ok { // check username exist or not
http.Error(w, "account doesn't exist", http.StatusNotFound)
return
}

if ok := models.CheckUserInput(user); !ok { // check if user input valid or not
http.Error(w, "account input invalid", http.StatusNotFound)
return
}
if err := models.CheckPasswordHash(user1.Password, user.Password); err != nil { // check password correct or not
http.Error(w, "password incorrect, err: "+err.Error(), http.StatusUnauthorized)
return
}
token, err := jwt.Create(user.Username, int(user1.Id)) // Create token
if err != nil {
http.Error(w, "internal server error, err: "+err.Error(), 500)
return
}

w.Header().Set("Content-Type", "application/json")
resToken := ResponseToken{
Message: "login success",
Token: token,
}

if err = json.NewEncoder(w).Encode(resToken); err != nil { // response token back to client
http.Error(w, "encode failed, err: " + err.Error(), http.StatusFailedDependency)
}
}
Loading