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
70 changes: 70 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package config

import (
"fmt"
"github.com/spf13/viper"
)

var Config *Configuration

type Configuration struct {
Server ServerConfiguration
Database DatabaseConfiguration
Cors CorsConfiguration
}

type DatabaseConfiguration struct {
Driver string
Dbname string
Username string
Password string
Host string
Port string
Charset string
MaxLifetime int `mapstructure:"max_lifetime"`
MaxOpenConns int `mapstructure:"max_open_conns"`
MaxIdleConns int `mapstructure:"max_idle_conns"`
}

type ServerConfiguration struct {
ServerPort string `mapstructure:"server_port"`
GinMode string `mapstructure:"gin_mode"`
}

type CorsConfiguration struct {
AllowedOrigins []string `mapstructure:"allowed_origins"`
AllowedMethods []string `mapstructure:"allowed_methods"`
AllowedHeaders []string `mapstructure:"allowed_headers"`
}

type CacheConfiguration struct {
Address string `mapstructure:"address"`
Password string `mapstructure:"password"`
DB int `mapstructure:"db"`
}

// SetupDB initialize configuration
func Setup(configPath string) error {
var configuration *Configuration

viper.SetConfigFile(configPath)
viper.SetConfigType("yaml")

if err := viper.ReadInConfig(); err != nil {
return fmt.Errorf("error reading config dir: %s, %v", configPath, err)
}

err := viper.Unmarshal(&configuration)
if err != nil {
return fmt.Errorf("unable to decode into struct, %v", err)
}

Config = configuration

return nil
}

// GetConfig helps you to get configuration data
func GetConfig() *Configuration {
return Config
}
28 changes: 28 additions & 0 deletions config/config_dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
database:
driver: "mysql"
dbname: "db_test_manabie_todo"
username: "user"
password: "password"
host: "127.0.0.1"
port: "3306"
charset: "utf8mb4"
max_lifetime: 7200
max_open_conns: 150
max_idle_conns: 50
server:
server_port: "8080"
jwt_secret: "jdnfksdmfksda"
#release | debug
gin_mode: "debug"
cors:
allowed_origins:
- http://localhost:3000
- http://127.0.0.1:3000
allowed_methods:
- GET
- POST
- PUT
- DELETE
allowed_headers:
- Authorization
- Content-Type
91 changes: 91 additions & 0 deletions database/datastore/connection.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package datastore

import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
"log"
"os"
"time"
"togo/config"
)

var (
dbConn *gorm.DB
err error
)

// SetupDB opens a database and saves the reference to `Database` struct.
func SetupDB() error {
var db = dbConn

configuration := config.GetConfig()

driver := configuration.Database.Driver
database := configuration.Database.Dbname
username := configuration.Database.Username
password := configuration.Database.Password
host := configuration.Database.Host
port := configuration.Database.Port
charset := configuration.Database.Charset

var dsn string

if driver == "mysql" { // MYSQL
dsn = fmt.Sprintf(
"%s:%s@tcp(%s:%s)/%s?charset=%s&parseTime=True&loc=Local",
username,
password,
host,
port,
database,
charset,
)

newLogger := logger.New(
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
logger.Config{
SlowThreshold: time.Second, // Slow SQL threshold
LogLevel: logger.Info, // Log level
IgnoreRecordNotFoundError: false, // Ignore ErrRecordNotFound error for logger
Colorful: true, // Disable color
},
)

db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: newLogger,
})
if err != nil {
return fmt.Errorf("db connection err: %v", err)
}
}

mySQLConn, err := db.DB()
if err != nil {
return fmt.Errorf("extracting mysqlDB from gorm erorr %v", err)
}

mySQLConn.SetMaxIdleConns(configuration.Database.MaxIdleConns)
mySQLConn.SetMaxOpenConns(configuration.Database.MaxOpenConns)
mySQLConn.SetConnMaxLifetime(time.Duration(configuration.Database.MaxLifetime) * time.Second)
dbConn = db
return nil
}

func GetDB() *gorm.DB {
return dbConn
}

func CloseDB() error {
db, err := dbConn.DB()
if err != nil {
return err
}

err = db.Close()
if err != nil {
return err
}
return nil
}
4 changes: 4 additions & 0 deletions database/docker/dev/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
FROM mysql:8.0.23

COPY ./database/docker/dev/my.cnf /etc/mysql/conf.d/my.cnf
COPY ./database/docker/dev/migration/*.sql /docker-entrypoint-initdb.d/
1 change: 1 addition & 0 deletions database/docker/dev/migration/000000_create_database.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
CREATE DATABASE IF NOT EXISTS manabie_todo_app_dev;
25 changes: 25 additions & 0 deletions database/docker/dev/migration/000001_create_tables.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL UNIQUE,
password_hashed VARCHAR(64) NOT NULL,
daily_limit INT NOT NULL,
active BOOLEAN NOT NULL DEFAULT TRUE,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
KEY `idx_display_name` (`display_name`),
KEY `idx_username` (`username`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1;

DROP TABLE IF EXISTS `todos`;
CREATE TABLE `notes` (
id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255) NOT NULL,
text TEXT NOT NULL,
done BOOLEAN NOT NULL DEFAULT FALSE,
active BOOLEAN NOT NULL DEFAULT TRUE,
fk_user INT NOT NULL,
created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
FULLTEXT(title, text)
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 AUTO_INCREMENT=1;
5 changes: 5 additions & 0 deletions database/docker/dev/my.cnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
[client]
default-character-set=utf8mb4
26 changes: 26 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
version: '3.7'

services:
manbaie_todo_db:
container_name: "db_test_manabie_todo"
platform: linux/x86_64
build:
context: .
dockerfile: ./database/docker/dev/Dockerfile
networks:
- default
restart: always
ports:
- "3306:3306"
environment:
MYSQL_RANDOM_ROOT_PASSWORD: "secret"
MYSQL_DATABASE: "db_test_manabie_todo"
MYSQL_USER: "user"
MYSQL_PASSWORD: "password"
volumes:
- mysql_data:/var/lib/mysql
command: --default-authentication-plugin=mysql_native_password
networks:
default:
volumes:
mysql_data:
30 changes: 30 additions & 0 deletions domain/controllers/todo_controller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package controllers

import (
"context"
"togo/domain/models"
"togo/domain/services"
)

type todoController struct {
todoService services.TodoService
}

type TodoController interface {
CreateTodo(ctx context.Context, newTodo *models.NewTodo) (*models.OverviewTodo, error)
}

func (tc *todoController) CreateTodo(ctx context.Context, newTodo *models.NewTodo) (*models.OverviewTodo, error) {
todo, err := tc.todoService.CreateTodo(ctx, newTodo)
if err != nil {
return nil, err
}

return todo, nil
}

func NewTodoController(todoService services.TodoService) *todoController {
return &todoController{
todoService: todoService,
}
}
31 changes: 31 additions & 0 deletions domain/handler/handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package handler

import (
"fmt"
"github.com/go-chi/chi"
"net/http"
"togo/domain/controllers"
"togo/domain/middlewares"
"togo/domain/services"
)

func NewHTTPServer(
middleware middlewares.Middleware,
todoService services.TodoService,
) http.Handler {

router := chi.NewRouter()
router.Use(
middleware.WithCors(),
)

router.HandleFunc(
"/health",
func(writer http.ResponseWriter, request *http.Request) {
_, _ = fmt.Fprintf(writer, "OK\n")
})

router.Route("/todo", NewTodoHandler(controllers.NewTodoController(todoService)))

return router
}
45 changes: 45 additions & 0 deletions domain/handler/todo_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package handler

import (
"encoding/json"
"fmt"
"github.com/go-chi/chi"
"net/http"
"togo/domain/controllers"
"togo/domain/models"
"togo/utils"
)

func NewTodoHandler(todoController controllers.TodoController) func(r chi.Router) {
th := &todoHandler{todoController: todoController}

return func(r chi.Router) {
r.Post("/todo", th.CreateTodo)
}
}

type todoHandler struct {
todoController controllers.TodoController
}

type TodoHandler interface {
CreateTodo(w http.ResponseWriter, r *http.Request)
}

func (th *todoHandler) CreateTodo(w http.ResponseWriter, r *http.Request) {
var newTodo *models.NewTodo

err := json.NewDecoder(r.Body).Decode(newTodo)
if err != nil {
http.Error(w, fmt.Sprintf("error todoHandler.CreateTodo: %v", err), http.StatusBadRequest)
return
}

todoCreated, err := th.todoController.CreateTodo(r.Context(), newTodo)
if err != nil {
http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
return
}

utils.ResponseWithJSON(w, http.StatusOK, todoCreated)
}
17 changes: 17 additions & 0 deletions domain/middlewares/cors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package middlewares

import (
"github.com/go-chi/cors"
"net/http"
"togo/config"
)

func (m middleware) WithCors() func(http.Handler) http.Handler {
conf := config.GetConfig()
return cors.New(cors.Options{
AllowedOrigins: conf.Cors.AllowedOrigins,
AllowedMethods: conf.Cors.AllowedMethods,
AllowedHeaders: conf.Cors.AllowedHeaders,
AllowCredentials: true,
}).Handler
}
Loading