Merge branch 'main' of https://github.com/Poomipat-Ch/taobin_recipe_manager
This commit is contained in:
commit
96f341aa28
26 changed files with 1068 additions and 664 deletions
|
|
@ -153,7 +153,7 @@ export class RecipeService {
|
|||
|
||||
getRecipeCountries(): Observable<string[]> {
|
||||
return this._httpClient
|
||||
.get<string[]>(environment.api + '/recipes/versions', {
|
||||
.get<string[]>(environment.api + '/recipes/countries', {
|
||||
withCredentials: true,
|
||||
responseType: 'json',
|
||||
})
|
||||
|
|
@ -162,7 +162,7 @@ export class RecipeService {
|
|||
|
||||
getRecipeFiles(country: string): Observable<string[]> {
|
||||
return this._httpClient
|
||||
.get<string[]>(environment.api + '/recipes/versions/' + country, {
|
||||
.get<string[]>(environment.api + '/recipes/' + country + '/versions', {
|
||||
withCredentials: true,
|
||||
responseType: 'json',
|
||||
})
|
||||
|
|
|
|||
6
server/contracts/common.go
Normal file
6
server/contracts/common.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
package contracts
|
||||
|
||||
type ResponseDefault struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
34
server/contracts/user.go
Normal file
34
server/contracts/user.go
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package contracts
|
||||
|
||||
import "recipe-manager/enums/permissions"
|
||||
|
||||
// ================================== Users ==================================
|
||||
|
||||
type CreateUserReq struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture"`
|
||||
Permissions permissions.Permission `json:"permissions"`
|
||||
}
|
||||
|
||||
// ================================== User ==================================
|
||||
|
||||
type UpdateUserNameReq struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type UpdateUserPermissionsReq struct {
|
||||
Permissions permissions.Permission `json:"permissions"`
|
||||
}
|
||||
|
||||
type UpdateUserPictureReq struct {
|
||||
Picture string `json:"picture"`
|
||||
}
|
||||
|
||||
type UserRes struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture"`
|
||||
Permissions permissions.Permission `json:"permissions"`
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
|
|
@ -39,7 +38,7 @@ func HashCommit(n int) (string, error) {
|
|||
byt := make([]byte, n)
|
||||
_, err := rand.Read(byt)
|
||||
if err != nil {
|
||||
Log.Debug("Error when hashing commit", zap.Error(err))
|
||||
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(byt), nil
|
||||
|
|
@ -48,14 +47,14 @@ func HashCommit(n int) (string, error) {
|
|||
func Insert(c *CommitLog) error {
|
||||
commit_db, err := sqlx.Connect("sqlite3", "./data/database.db")
|
||||
if err != nil {
|
||||
Log.Fatal("Error when connecting to database", zap.Error(err))
|
||||
|
||||
}
|
||||
// init table in db
|
||||
commit_db.MustExec(schema)
|
||||
|
||||
_, err = commit_db.NamedExec("INSERT INTO commit_log (id, msg, created_at, editor, change_file) VALUES (:id, :msg, :created_at, :editor, :change_file)", c)
|
||||
if err != nil {
|
||||
Log.Error("Error when insert commit log", zap.Error(err))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -74,11 +73,9 @@ func GetCommitLogOfFilename(countryId string, filename string) ([]CommitLog, err
|
|||
filename = filenameParts[1] + "_" + filenameParts[2]
|
||||
}
|
||||
|
||||
Log.Debug("CommitDB", zap.Any("lookup", filename))
|
||||
|
||||
commitDB, err := sqlx.Connect("sqlite3", "./data/database.db")
|
||||
if err != nil {
|
||||
Log.Fatal("Error when connecting to database", zap.Error(err))
|
||||
|
||||
}
|
||||
|
||||
var commits []CommitLog
|
||||
|
|
@ -96,7 +93,6 @@ func GetCommitLogOfFilename(countryId string, filename string) ([]CommitLog, err
|
|||
}
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error when get commit log", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -106,14 +102,14 @@ func GetCommitLogOfFilename(countryId string, filename string) ([]CommitLog, err
|
|||
func GetCommitLogs() ([]CommitLog, error) {
|
||||
commit_db, err := sqlx.Connect("sqlite3", "./data/database.db")
|
||||
if err != nil {
|
||||
Log.Fatal("Error when connecting to database", zap.Error(err))
|
||||
|
||||
}
|
||||
|
||||
var commits []CommitLog
|
||||
err = commit_db.Get(&commits, "SELECT * FROM commit_log", nil)
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error when get commit log", zap.Error(err))
|
||||
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package data
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"recipe-manager/helpers"
|
||||
|
|
@ -12,10 +11,6 @@ import (
|
|||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
Log = logger.GetInstance()
|
||||
)
|
||||
|
||||
type RecipeWithTimeStamps struct {
|
||||
Recipe models.Recipe
|
||||
TimeStamps int64
|
||||
|
|
@ -28,9 +23,10 @@ type Data struct {
|
|||
currentRecipe *models.Recipe
|
||||
recipeMap map[string]RecipeWithTimeStamps
|
||||
Countries []helpers.CountryName
|
||||
taoLogger *logger.TaoLogger
|
||||
}
|
||||
|
||||
func NewData() *Data {
|
||||
func NewData(taoLogger *logger.TaoLogger) *Data {
|
||||
|
||||
countries := []helpers.CountryName{{
|
||||
CountryID: "tha",
|
||||
|
|
@ -66,6 +62,7 @@ func NewData() *Data {
|
|||
},
|
||||
},
|
||||
Countries: countries,
|
||||
taoLogger: taoLogger,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +88,7 @@ func (d *Data) GetRecipe(countryID, filename string) *models.Recipe {
|
|||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
logger.GetInstance().Error("Error when read recipe file [GetRecipe]", zap.Error(err))
|
||||
d.taoLogger.Log.Error("Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
return d.currentRecipe
|
||||
}
|
||||
|
||||
|
|
@ -148,7 +145,7 @@ func (d *Data) GetRecipe01ByProductCode(filename, countryID, productCode string)
|
|||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
logger.GetInstance().Error("Error when read recipe file [GetRecipe01ByProductCode]", zap.Error(err))
|
||||
d.taoLogger.Log.Error("Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
for _, v := range d.currentRecipe.Recipe01 {
|
||||
if v.ProductCode == productCode {
|
||||
return v, nil
|
||||
|
|
@ -222,7 +219,7 @@ func (d *Data) GetMaterialSetting(countryID, filename string) []models.MaterialS
|
|||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
logger.GetInstance().Error("Error when read recipe file [GetMaterialSetting]", zap.Error(err))
|
||||
d.taoLogger.Log.Error("Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
copy(result, d.currentRecipe.MaterialSetting)
|
||||
return result
|
||||
}
|
||||
|
|
@ -266,7 +263,7 @@ func (d *Data) GetMaterialCode(ids []uint64, countryID, filename string) []model
|
|||
recipe, err := helpers.ReadRecipeFile(countryID, filename)
|
||||
|
||||
if err != nil {
|
||||
logger.GetInstance().Error("Error when read recipe file [GetMaterialCode]", zap.Error(err))
|
||||
d.taoLogger.Log.Error("Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
return d.currentRecipe.MaterialCode
|
||||
}
|
||||
|
||||
|
|
@ -332,13 +329,3 @@ func (d *Data) GetCountryIDByName(countryName string) (string, error) {
|
|||
}
|
||||
return "", fmt.Errorf("country name: %s not found", countryName)
|
||||
}
|
||||
|
||||
func (d *Data) ExportToJSON() []byte {
|
||||
b_recipe, err := json.Marshal(d.currentRecipe)
|
||||
if err != nil {
|
||||
Log.Error("Error when marshal recipe", zap.Error(err))
|
||||
return nil
|
||||
}
|
||||
|
||||
return b_recipe
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
DROP TABLE IF EXISTS users;
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
-- slqlite3
|
||||
-- create users table
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
email TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
email TEXT UNIQUE NOT NULL,
|
||||
permissions INT DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users DROP COLUMN picture;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE users ADD COLUMN picture TEXT;
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
package data
|
||||
|
||||
import "github.com/jmoiron/sqlx"
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func NewSqliteDatabase() *sqlx.DB {
|
||||
db := sqlx.MustConnect("sqlite3", "./data/database.db")
|
||||
|
|
|
|||
16
server/enums/permissions/permission.go
Normal file
16
server/enums/permissions/permission.go
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package permissions
|
||||
|
||||
type Permission int
|
||||
|
||||
const (
|
||||
ThaiPermission Permission = 1 << iota
|
||||
MalayPermission
|
||||
AusPermission
|
||||
// NOTE: Add more permission here
|
||||
|
||||
SuperAdmin
|
||||
)
|
||||
|
||||
func (userPermissions Permission) IsHavePermission(requiredPermissions Permission) bool {
|
||||
return (userPermissions & requiredPermissions) == requiredPermissions
|
||||
}
|
||||
|
|
@ -4,10 +4,7 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"recipe-manager/services/logger"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func pull_request() error {
|
||||
|
|
@ -22,7 +19,7 @@ func pull_request() error {
|
|||
|
||||
if len(output) > 0 {
|
||||
if string(output) == "Already up to date." || string(output) == "Coffee recipe updated." {
|
||||
logger.GetInstance().Info("Git pull successful", zap.String("output", string(output)))
|
||||
//logger.GetInstance().Info("Git pull successful", zap.String("output", string(output)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,10 @@ go 1.21.1
|
|||
require (
|
||||
github.com/go-chi/chi/v5 v5.0.10
|
||||
github.com/go-chi/cors v1.2.1
|
||||
github.com/gorilla/websocket v1.5.0
|
||||
github.com/jmoiron/sqlx v1.3.5
|
||||
github.com/mattn/go-sqlite3 v1.14.18
|
||||
github.com/pkg/errors v0.9.1
|
||||
golang.org/x/oauth2 v0.12.0
|
||||
google.golang.org/api v0.143.0
|
||||
)
|
||||
|
|
@ -14,7 +18,7 @@ require (
|
|||
cloud.google.com/go/compute/metadata v0.2.3 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/google/uuid v1.3.1 // indirect
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
|
|
@ -24,12 +28,6 @@ require (
|
|||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gorilla/websocket v1.5.0 // indirect
|
||||
github.com/jmoiron/sqlx v1.3.5 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.18 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||
github.com/golang/protobuf v1.5.3 // indirect
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ github.com/go-chi/cors v1.2.1/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vz
|
|||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
|
||||
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
|
|
@ -134,8 +135,8 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
|
|||
github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o=
|
||||
github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1 h1:SBWmZhjUDRorQxrN0nwzf+AHBxnbFjViHQS4P0yVpmQ=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.1/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
|
|
@ -164,6 +165,7 @@ 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/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
|
|
@ -176,6 +178,7 @@ github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4
|
|||
github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
|
|
|||
|
|
@ -3,15 +3,8 @@ package helpers
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"recipe-manager/services/logger"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
Log = logger.GetInstance()
|
||||
)
|
||||
|
||||
// DynamicCompare compares two values dynamically and returns true if they are equal.
|
||||
|
|
@ -101,13 +94,13 @@ func GetTempFile(filename string, user string, suffix int) string {
|
|||
if os.IsNotExist(err) {
|
||||
// Create temp file
|
||||
if suffix == 0 {
|
||||
Log.Debug("[helpers] Suffix 0 GetTempFile", zap.Any("filename", filename))
|
||||
|
||||
return strings.Replace(filename, ".json", "_"+user+".tmp"+strconv.Itoa(suffix), 1)
|
||||
}
|
||||
|
||||
// change extension from json to tmp
|
||||
filename = strings.Replace(filename, ".json", "_"+user+".tmp"+strconv.Itoa(suffix), 1)
|
||||
Log.Debug("[helpers] GetTempFile", zap.Any("filename", filename))
|
||||
|
||||
return filename
|
||||
} else {
|
||||
|
||||
|
|
|
|||
53
server/middlewares/authorized.go
Normal file
53
server/middlewares/authorized.go
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"net/http"
|
||||
"recipe-manager/enums/permissions"
|
||||
"recipe-manager/models"
|
||||
)
|
||||
|
||||
func Authorize(p []permissions.Permission, nextRoute http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
user := r.Context().Value("user").(*models.User)
|
||||
|
||||
for _, pm := range p {
|
||||
if !user.Permissions.IsHavePermission(pm) {
|
||||
// If not have permission response unauthorized
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
err := json.NewEncoder(w).Encode("Unauthorized")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
nextRoute.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func OwnOrAuthorize(p []permissions.Permission, nextRoute http.HandlerFunc) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
reqUserID := chi.URLParam(r, "id")
|
||||
user := r.Context().Value("user").(*models.User)
|
||||
|
||||
if reqUserID == "" {
|
||||
// If not have permission response unauthorized
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
err := json.NewEncoder(w).Encode("Unauthorized")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if reqUserID == user.ID {
|
||||
nextRoute.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
Authorize(p, nextRoute)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
package models
|
||||
|
||||
import "recipe-manager/enums/permissions"
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture"`
|
||||
Permissions permissions.Permission `json:"permissions"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
package routers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"recipe-manager/config"
|
||||
"recipe-manager/services/oauth"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/oauth2"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"recipe-manager/config"
|
||||
"recipe-manager/services/logger"
|
||||
"recipe-manager/services/oauth"
|
||||
"recipe-manager/services/user"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type AuthRouter struct {
|
||||
cfg *config.ServerConfig
|
||||
oauth oauth.OAuthService
|
||||
}
|
||||
|
||||
func NewAuthRouter(cfg *config.ServerConfig, oauth oauth.OAuthService) *AuthRouter {
|
||||
return &AuthRouter{cfg, oauth}
|
||||
cfg *config.ServerConfig
|
||||
oauth oauth.OAuthService
|
||||
userService user.UserService
|
||||
taoLogger *logger.TaoLogger
|
||||
}
|
||||
|
||||
func (ar *AuthRouter) Route(r chi.Router) {
|
||||
|
|
@ -29,7 +30,10 @@ func (ar *AuthRouter) Route(r chi.Router) {
|
|||
|
||||
// generate state and nonce
|
||||
bytes := make([]byte, 32)
|
||||
rand.Read(bytes)
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
state := base64.URLEncoding.EncodeToString(bytes)
|
||||
|
||||
stateMap := map[string]string{}
|
||||
|
|
@ -38,14 +42,16 @@ func (ar *AuthRouter) Route(r chi.Router) {
|
|||
stateMap["redirect_to"] = r.URL.Query().Get("redirect_to")
|
||||
}
|
||||
|
||||
url := ar.oauth.AuthURL(state, stateMap)
|
||||
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
|
||||
authURL := ar.oauth.AuthURL(state, stateMap)
|
||||
http.Redirect(w, r, authURL, http.StatusTemporaryRedirect)
|
||||
})
|
||||
|
||||
r.Get("/google/callback", func(w http.ResponseWriter, r *http.Request) {
|
||||
// check state
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
var redirect_to string
|
||||
var redirectTo string
|
||||
state := r.URL.Query().Get("state")
|
||||
if state == "" {
|
||||
http.Error(w, "State not found", http.StatusBadRequest)
|
||||
|
|
@ -58,7 +64,7 @@ func (ar *AuthRouter) Route(r chi.Router) {
|
|||
return
|
||||
}
|
||||
|
||||
redirect_to = val["redirect_to"]
|
||||
redirectTo = val["redirect_to"]
|
||||
|
||||
ar.oauth.RemoveState(state)
|
||||
}
|
||||
|
|
@ -70,25 +76,44 @@ func (ar *AuthRouter) Route(r chi.Router) {
|
|||
return
|
||||
}
|
||||
|
||||
// get user info
|
||||
user, err := ar.oauth.GetUserInfo(r.Context(), token)
|
||||
// get userInfo info
|
||||
userInfo, err := ar.oauth.GetUserInfo(r.Context(), token)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Error getting user info", http.StatusBadRequest)
|
||||
http.Error(w, "Error getting userInfo info", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// map with database
|
||||
userFromDb, err := ar.userService.GetUserByEmail(ctx, userInfo.Email)
|
||||
if err != nil {
|
||||
http.Error(w, "Error while getting user data from database.", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if userFromDb == nil {
|
||||
http.Error(w, "Unauthorized, We not found your email, Please contact admin.", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
picture := userInfo.Picture
|
||||
if userFromDb.Picture != "" {
|
||||
picture = userFromDb.Picture
|
||||
}
|
||||
|
||||
value := url.Values{
|
||||
"name": {user.Name},
|
||||
"email": {user.Email},
|
||||
"picture": {user.Picture},
|
||||
"id": {userFromDb.ID},
|
||||
"name": {userFromDb.Name},
|
||||
"email": {userInfo.Email},
|
||||
"picture": {picture},
|
||||
"permissions": {strconv.Itoa(int(userFromDb.Permissions))},
|
||||
}
|
||||
|
||||
if redirect_to != "" {
|
||||
value.Add("redirect_to", redirect_to)
|
||||
if redirectTo != "" {
|
||||
value.Add("redirect_to", redirectTo)
|
||||
}
|
||||
|
||||
Log.Info("User Log-In Success", zap.String("user", user.Name), zap.String("email", user.Email))
|
||||
ar.taoLogger.Log.Info("User Log-In Success", zap.String("userInfo", userInfo.Name), zap.String("email", userInfo.Email))
|
||||
|
||||
// redirect to frontend with token and refresh token
|
||||
w.Header().Add("set-cookie", "access_token="+token.AccessToken+"; Path=/; HttpOnly; SameSite=None; Secure; Max-Age=3600")
|
||||
|
|
@ -132,38 +157,9 @@ func (ar *AuthRouter) Route(r chi.Router) {
|
|||
w.Header().Add("set-cookie", "refresh_token=; Path=/; HttpOnly; SameSite=None; Secure; Max-Age=0")
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
|
||||
r.Get("/user", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
token := &oauth2.Token{}
|
||||
if cookie, err := r.Cookie("access_token"); err == nil {
|
||||
token.AccessToken = cookie.Value
|
||||
}
|
||||
|
||||
// if have refresh token, set refresh token to token
|
||||
if cookie, err := r.Cookie("refresh_token"); err == nil {
|
||||
token.RefreshToken = cookie.Value
|
||||
}
|
||||
|
||||
if token.AccessToken == "" && token.RefreshToken == "" {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// get user info
|
||||
user, err := ar.oauth.GetUserInfo(r.Context(), token)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Error getting user info", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// return user info
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"user": user,
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func NewAuthRouter(cfg *config.ServerConfig, oauth oauth.OAuthService, userService user.UserService, taoLogger *logger.TaoLogger) *AuthRouter {
|
||||
return &AuthRouter{cfg, oauth, userService, taoLogger}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,91 +2,109 @@ package routers
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"recipe-manager/data"
|
||||
"recipe-manager/models"
|
||||
"recipe-manager/services/logger"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type MaterialRouter struct {
|
||||
data *data.Data
|
||||
data *data.Data
|
||||
taoLogger *logger.TaoLogger
|
||||
}
|
||||
|
||||
func NewMaterialRouter(data *data.Data) *MaterialRouter {
|
||||
func NewMaterialRouter(data *data.Data, taoLogger *logger.TaoLogger) *MaterialRouter {
|
||||
return &MaterialRouter{
|
||||
data: data,
|
||||
data: data,
|
||||
taoLogger: taoLogger,
|
||||
}
|
||||
}
|
||||
|
||||
func (mr *MaterialRouter) Route(r chi.Router) {
|
||||
r.Route("/materials", func(r chi.Router) {
|
||||
r.Get("/code", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
r.Get("/code", mr.getMaterialCode)
|
||||
|
||||
filename := r.URL.Query().Get("filename")
|
||||
country := r.URL.Query().Get("country")
|
||||
|
||||
matIDs := r.URL.Query().Get("mat_ids")
|
||||
|
||||
var matIDsUint []uint64
|
||||
for _, v := range strings.Split(matIDs, ",") {
|
||||
matIDUint, err := strconv.ParseUint(v, 10, 64)
|
||||
|
||||
if err != nil || matIDUint == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
matIDsUint = append(matIDsUint, matIDUint)
|
||||
}
|
||||
|
||||
countryID, err := mr.data.GetCountryIDByName(country)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Country not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
material := mr.data.GetMaterialCode(matIDsUint, countryID, filename)
|
||||
|
||||
json.NewEncoder(w).Encode(material)
|
||||
})
|
||||
|
||||
r.Get("/setting/{mat_id}", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
filename := r.URL.Query().Get("filename")
|
||||
country := r.URL.Query().Get("country")
|
||||
|
||||
countryID, err := mr.data.GetCountryIDByName(country)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Country not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
material := mr.data.GetMaterialSetting(countryID, filename)
|
||||
|
||||
matID := chi.URLParam(r, "mat_id")
|
||||
|
||||
matIDuint, err := strconv.ParseUint(matID, 10, 64)
|
||||
if err != nil {
|
||||
http.Error(w, "Invalid material id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var matSetting models.MaterialSetting
|
||||
|
||||
for _, mat := range material {
|
||||
if mat.ID == matIDuint {
|
||||
matSetting = mat
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(matSetting)
|
||||
})
|
||||
r.Get("/setting/{mat_id}", mr.getMaterialSettingByMatID)
|
||||
})
|
||||
}
|
||||
|
||||
func (mr *MaterialRouter) getMaterialCode(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
filename := r.URL.Query().Get("filename")
|
||||
country := r.URL.Query().Get("country")
|
||||
|
||||
matIDs := r.URL.Query().Get("mat_ids")
|
||||
|
||||
var matIDsUint []uint64
|
||||
for _, v := range strings.Split(matIDs, ",") {
|
||||
matIDUint, err := strconv.ParseUint(v, 10, 64)
|
||||
|
||||
if err != nil || matIDUint == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
matIDsUint = append(matIDsUint, matIDUint)
|
||||
}
|
||||
|
||||
countryID, err := mr.data.GetCountryIDByName(country)
|
||||
|
||||
if err != nil {
|
||||
mr.taoLogger.Log.Error("MaterialRouter.GetMaterialCode", zap.Error(err))
|
||||
http.Error(w, "Country not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
material := mr.data.GetMaterialCode(matIDsUint, countryID, filename)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(material); err != nil {
|
||||
mr.taoLogger.Log.Error("MaterialRouter.GetMaterialCode", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (mr *MaterialRouter) getMaterialSettingByMatID(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
filename := r.URL.Query().Get("filename")
|
||||
country := r.URL.Query().Get("country")
|
||||
|
||||
countryID, err := mr.data.GetCountryIDByName(country)
|
||||
|
||||
if err != nil {
|
||||
mr.taoLogger.Log.Error("MaterialRouter.GetMaterialSettingByMatID", zap.Error(err))
|
||||
http.Error(w, "Country not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
material := mr.data.GetMaterialSetting(countryID, filename)
|
||||
|
||||
matID := chi.URLParam(r, "mat_id")
|
||||
|
||||
matIDuint, err := strconv.ParseUint(matID, 10, 64)
|
||||
if err != nil {
|
||||
mr.taoLogger.Log.Error("MaterialRouter.GetMaterialSettingByMatID", zap.Error(err))
|
||||
http.Error(w, "Invalid material id", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var matSetting models.MaterialSetting
|
||||
|
||||
for _, mat := range material {
|
||||
if mat.ID == matIDuint {
|
||||
matSetting = mat
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(matSetting); err != nil {
|
||||
mr.taoLogger.Log.Error("MaterialRouter.GetMaterialSettingByMatID", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/pkg/errors"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
|
@ -25,172 +26,38 @@ type RecipeRouter struct {
|
|||
data *data.Data
|
||||
sheetService sheet.SheetService
|
||||
recipeService recipe.RecipeService
|
||||
taoLogger *logger.TaoLogger
|
||||
}
|
||||
|
||||
var (
|
||||
Log = logger.GetInstance()
|
||||
)
|
||||
|
||||
func NewRecipeRouter(data *data.Data, recipeService recipe.RecipeService, sheetService sheet.SheetService) *RecipeRouter {
|
||||
func NewRecipeRouter(data *data.Data, recipeService recipe.RecipeService, sheetService sheet.SheetService, taoLogger *logger.TaoLogger) *RecipeRouter {
|
||||
return &RecipeRouter{
|
||||
data: data,
|
||||
recipeService: recipeService,
|
||||
sheetService: sheetService,
|
||||
data,
|
||||
sheetService,
|
||||
recipeService,
|
||||
taoLogger,
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) Route(r chi.Router) {
|
||||
r.Route("/recipes", func(r chi.Router) {
|
||||
r.Get("/dashboard", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
r.Get("/dashboard", rr.dashBoard)
|
||||
|
||||
country := r.URL.Query().Get("country")
|
||||
filename := r.URL.Query().Get("filename")
|
||||
r.Get("/overview", rr.overview)
|
||||
|
||||
result, err := rr.recipeService.GetRecipeDashboard(&contracts.RecipeDashboardRequest{
|
||||
Country: country,
|
||||
Filename: filename,
|
||||
})
|
||||
r.Get("/{product_code}", rr.getRecipeByProductCode)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
r.Get("/{product_code}/mat", rr.getRecipeMatByProductCode)
|
||||
|
||||
json.NewEncoder(w).Encode(result)
|
||||
})
|
||||
r.Get("/{country}/{filename}/json", rr.getRecipeJson)
|
||||
|
||||
r.Get("/overview", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
var take, offset uint64 = 10, 0
|
||||
if newOffset, err := strconv.ParseUint(r.URL.Query().Get("offset"), 10, 64); err == nil {
|
||||
offset = newOffset
|
||||
}
|
||||
r.Post("/edit/{country}/{filename}", rr.updateRecipe)
|
||||
|
||||
if newTake, err := strconv.ParseUint(r.URL.Query().Get("take"), 10, 64); err == nil {
|
||||
take = newTake
|
||||
}
|
||||
r.Get("/saved/{country}/{filename_version_only}", rr.getSavedRecipes)
|
||||
|
||||
country := r.URL.Query().Get("country")
|
||||
filename := r.URL.Query().Get("filename")
|
||||
materialIds := r.URL.Query().Get("materialIds")
|
||||
|
||||
var materialIdsUint []int
|
||||
for _, v := range strings.Split(materialIds, ",") {
|
||||
materialIdUint, err := strconv.ParseUint(v, 10, 64)
|
||||
if err != nil || materialIdUint == 0 {
|
||||
continue
|
||||
}
|
||||
materialIdsUint = append(materialIdsUint, int(materialIdUint))
|
||||
}
|
||||
|
||||
result, err := rr.recipeService.GetRecipeOverview(&contracts.RecipeOverviewRequest{
|
||||
Take: int(take),
|
||||
Skip: int(offset),
|
||||
Search: r.URL.Query().Get("search"),
|
||||
Country: country,
|
||||
Filename: filename,
|
||||
MatIds: materialIdsUint,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(result)
|
||||
})
|
||||
|
||||
r.Get("/{product_code}", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
productCode := chi.URLParam(r, "product_code")
|
||||
|
||||
// recipe := rr.data.GetRecipe01()
|
||||
// recipeMetaData := rr.sheetService.GetSheet(r.Context(), "1rSUKcc5POR1KeZFGoeAZIoVoI7LPGztBhPw5Z_ConDE")
|
||||
|
||||
// var recipeResult *models.Recipe01
|
||||
// recipeMetaDataResult := map[string]string{}
|
||||
|
||||
// for _, v := range recipe {
|
||||
// if v.ProductCode == productCode {
|
||||
// recipeResult = &v
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// for _, v := range recipeMetaData {
|
||||
// if v[0].(string) == productCode {
|
||||
// recipeMetaDataResult = map[string]string{
|
||||
// "productCode": v[0].(string),
|
||||
// "name": v[1].(string),
|
||||
// "otherName": v[2].(string),
|
||||
// "description": v[3].(string),
|
||||
// "otherDescription": v[4].(string),
|
||||
// "picture": v[5].(string),
|
||||
// }
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// if recipeResult == nil {
|
||||
// http.Error(w, "Not Found", http.StatusNotFound)
|
||||
// return
|
||||
// }
|
||||
|
||||
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
// "recipe": recipeResult,
|
||||
// "recipeMetaData": recipeMetaDataResult,
|
||||
// })
|
||||
|
||||
result, err := rr.recipeService.GetRecipeDetail(&contracts.RecipeDetailRequest{
|
||||
Filename: r.URL.Query().Get("filename"),
|
||||
Country: r.URL.Query().Get("country"),
|
||||
ProductCode: productCode,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(result)
|
||||
})
|
||||
|
||||
r.Get("/{product_code}/mat", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
productCode := chi.URLParam(r, "product_code")
|
||||
|
||||
result, err := rr.recipeService.GetRecipeDetailMat(&contracts.RecipeDetailRequest{
|
||||
Filename: r.URL.Query().Get("filename"),
|
||||
Country: r.URL.Query().Get("country"),
|
||||
ProductCode: productCode,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(result)
|
||||
})
|
||||
|
||||
r.Get("/{country}/{filename}/json", func(w http.ResponseWriter, r *http.Request) {
|
||||
country := chi.URLParam(r, "country")
|
||||
filename := chi.URLParam(r, "filename")
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
countryID, err := rr.data.GetCountryIDByName(country)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(rr.data.GetRecipe(countryID, filename))
|
||||
})
|
||||
|
||||
r.Get("/versions", func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Get("/countries", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
// get key from map
|
||||
keys := []string{}
|
||||
var keys []string
|
||||
for k := range rr.data.AllRecipeFiles {
|
||||
countryName, err := rr.data.GetCountryNameByID(k)
|
||||
if err != nil {
|
||||
|
|
@ -198,10 +65,14 @@ func (rr *RecipeRouter) Route(r chi.Router) {
|
|||
}
|
||||
keys = append(keys, countryName)
|
||||
}
|
||||
json.NewEncoder(w).Encode(keys)
|
||||
if err := json.NewEncoder(w).Encode(keys); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.GetCountryRecipe", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
r.Get("/versions/{country}", func(w http.ResponseWriter, r *http.Request) {
|
||||
r.Get("/{country}/versions", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
countryName := chi.URLParam(r, "country")
|
||||
|
|
@ -210,198 +81,378 @@ func (rr *RecipeRouter) Route(r chi.Router) {
|
|||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", countryName), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
files := []string{}
|
||||
var files []string
|
||||
for _, v := range rr.data.AllRecipeFiles[countryID] {
|
||||
files = append(files, v.Name)
|
||||
}
|
||||
json.NewEncoder(w).Encode(files)
|
||||
|
||||
if err := json.NewEncoder(w).Encode(files); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.GetVersionsCountryRecipe", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
r.Get("/test/sheet", func(w http.ResponseWriter, r *http.Request) {
|
||||
result := rr.sheetService.GetSheet(r.Context(), "1rSUKcc5POR1KeZFGoeAZIoVoI7LPGztBhPw5Z_ConDE")
|
||||
|
||||
mapResult := []map[string]string{}
|
||||
var mapResult []map[string]string
|
||||
|
||||
for _, v := range result {
|
||||
mapResult = append(mapResult, map[string]string{
|
||||
"productCode": v[0].(string),
|
||||
"name": v[1].(string),
|
||||
"otherName": v[2].(string),
|
||||
"Description": v[3].(string),
|
||||
"description": v[3].(string),
|
||||
"otherDescription": v[4].(string),
|
||||
"picture": v[5].(string),
|
||||
})
|
||||
}
|
||||
json.NewEncoder(w).Encode(mapResult)
|
||||
})
|
||||
|
||||
r.Post("/edit/{country}/{filename}", func(w http.ResponseWriter, r *http.Request) {
|
||||
Log.Debug("Edit: ", zap.String("path", r.RequestURI))
|
||||
filename := chi.URLParam(r, "filename")
|
||||
country := chi.URLParam(r, "country")
|
||||
|
||||
countryID, err := rr.data.GetCountryIDByName(country)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound)
|
||||
if err := json.NewEncoder(w).Encode(mapResult); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.TestSheet", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
target_recipe := rr.data.GetRecipe(countryID, filename)
|
||||
|
||||
Log.Debug("Target => ", zap.Any("target", target_recipe.MachineSetting.ConfigNumber))
|
||||
|
||||
// check request structure
|
||||
|
||||
// Body
|
||||
var ch_map map[string]interface{}
|
||||
var changes models.Recipe01
|
||||
|
||||
err = json.NewDecoder(r.Body).Decode(&ch_map)
|
||||
if err != nil {
|
||||
Log.Error("Decode in request failed: ", zap.Error(err))
|
||||
}
|
||||
|
||||
// commit request
|
||||
editor := ch_map["edit_by"].(string)
|
||||
Log.Debug("requester", zap.Any("editor", editor))
|
||||
commit_msg := ch_map["commit_msg"].(string)
|
||||
Log.Debug("commit_msg", zap.Any("commit_msg", commit_msg))
|
||||
|
||||
changes = changes.FromMap(ch_map)
|
||||
|
||||
Log.Debug("Changes: ", zap.Any("changes", changes))
|
||||
// TODO: find the matched pd
|
||||
target_menu, err := rr.data.GetRecipe01ByProductCode(filename, countryID, changes.ProductCode)
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error when get recipe by product code", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
menu_map := target_menu.ToMap()
|
||||
change_map := changes.ToMap()
|
||||
|
||||
// Find changes
|
||||
for key, val := range menu_map {
|
||||
|
||||
test_bool, err := helpers.DynamicCompare(val, change_map[key])
|
||||
|
||||
if err != nil {
|
||||
Log.Error("DynamicCompare in request failed: ", zap.Error(err))
|
||||
}
|
||||
|
||||
if !test_bool {
|
||||
menu_map[key] = change_map[key]
|
||||
}
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
tempRecipe := models.Recipe01{}
|
||||
tempRecipe = tempRecipe.FromMap(menu_map)
|
||||
rr.data.SetValuesToRecipe(tempRecipe)
|
||||
Log.Debug("ApplyChange", zap.Any("status", "passed"))
|
||||
|
||||
// check if changed
|
||||
// Log.Debug("Check if changed", zap.Any("result", rr.data.GetRecipe01ByProductCode(changes.ProductCode)))
|
||||
|
||||
// target saved filename
|
||||
saved_filename := path.Join("./cofffeemachineConfig", countryID, filename)
|
||||
|
||||
// store @ temporary file
|
||||
temp_file_name := helpers.GetTempFile(saved_filename, editor, 0)
|
||||
|
||||
// TODO: push this change, editor, commit_msg into db
|
||||
|
||||
// gen hash
|
||||
commit_hash, err := data.HashCommit(8)
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error when hash commit", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
commit := data.CommitLog{
|
||||
|
||||
Id: commit_hash,
|
||||
Msg: commit_msg,
|
||||
Created_at: time.Now().Format("2006-01-02 15:04:05"),
|
||||
Editor: editor,
|
||||
Change_file: temp_file_name,
|
||||
}
|
||||
|
||||
err = data.Insert(&commit)
|
||||
Log.Debug("Commit", zap.Any("attr", commit))
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error when insert commit log", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
file, _ := os.Create(temp_file_name)
|
||||
if err != nil {
|
||||
Log.Error("Error when tried to create file", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(file)
|
||||
encoder.SetIndent("", " ")
|
||||
// err = encoder.Encode(rr.data.GetRecipe(countryID, temp_file_name))
|
||||
err = encoder.Encode(rr.data.GetRecipe(countryID, filename))
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error when write file", zap.Error(err))
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"commit_id": commit_hash,
|
||||
})
|
||||
})
|
||||
|
||||
// get saved files
|
||||
r.Get("/saved/{country}/{filename_version_only}", func(w http.ResponseWriter, r *http.Request) {
|
||||
file_version := chi.URLParam(r, "filename_version_only")
|
||||
country := chi.URLParam(r, "country")
|
||||
|
||||
countryID, err := rr.data.GetCountryIDByName(country)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// recipe_root_path := "./cofffeemachineConfig/"
|
||||
|
||||
// structure
|
||||
// full_file_name_targets := []string{}
|
||||
|
||||
// files, err := os.ReadDir(recipe_root_path + countryID)
|
||||
|
||||
// if err != nil {
|
||||
// Log.Error("Error when read directory", zap.Error(err))
|
||||
// return
|
||||
// }
|
||||
|
||||
// for _, file := range files {
|
||||
// Log.Debug("File: ", zap.Any("file", file.Name()))
|
||||
// if strings.Contains(file.Name(), file_version) && strings.Contains(file.Name(), ".tmp") {
|
||||
// full_file_name_targets = append(full_file_name_targets, file.Name())
|
||||
// }
|
||||
// }
|
||||
|
||||
commits, err := data.GetCommitLogOfFilename(countryID, file_version)
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error when get commit log", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"files": commits})
|
||||
|
||||
Log.Debug("Saved Files: ", zap.Any("files", commits))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ====================== Handler =================================
|
||||
|
||||
func (rr *RecipeRouter) dashBoard(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
|
||||
country := r.URL.Query().Get("country")
|
||||
filename := r.URL.Query().Get("filename")
|
||||
|
||||
result, err := rr.recipeService.GetRecipeDashboard(&contracts.RecipeDashboardRequest{
|
||||
Country: country,
|
||||
Filename: filename,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.Dashboard", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) overview(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
var take, offset uint64 = 10, 0
|
||||
if newOffset, err := strconv.ParseUint(r.URL.Query().Get("offset"), 10, 64); err == nil {
|
||||
offset = newOffset
|
||||
}
|
||||
|
||||
if newTake, err := strconv.ParseUint(r.URL.Query().Get("take"), 10, 64); err == nil {
|
||||
take = newTake
|
||||
}
|
||||
|
||||
country := r.URL.Query().Get("country")
|
||||
filename := r.URL.Query().Get("filename")
|
||||
materialIds := r.URL.Query().Get("materialIds")
|
||||
|
||||
var materialIdsUint []int
|
||||
for _, v := range strings.Split(materialIds, ",") {
|
||||
materialIdUint, err := strconv.ParseUint(v, 10, 64)
|
||||
if err != nil || materialIdUint == 0 {
|
||||
continue
|
||||
}
|
||||
materialIdsUint = append(materialIdsUint, int(materialIdUint))
|
||||
}
|
||||
|
||||
result, err := rr.recipeService.GetRecipeOverview(&contracts.RecipeOverviewRequest{
|
||||
Take: int(take),
|
||||
Skip: int(offset),
|
||||
Search: r.URL.Query().Get("search"),
|
||||
Country: country,
|
||||
Filename: filename,
|
||||
MatIds: materialIdsUint,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.Overview", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) getRecipeByProductCode(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
productCode := chi.URLParam(r, "product_code")
|
||||
fileName := r.URL.Query().Get("filename")
|
||||
country := r.URL.Query().Get("country")
|
||||
|
||||
// recipe := rr.data.GetRecipe01()
|
||||
// recipeMetaData := rr.sheetService.GetSheet(r.Context(), "1rSUKcc5POR1KeZFGoeAZIoVoI7LPGztBhPw5Z_ConDE")
|
||||
|
||||
// var recipeResult *models.Recipe01
|
||||
// recipeMetaDataResult := map[string]string{}
|
||||
|
||||
// for _, v := range recipe {
|
||||
// if v.ProductCode == productCode {
|
||||
// recipeResult = &v
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// for _, v := range recipeMetaData {
|
||||
// if v[0].(string) == productCode {
|
||||
// recipeMetaDataResult = map[string]string{
|
||||
// "productCode": v[0].(string),
|
||||
// "name": v[1].(string),
|
||||
// "otherName": v[2].(string),
|
||||
// "description": v[3].(string),
|
||||
// "otherDescription": v[4].(string),
|
||||
// "picture": v[5].(string),
|
||||
// }
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// if recipeResult == nil {
|
||||
// http.Error(w, "Not Found", http.StatusNotFound)
|
||||
// return
|
||||
// }
|
||||
|
||||
// json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
// "recipe": recipeResult,
|
||||
// "recipeMetaData": recipeMetaDataResult,
|
||||
// })
|
||||
|
||||
result, err := rr.recipeService.GetRecipeDetail(&contracts.RecipeDetailRequest{
|
||||
Filename: fileName,
|
||||
Country: country,
|
||||
ProductCode: productCode,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.GetRecipeByProductCode", zap.Error(err))
|
||||
http.Error(w, fmt.Sprintf("Recipe file: %s with productCode: %s not found", fileName, productCode), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.GetRecipeByProductCode", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) getRecipeMatByProductCode(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
productCode := chi.URLParam(r, "product_code")
|
||||
fileName := r.URL.Query().Get("filename")
|
||||
country := r.URL.Query().Get("country")
|
||||
|
||||
result, err := rr.recipeService.GetRecipeDetailMat(&contracts.RecipeDetailRequest{
|
||||
Filename: fileName,
|
||||
Country: country,
|
||||
ProductCode: productCode,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.GetRecipeMatByProductCode", zap.Error(err))
|
||||
http.Error(w, fmt.Sprintf("Material Recipe file: %s with productCode: %s not found", fileName, productCode), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(result); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.GetRecipeMatByProductCode", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) getRecipeJson(w http.ResponseWriter, r *http.Request) {
|
||||
country := chi.URLParam(r, "country")
|
||||
filename := chi.URLParam(r, "filename")
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
countryID, err := rr.data.GetCountryIDByName(country)
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.GetRecipeJson", zap.Error(err))
|
||||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(rr.data.GetRecipe(countryID, filename)); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.GetRecipeJson", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) updateRecipe(w http.ResponseWriter, r *http.Request) {
|
||||
rr.taoLogger.Log.Debug("Edit: ", zap.String("path", r.RequestURI))
|
||||
filename := chi.URLParam(r, "filename")
|
||||
country := chi.URLParam(r, "country")
|
||||
|
||||
countryID, err := rr.data.GetCountryIDByName(country)
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.UpdateRecipe", zap.Error(err))
|
||||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
targetRecipe := rr.data.GetRecipe(countryID, filename)
|
||||
|
||||
rr.taoLogger.Log.Debug("Target => ", zap.Any("target", targetRecipe.MachineSetting.ConfigNumber))
|
||||
|
||||
// Body
|
||||
var ch_map map[string]interface{}
|
||||
var changes models.Recipe01
|
||||
|
||||
err = json.NewDecoder(r.Body).Decode(&ch_map)
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.UpdateRecipe", zap.Error(errors.WithMessage(err, "Decode in request failed")))
|
||||
http.Error(w, "Can't Decode Recipe request body.", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// commit request
|
||||
editor := ch_map["edit_by"].(string)
|
||||
commit_msg := ch_map["commit_msg"].(string)
|
||||
|
||||
changes = changes.FromMap(ch_map)
|
||||
|
||||
rr.taoLogger.Log.Debug("Changes: ", zap.Any("changes", changes))
|
||||
// TODO: find the matched pd
|
||||
targetMenu, err := rr.data.GetRecipe01ByProductCode(filename, countryID, changes.ProductCode)
|
||||
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.UpdateRecipe", zap.Error(errors.WithMessage(err, "Error when get recipe by product code")))
|
||||
http.Error(w, fmt.Sprintf("Recipe country: %s file: %s productCode: %s not found.", country, filename, changes.ProductCode), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
menuMap := targetMenu.ToMap()
|
||||
changeMap := changes.ToMap()
|
||||
|
||||
// Find changes
|
||||
for key, val := range menuMap {
|
||||
|
||||
testBool, err := helpers.DynamicCompare(val, changeMap[key])
|
||||
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.UpdateRecipe", zap.Error(errors.WithMessage(err, "DynamicCompare in request failed")))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !testBool {
|
||||
menuMap[key] = changeMap[key]
|
||||
}
|
||||
}
|
||||
|
||||
// Apply changes
|
||||
tempRecipe := models.Recipe01{}
|
||||
tempRecipe = tempRecipe.FromMap(menuMap)
|
||||
rr.data.SetValuesToRecipe(tempRecipe)
|
||||
rr.taoLogger.Log.Debug("ApplyChange", zap.Any("status", "passed"))
|
||||
|
||||
// check if changed
|
||||
// Log.Debug("Check if changed", zap.Any("result", rr.data.GetRecipe01ByProductCode(changes.ProductCode)))
|
||||
|
||||
// target saved filename
|
||||
saved_filename := path.Join("./cofffeemachineConfig", countryID, filename)
|
||||
|
||||
// store @ temporary file
|
||||
temp_file_name := helpers.GetTempFile(saved_filename, editor, 0)
|
||||
|
||||
// TODO: push this change, editor, commit_msg into db
|
||||
|
||||
// gen hash
|
||||
commit_hash, err := data.HashCommit(8)
|
||||
|
||||
commit := data.CommitLog{
|
||||
|
||||
Id: commit_hash,
|
||||
Msg: commit_msg,
|
||||
Created_at: time.Now().Format("2006-01-02 15:04:05"),
|
||||
Editor: editor,
|
||||
Change_file: temp_file_name,
|
||||
}
|
||||
|
||||
err = data.Insert(&commit)
|
||||
|
||||
file, _ := os.Create(temp_file_name)
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.UpdateRecipe", zap.Error(errors.WithMessage(err, "Error when tried to create file")))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
encoder := json.NewEncoder(file)
|
||||
encoder.SetIndent("", " ")
|
||||
err = encoder.Encode(rr.data.GetRecipe(countryID, filename))
|
||||
|
||||
if err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.UpdateRecipe", zap.Error(errors.WithMessage(err, "Error when write file")))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"commit_id": commit_hash,
|
||||
})
|
||||
}
|
||||
|
||||
func (rr *RecipeRouter) getSavedRecipes(w http.ResponseWriter, r *http.Request) {
|
||||
// get saved files
|
||||
// r.Get(, func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Log.Debug("Saved Files: ", zap.Any("files", commits))
|
||||
// })
|
||||
|
||||
file_version := chi.URLParam(r, "filename_version_only")
|
||||
country := chi.URLParam(r, "country")
|
||||
|
||||
countryID, err := rr.data.GetCountryIDByName(country)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Country Name: %s not found!!!", country), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
// recipe_root_path := "./cofffeemachineConfig/"
|
||||
|
||||
// structure
|
||||
// full_file_name_targets := []string{}
|
||||
|
||||
// files, err := os.ReadDir(recipe_root_path + countryID)
|
||||
|
||||
// if err != nil {
|
||||
// Log.Error("Error when read directory", zap.Error(err))
|
||||
// return
|
||||
// }
|
||||
|
||||
// for _, file := range files {
|
||||
// Log.Debug("File: ", zap.Any("file", file.Name()))
|
||||
// if strings.Contains(file.Name(), file_version) && strings.Contains(file.Name(), ".tmp") {
|
||||
// full_file_name_targets = append(full_file_name_targets, file.Name())
|
||||
// }
|
||||
// }
|
||||
|
||||
commits, err := data.GetCommitLogOfFilename(countryID, file_version)
|
||||
|
||||
if err != nil {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{"files": commits})
|
||||
}
|
||||
|
|
|
|||
98
server/routers/user.go
Normal file
98
server/routers/user.go
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package routers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/zap"
|
||||
"net/http"
|
||||
"recipe-manager/contracts"
|
||||
"recipe-manager/enums/permissions"
|
||||
"recipe-manager/middlewares"
|
||||
"recipe-manager/services/logger"
|
||||
"recipe-manager/services/user"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserRouter struct {
|
||||
taoLogger *logger.TaoLogger
|
||||
userService user.UserService
|
||||
}
|
||||
|
||||
func NewUserRouter(taoLogger *logger.TaoLogger, userService user.UserService) *UserRouter {
|
||||
return &UserRouter{taoLogger, userService}
|
||||
}
|
||||
|
||||
func (ur *UserRouter) Route(r chi.Router) {
|
||||
|
||||
// Users
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", middlewares.Authorize([]permissions.Permission{permissions.SuperAdmin}, ur.getUsers))
|
||||
|
||||
r.Post("/", middlewares.Authorize([]permissions.Permission{permissions.SuperAdmin}, ur.createUser))
|
||||
})
|
||||
|
||||
// User
|
||||
r.Route("/user", func(r chi.Router) {
|
||||
r.Get("/{id}", middlewares.OwnOrAuthorize([]permissions.Permission{permissions.SuperAdmin}, ur.getUser))
|
||||
})
|
||||
}
|
||||
|
||||
// ================== Users Handler ================================
|
||||
|
||||
func (ur *UserRouter) getUsers(w http.ResponseWriter, r *http.Request) {
|
||||
// TODO: get all User, This route only SuperAdmin permission can access
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
}
|
||||
|
||||
func (ur *UserRouter) createUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
u := &contracts.CreateUserReq{}
|
||||
if err := json.NewDecoder(r.Body).Decode(u); err != nil {
|
||||
ur.taoLogger.Log.Error("UserRouter.CreateUser", zap.Error(err))
|
||||
}
|
||||
|
||||
ur.taoLogger.Log.Info("UserRouter.CreateUser", zap.Reflect("u", u))
|
||||
|
||||
if err := ur.userService.CreateNewUser(ctx, u.Name, u.Email, u.Picture, u.Permissions); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
if err := json.NewEncoder(w).Encode(&contracts.ResponseDefault{
|
||||
Status: http.StatusText(http.StatusNoContent),
|
||||
Message: "Created",
|
||||
}); err != nil {
|
||||
ur.taoLogger.Log.Error("UserRouter.CreateUser", zap.Error(err))
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== User Handler ===============================
|
||||
|
||||
func (ur *UserRouter) getUser(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), time.Second*5)
|
||||
defer cancel()
|
||||
|
||||
userID := chi.URLParam(r, "id")
|
||||
|
||||
getUser, err := ur.userService.GetUserByID(ctx, userID)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(&contracts.UserRes{
|
||||
ID: getUser.ID,
|
||||
Name: getUser.Name,
|
||||
Email: getUser.Email,
|
||||
Picture: getUser.Picture,
|
||||
Permissions: getUser.Permissions,
|
||||
}); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
219
server/server.go
219
server/server.go
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
|
|
@ -12,33 +13,27 @@ import (
|
|||
"path/filepath"
|
||||
"recipe-manager/config"
|
||||
"recipe-manager/data"
|
||||
"recipe-manager/enums/permissions"
|
||||
"recipe-manager/models"
|
||||
"recipe-manager/routers"
|
||||
"recipe-manager/services/logger"
|
||||
"recipe-manager/services/oauth"
|
||||
"recipe-manager/services/recipe"
|
||||
"recipe-manager/services/sheet"
|
||||
"recipe-manager/services/user"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
var (
|
||||
Log = logger.GetInstance()
|
||||
python_api_lock sync.Mutex
|
||||
|
||||
upgrader = websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
pythonApiLock sync.Mutex
|
||||
)
|
||||
|
||||
func loadConfig(path string) (*config.ServerConfig, error) {
|
||||
|
|
@ -48,25 +43,27 @@ func loadConfig(path string) (*config.ServerConfig, error) {
|
|||
|
||||
viper.AutomaticEnv()
|
||||
|
||||
var config config.ServerConfig
|
||||
var serverConfig config.ServerConfig
|
||||
err := viper.ReadInConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = viper.Unmarshal(&config)
|
||||
err = viper.Unmarshal(&serverConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
return &serverConfig, nil
|
||||
}
|
||||
|
||||
type Server struct {
|
||||
server *http.Server
|
||||
data *data.Data
|
||||
cfg *config.ServerConfig
|
||||
oauth oauth.OAuthService
|
||||
server *http.Server
|
||||
data *data.Data
|
||||
database *sqlx.DB
|
||||
cfg *config.ServerConfig
|
||||
oauth oauth.OAuthService
|
||||
taoLogger *logger.TaoLogger
|
||||
}
|
||||
|
||||
func NewServer() *Server {
|
||||
|
|
@ -77,30 +74,33 @@ func NewServer() *Server {
|
|||
log.Fatal(err)
|
||||
}
|
||||
|
||||
taoLogger := logger.NewTaoLogger(serverCfg)
|
||||
|
||||
return &Server{
|
||||
server: &http.Server{Addr: fmt.Sprintf(":%d", serverCfg.ServerPort)},
|
||||
data: data.NewData(),
|
||||
cfg: serverCfg,
|
||||
oauth: oauth.NewOAuthService(serverCfg),
|
||||
server: &http.Server{Addr: fmt.Sprintf(":%d", serverCfg.ServerPort)},
|
||||
data: data.NewData(taoLogger),
|
||||
database: data.NewSqliteDatabase(),
|
||||
cfg: serverCfg,
|
||||
oauth: oauth.NewOAuthService(serverCfg),
|
||||
taoLogger: taoLogger,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) Run() error {
|
||||
|
||||
// logger
|
||||
// defer log_inst.Sync()
|
||||
|
||||
if s.cfg.Debug {
|
||||
// logger.SetLevel("DEBUG")
|
||||
Log.Debug("Debug mode", zap.Bool("enable", s.cfg.Debug))
|
||||
logger.EnableDebug(s.cfg.Debug)
|
||||
}
|
||||
|
||||
//go cli.CommandLineListener()
|
||||
|
||||
s.createHandler()
|
||||
// log.Printf("Server running on %s", s.server.Addr)
|
||||
Log.Info("Server running", zap.String("addr", s.server.Addr))
|
||||
s.taoLogger.Log.Info("Server running", zap.String("addr", s.server.Addr))
|
||||
|
||||
defer func(Log *zap.Logger) {
|
||||
err := Log.Sync()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}(s.taoLogger.Log)
|
||||
|
||||
return s.server.ListenAndServe()
|
||||
}
|
||||
|
||||
|
|
@ -115,15 +115,22 @@ func (s *Server) createHandler() {
|
|||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
||||
}))
|
||||
|
||||
database := data.NewData()
|
||||
// Recipe Service
|
||||
recipeService := recipe.NewRecipeService(s.data)
|
||||
|
||||
// User Service
|
||||
userService := user.NewUserService(s.cfg, s.database, s.taoLogger)
|
||||
|
||||
// Seed
|
||||
_ = userService.CreateNewUser(context.WithValue(context.Background(), "user", &models.User{Email: "system"}), "kenta420", "poomipat.c@forth.co.th", "", permissions.SuperAdmin)
|
||||
|
||||
// Auth Router
|
||||
r.Group(func(r chi.Router) {
|
||||
ar := routers.NewAuthRouter(s.cfg, s.oauth)
|
||||
ar := routers.NewAuthRouter(s.cfg, s.oauth, userService, s.taoLogger)
|
||||
ar.Route(r)
|
||||
})
|
||||
|
||||
// Protect Group
|
||||
// Protected Group
|
||||
r.Group(func(r chi.Router) {
|
||||
|
||||
r.Use(func(next http.Handler) http.Handler {
|
||||
|
|
@ -134,7 +141,7 @@ func (s *Server) createHandler() {
|
|||
token.AccessToken = cookie.Value
|
||||
}
|
||||
|
||||
user, err := s.oauth.GetUserInfo(r.Context(), token)
|
||||
userInfo, err := s.oauth.GetUserInfo(r.Context(), token)
|
||||
|
||||
if err != nil {
|
||||
// if have refresh token, set refresh token to token
|
||||
|
|
@ -149,11 +156,38 @@ func (s *Server) createHandler() {
|
|||
return
|
||||
}
|
||||
|
||||
userInfo, err = s.oauth.GetUserInfo(r.Context(), newToken)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// set new token to cookie
|
||||
w.Header().Add("set-cookie", fmt.Sprintf("access_token=%s; Path=/; HttpOnly; SameSite=None; Secure; Max-Age=3600", newToken.AccessToken))
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), "user", user)
|
||||
if userInfo != nil {
|
||||
userFromDB, err := userService.GetUserByEmail(r.Context(), userInfo.Email)
|
||||
|
||||
if err != nil {
|
||||
if err != nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if userFromDB != nil {
|
||||
userInfo.ID = userFromDB.ID
|
||||
userInfo.Name = userFromDB.Name
|
||||
if userFromDB.Picture != "" {
|
||||
userInfo.Picture = userFromDB.Picture
|
||||
}
|
||||
userInfo.Permissions = userFromDB.Permissions
|
||||
}
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), "user", userInfo)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
|
|
@ -163,18 +197,18 @@ func (s *Server) createHandler() {
|
|||
|
||||
// locking
|
||||
if !pyAPIhandler(w, r) {
|
||||
Log.Warn("Merge - user tried to access while another user is requesting merge",
|
||||
zap.String("user", r.Context().Value("user").(*models.User).Name))
|
||||
s.taoLogger.Log.Warn("Merge - u tried to access while another u is requesting merge",
|
||||
zap.String("u", r.Context().Value("u").(*models.User).Name))
|
||||
return
|
||||
} else {
|
||||
Log.Debug("Merge - user has access", zap.String("user", r.Context().Value("user").(*models.User).Name))
|
||||
s.taoLogger.Log.Debug("Merge - u has access", zap.String("u", r.Context().Value("u").(*models.User).Name))
|
||||
}
|
||||
|
||||
var targetMap map[string]interface{}
|
||||
err := json.NewDecoder(r.Body).Decode(&targetMap)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Log.Fatal("Merge request failed", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Merge request failed", zap.Error(err))
|
||||
return
|
||||
}
|
||||
repo_path := "cofffeemachineConfig/coffeethai02_"
|
||||
|
|
@ -187,12 +221,12 @@ func (s *Server) createHandler() {
|
|||
// find target file in the cofffeemachineConfig
|
||||
if _, err := os.Stat(repo_path + master_version + ".json"); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Log.Fatal("Merge request failed. Master file not found: ", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Merge request failed. Master file not found: ", zap.Error(err))
|
||||
return
|
||||
}
|
||||
if _, err := os.Stat(repo_path + dev_version + ".json"); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Log.Fatal("Merge request failed. Dev file not found: ", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Merge request failed. Dev file not found: ", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -200,60 +234,60 @@ func (s *Server) createHandler() {
|
|||
dev_path := repo_path + dev_version + ".json"
|
||||
|
||||
// Get who's requesting
|
||||
user := r.Context().Value("user").(*models.User)
|
||||
Log.Info("Request merge by", zap.String("user", user.Name))
|
||||
u := r.Context().Value("u").(*models.User)
|
||||
s.taoLogger.Log.Info("Request merge by", zap.String("u", u.Name))
|
||||
|
||||
// lookup for python exec
|
||||
py_exec, err := exec.LookPath("python")
|
||||
pyExec, err := exec.LookPath("python")
|
||||
if err != nil {
|
||||
Log.Fatal("Python error: ", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Python error: ", zap.Error(err))
|
||||
} else {
|
||||
py_exec, err = filepath.Abs(py_exec)
|
||||
pyExec, err = filepath.Abs(pyExec)
|
||||
}
|
||||
|
||||
Log.Info("Found python exec: ", zap.String("PythonPath", py_exec))
|
||||
s.taoLogger.Log.Info("Found python exec: ", zap.String("PythonPath", pyExec))
|
||||
// target api file
|
||||
merge_api, api_err := os.Open("./python_api/merge_recipe.py")
|
||||
mergeApi, api_err := os.Open("./python_api/merge_recipe.py")
|
||||
if api_err != nil {
|
||||
Log.Fatal("Merge request failed. Python api error: ", zap.String("ApiErr", api_err.Error()))
|
||||
s.taoLogger.Log.Fatal("Merge request failed. Python api error: ", zap.String("ApiErr", api_err.Error()))
|
||||
}
|
||||
defer merge_api.Close()
|
||||
defer mergeApi.Close()
|
||||
// log.Println("Locate python api", merge_api.Name())
|
||||
Log.Info("Locate python api", zap.String("ApiName", merge_api.Name()))
|
||||
cmd := exec.Command(py_exec, merge_api.Name(), "merge", master_path, dev_path, output_path, changelog_path, "", user.Name)
|
||||
s.taoLogger.Log.Info("Locate python api", zap.String("ApiName", mergeApi.Name()))
|
||||
cmd := exec.Command(pyExec, mergeApi.Name(), "merge", master_path, dev_path, output_path, changelog_path, "", u.Name)
|
||||
|
||||
// log.Println("Run merge command", cmd)
|
||||
Log.Info("Merge", zap.String("master", master_path), zap.String("dev", dev_path), zap.String("output", output_path))
|
||||
Log.Debug("Run merge command", zap.String("Command", cmd.String()))
|
||||
s.taoLogger.Log.Info("Merge", zap.String("master", master_path), zap.String("dev", dev_path), zap.String("output", output_path))
|
||||
s.taoLogger.Log.Debug("Run merge command", zap.String("Command", cmd.String()))
|
||||
out, err := cmd.CombinedOutput()
|
||||
// log.Println(string(out))
|
||||
Log.Debug("Merge output", zap.String("Output", string(out)))
|
||||
s.taoLogger.Log.Debug("Merge output", zap.String("Output", string(out)))
|
||||
if err != nil {
|
||||
// log.Fatalln("Merge request failed. Python merge failed: ", err)
|
||||
Log.Fatal("Merge request failed. Python merge failed", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Merge request failed. Python merge failed", zap.Error(err))
|
||||
}
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "Merge success"})
|
||||
Log.Info("Merge success", zap.String("output", "merge success"))
|
||||
s.taoLogger.Log.Info("Merge success", zap.String("output", "merge success"))
|
||||
})
|
||||
|
||||
r.Post("/dllog", func(w http.ResponseWriter, r *http.Request) {
|
||||
Log.Debug("Request uri = ", zap.String("uri", r.RequestURI))
|
||||
s.taoLogger.Log.Debug("Request uri = ", zap.String("uri", r.RequestURI))
|
||||
|
||||
Log.Debug("Query param = ", zap.String("query", r.URL.Query().Get("query")))
|
||||
s.taoLogger.Log.Debug("Query param = ", zap.String("query", r.URL.Query().Get("query")))
|
||||
// param
|
||||
param := r.URL.Query().Get("query")
|
||||
Log.Debug("Param = ", zap.String("param", param))
|
||||
s.taoLogger.Log.Debug("Param = ", zap.String("param", param))
|
||||
|
||||
var postRequest map[string]interface{}
|
||||
err := json.NewDecoder(r.Body).Decode(&postRequest)
|
||||
Log.Debug("Log request: ", zap.String("postRequest", fmt.Sprintf("%+v", postRequest)))
|
||||
s.taoLogger.Log.Debug("Log request: ", zap.String("postRequest", fmt.Sprintf("%+v", postRequest)))
|
||||
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
Log.Fatal("Decode in request failed: ", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Decode in request failed: ", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -269,25 +303,25 @@ func (s *Server) createHandler() {
|
|||
}
|
||||
|
||||
log_name := postRequest["filename"].(string)
|
||||
Log.Warn("Log file name: ", zap.String("filename", log_name))
|
||||
s.taoLogger.Log.Warn("Log file name: ", zap.String("filename", log_name))
|
||||
|
||||
if log_name == "" {
|
||||
Log.Fatal("Empty log file name")
|
||||
s.taoLogger.Log.Fatal("Empty log file name")
|
||||
}
|
||||
|
||||
// log.Println("Log file ext: ", file_ext)
|
||||
default_changelog_path := "cofffeemachineConfig/" + param + "/"
|
||||
Log.Debug("Default changelog path: ", zap.String("default_changelog_path", default_changelog_path))
|
||||
s.taoLogger.Log.Debug("Default changelog path: ", zap.String("default_changelog_path", default_changelog_path))
|
||||
|
||||
changelog_path := default_changelog_path + log_name + file_ext
|
||||
Log.Debug("Changelog path: ", zap.String("changelog_path", changelog_path))
|
||||
s.taoLogger.Log.Debug("Changelog path: ", zap.String("changelog_path", changelog_path))
|
||||
if strings.Contains(log_name, "cofffeemachineConfig") && strings.Contains(log_name, ".json") {
|
||||
changelog_path = log_name
|
||||
}
|
||||
|
||||
logFile, err := os.Open(changelog_path)
|
||||
if err != nil {
|
||||
Log.Fatal("Log request failed: ", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Log request failed: ", zap.Error(err))
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
|
|
@ -298,20 +332,20 @@ func (s *Server) createHandler() {
|
|||
var logFileJson map[string]interface{}
|
||||
err = json.NewDecoder(logFile).Decode(&logFileJson)
|
||||
if err != nil {
|
||||
Log.Fatal("Error when decode log file: ", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Error when decode log file: ", zap.Error(err))
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(logFileJson)
|
||||
Log.Info("Log file: ", zap.String("filename", log_name))
|
||||
s.taoLogger.Log.Info("Log file: ", zap.String("filename", log_name))
|
||||
} else {
|
||||
w.Header().Set("Content-Disposition", "attachment; filename=logfile"+file_ext)
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
|
||||
_, err = io.Copy(w, logFile)
|
||||
if err != nil {
|
||||
Log.Fatal("Could not send blob", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Could not send blob", zap.Error(err))
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
|
@ -324,25 +358,25 @@ func (s *Server) createHandler() {
|
|||
//spl
|
||||
spl_path := strings.Split(r.RequestURI, "/")
|
||||
if len(spl_path) > 3 {
|
||||
Log.Warn("Unexpected depth: ",
|
||||
s.taoLogger.Log.Warn("Unexpected depth: ",
|
||||
zap.String("path", r.RequestURI),
|
||||
zap.String("depth", fmt.Sprintf("%d", len(spl_path))))
|
||||
}
|
||||
|
||||
if spl_path[2] == "" {
|
||||
Log.Error("Empty target dir", zap.String("path", r.RequestURI))
|
||||
s.taoLogger.Log.Error("Empty target dir", zap.String("path", r.RequestURI))
|
||||
}
|
||||
|
||||
Log.Debug("Split path = ", zap.Any("paths", spl_path))
|
||||
s.taoLogger.Log.Debug("Split path = ", zap.Any("paths", spl_path))
|
||||
|
||||
// Log.Info("Target dir: ", zap.String("dir", "cofffeemachineConfig"))
|
||||
// s.taoLogger.Log.Info("Target dir: ", zap.String("dir", "cofffeemachineConfig"))
|
||||
|
||||
main_folder := "cofffeemachineConfig"
|
||||
target_path := main_folder + "/" + spl_path[2]
|
||||
|
||||
dir, err := os.ReadDir(target_path)
|
||||
if err != nil {
|
||||
Log.Error("Error while trying to read dir: ", zap.String("dir", target_path), zap.Error(err))
|
||||
s.taoLogger.Log.Error("Error while trying to read dir: ", zap.String("dir", target_path), zap.Error(err))
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
|
|
@ -356,12 +390,12 @@ func (s *Server) createHandler() {
|
|||
file_ext = ".json"
|
||||
break
|
||||
}
|
||||
Log.Debug("Set file ext = ", zap.String("file_ext", file_ext))
|
||||
s.taoLogger.Log.Debug("Set file ext = ", zap.String("file_ext", file_ext))
|
||||
|
||||
displayable := make([]string, 0)
|
||||
for _, file := range dir {
|
||||
if strings.Contains(file.Name(), file_ext) {
|
||||
Log.Debug("Found file: ", zap.String("file", file.Name()))
|
||||
s.taoLogger.Log.Debug("Found file: ", zap.String("file", file.Name()))
|
||||
displayable = append(displayable, file.Name()[:len(file.Name())-len(filepath.Ext(file.Name()))])
|
||||
}
|
||||
}
|
||||
|
|
@ -370,7 +404,7 @@ func (s *Server) createHandler() {
|
|||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string][]string{"dirs": displayable})
|
||||
Log.Debug("Scan dir completed < ", zap.String("path", r.RequestURI))
|
||||
s.taoLogger.Log.Debug("Scan dir completed < ", zap.String("path", r.RequestURI))
|
||||
})
|
||||
|
||||
r.Get("/get_log_relation", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -378,27 +412,27 @@ func (s *Server) createHandler() {
|
|||
// Python looker
|
||||
py_exec, err := exec.LookPath("python")
|
||||
if err != nil {
|
||||
Log.Error("Error while trying to find python: ", zap.Error(err))
|
||||
s.taoLogger.Log.Error("Error while trying to find python: ", zap.Error(err))
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
merge_timeline, api_err := os.Open("./python_api/merge_timeline.py")
|
||||
if api_err != nil {
|
||||
Log.Error("Error while trying to open merge_timeline.json: ", zap.Error(api_err))
|
||||
s.taoLogger.Log.Error("Error while trying to open merge_timeline.json: ", zap.Error(api_err))
|
||||
http.Error(w, api_err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
defer merge_timeline.Close()
|
||||
|
||||
cmd := exec.Command(py_exec, merge_timeline.Name(), "get_relate")
|
||||
|
||||
Log.Debug("Command: ", zap.String("command", cmd.String()))
|
||||
s.taoLogger.Log.Debug("Command: ", zap.String("command", cmd.String()))
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
|
||||
Log.Debug("Output: ", zap.String("output", string(out)))
|
||||
s.taoLogger.Log.Debug("Output: ", zap.String("output", string(out)))
|
||||
|
||||
if err != nil {
|
||||
Log.Error("Error while trying to run python: ", zap.Error(err))
|
||||
s.taoLogger.Log.Error("Error while trying to run python: ", zap.Error(err))
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
|
|
@ -414,28 +448,29 @@ func (s *Server) createHandler() {
|
|||
})
|
||||
|
||||
r.Post("/diffpy/*", func(w http.ResponseWriter, r *http.Request) {
|
||||
Log.Debug("Diffpy: ", zap.String("path", r.RequestURI))
|
||||
s.taoLogger.Log.Debug("Diffpy: ", zap.String("path", r.RequestURI))
|
||||
// TODO: add command exec `python_Exec` `merge_recipe.py` `diff` `master_version` `version-version-version` `debug?` `flatten={true|false}` `out={true|false}`
|
||||
})
|
||||
|
||||
sheetService, err := sheet.NewSheetService(context.Background(), s.cfg)
|
||||
|
||||
if err != nil {
|
||||
Log.Fatal("Error while trying to create sheet service: ", zap.Error(err))
|
||||
s.taoLogger.Log.Fatal("Error while trying to create sheet service: ", zap.Error(err))
|
||||
return
|
||||
}
|
||||
|
||||
// Recipe Service
|
||||
rs := recipe.NewRecipeService(database)
|
||||
|
||||
// Recipe Router
|
||||
rr := routers.NewRecipeRouter(database, rs, sheetService)
|
||||
rr := routers.NewRecipeRouter(s.data, recipeService, sheetService, s.taoLogger)
|
||||
rr.Route(r)
|
||||
|
||||
// Material Router
|
||||
mr := routers.NewMaterialRouter(database)
|
||||
mr := routers.NewMaterialRouter(s.data, s.taoLogger)
|
||||
mr.Route(r)
|
||||
|
||||
// User Router
|
||||
ur := routers.NewUserRouter(s.taoLogger, userService)
|
||||
ur.Route(r)
|
||||
|
||||
})
|
||||
|
||||
r.NotFound(func(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
@ -454,11 +489,11 @@ func (s *Server) Shutdown(ctx context.Context) error {
|
|||
func pyAPIhandler(w http.ResponseWriter, r *http.Request) bool {
|
||||
timeout := 10 * time.Second
|
||||
|
||||
if !lockThenTimeout(&python_api_lock, timeout) {
|
||||
if !lockThenTimeout(&pythonApiLock, timeout) {
|
||||
http.Error(w, "API is busy", http.StatusServiceUnavailable)
|
||||
return false
|
||||
}
|
||||
defer python_api_lock.Unlock()
|
||||
defer pythonApiLock.Unlock()
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,76 +2,66 @@ package logger
|
|||
|
||||
import (
|
||||
"os"
|
||||
"recipe-manager/config"
|
||||
|
||||
"github.com/natefinch/lumberjack"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
var (
|
||||
log_inst = _NewLogger()
|
||||
type TaoLogger struct {
|
||||
cfg *config.ServerConfig
|
||||
enableDebug bool
|
||||
Log *zap.Logger
|
||||
}
|
||||
|
||||
enable_debug = false
|
||||
log_level = zap.NewAtomicLevel()
|
||||
func (tl *TaoLogger) initConfig() *zap.Logger {
|
||||
|
||||
log_file_config = zapcore.AddSync(&lumberjack.Logger{
|
||||
Filename: "services/logger/serverlog.log",
|
||||
MaxSize: 500, // megabytes
|
||||
MaxBackups: 3,
|
||||
MaxAge: 28, //days
|
||||
LocalTime: true,
|
||||
})
|
||||
json_enc = zapcore.NewJSONEncoder(zapcore.EncoderConfig{
|
||||
TimeKey: "timestamp",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "message",
|
||||
StacktraceKey: "error",
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
})
|
||||
console_enc = zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
|
||||
TimeKey: "timestamp",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "message",
|
||||
StacktraceKey: "error",
|
||||
EncodeLevel: zapcore.CapitalColorLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
})
|
||||
)
|
||||
|
||||
func createLogggerConfig() *zap.Logger {
|
||||
|
||||
enable_debug_mode := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zap.InfoLevel || enable_debug
|
||||
enableDebugMode := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool {
|
||||
return lvl >= zap.InfoLevel || tl.enableDebug
|
||||
})
|
||||
|
||||
log_core := zapcore.NewTee(
|
||||
zapcore.NewCore(json_enc, log_file_config, enable_debug_mode),
|
||||
zapcore.NewCore(console_enc, zapcore.AddSync(os.Stdout), enable_debug_mode),
|
||||
logCore := zapcore.NewTee(
|
||||
zapcore.NewCore(zapcore.NewJSONEncoder(zapcore.EncoderConfig{
|
||||
TimeKey: "timestamp",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "message",
|
||||
StacktraceKey: "error",
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
}), zapcore.AddSync(&lumberjack.Logger{
|
||||
Filename: "services/logger/serverlog.log",
|
||||
MaxSize: 500, // megabytes
|
||||
MaxBackups: 3,
|
||||
MaxAge: 28, //days
|
||||
LocalTime: true,
|
||||
}), enableDebugMode),
|
||||
zapcore.NewCore(zapcore.NewConsoleEncoder(zapcore.EncoderConfig{
|
||||
TimeKey: "timestamp",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "message",
|
||||
StacktraceKey: "error",
|
||||
EncodeLevel: zapcore.CapitalColorLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
}), zapcore.AddSync(os.Stdout), enableDebugMode),
|
||||
)
|
||||
|
||||
return zap.New(log_core)
|
||||
return zap.New(logCore)
|
||||
}
|
||||
|
||||
func _NewLogger() *zap.Logger {
|
||||
log := createLogggerConfig()
|
||||
defer log.Sync()
|
||||
return log
|
||||
}
|
||||
func NewTaoLogger(cfg *config.ServerConfig) *TaoLogger {
|
||||
logger := &TaoLogger{cfg, false, nil}
|
||||
logger.Log = logger.initConfig()
|
||||
|
||||
func GetInstance() *zap.Logger {
|
||||
return log_inst
|
||||
}
|
||||
if cfg.Debug {
|
||||
// logger.SetLevel("DEBUG")
|
||||
logger.Log.Debug("Debug mode", zap.Bool("enable", cfg.Debug))
|
||||
logger.enableDebug = true
|
||||
}
|
||||
|
||||
func EnableDebug(state bool) {
|
||||
enable_debug = state
|
||||
log_inst.Debug("EnableDebug", zap.Bool("enable", state))
|
||||
}
|
||||
|
||||
func GetDbgState() bool {
|
||||
return enable_debug
|
||||
return logger
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,9 @@ func (o *oauthService) GetUserInfo(ctx context.Context, token *oauth2.Token) (*m
|
|||
defer resp.Body.Close()
|
||||
|
||||
var userInfo map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&userInfo)
|
||||
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userInfo["error"] != nil {
|
||||
return nil, errors.New("Error getting user info")
|
||||
|
|
|
|||
9
server/services/user/queries/query.go
Normal file
9
server/services/user/queries/query.go
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package queries
|
||||
|
||||
const (
|
||||
GetUserByID = "SELECT id, name, email, picture, permissions FROM users WHERE id = ?"
|
||||
GetUserByEmail = "SELECT id, name, email, picture, permissions FROM users WHERE email = ?"
|
||||
CreateUser = "INSERT INTO users (id, name, email, picture, permissions) VALUES (? , ?, ?, ? , ?)"
|
||||
SetNameUser = "UPDATE users SET name = ? WHERE id = ?"
|
||||
SetPermissionsUser = "UPDATE users SET permissions = ? WHERE id = ?"
|
||||
)
|
||||
112
server/services/user/user.go
Normal file
112
server/services/user/user.go
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.uber.org/zap"
|
||||
"recipe-manager/config"
|
||||
"recipe-manager/enums/permissions"
|
||||
"recipe-manager/models"
|
||||
"recipe-manager/services/logger"
|
||||
"recipe-manager/services/user/queries"
|
||||
)
|
||||
|
||||
type UserService interface {
|
||||
CreateNewUser(ctx context.Context, name, email, picture string, permissions permissions.Permission) error
|
||||
UpdateName(ctx context.Context, userID, name string) error
|
||||
UpdatePermissions(ctx context.Context, userID string, permissions permissions.Permission) error
|
||||
|
||||
GetUserByID(ctx context.Context, userID string) (*models.User, error)
|
||||
GetUserByEmail(ctx context.Context, email string) (*models.User, error)
|
||||
}
|
||||
|
||||
type userService struct {
|
||||
cft *config.ServerConfig
|
||||
db *sqlx.DB
|
||||
taoLogger *logger.TaoLogger
|
||||
}
|
||||
|
||||
func (u *userService) CreateNewUser(ctx context.Context, name, email, picture string, permissions permissions.Permission) error {
|
||||
|
||||
user := ctx.Value("user").(*models.User)
|
||||
u.taoLogger.Log.Info("User.CreateNewUser", zap.Reflect("user", map[string]interface{}{
|
||||
"name": name,
|
||||
"email": email,
|
||||
"picture": picture,
|
||||
"permissions": permissions,
|
||||
}), zap.String("by", user.Email))
|
||||
|
||||
userID := uuid.New()
|
||||
|
||||
_, err := u.db.ExecContext(ctx, queries.CreateUser, userID, name, email, picture, permissions)
|
||||
|
||||
if err != nil {
|
||||
|
||||
u.taoLogger.Log.Error("User.CreateNewUser", zap.Error(err), zap.String("by", user.Email))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *userService) UpdateName(ctx context.Context, userID, name string) error {
|
||||
|
||||
user := ctx.Value("user").(*models.User)
|
||||
u.taoLogger.Log.Info("User.UpdateName", zap.String("userID", userID), zap.String("name", name), zap.String("by", user.Email))
|
||||
|
||||
_, err := u.db.ExecContext(ctx, queries.SetNameUser, name, userID)
|
||||
|
||||
if err != nil {
|
||||
|
||||
u.taoLogger.Log.Error("User.UpdateName", zap.Error(err), zap.String("by", user.Email))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *userService) UpdatePermissions(ctx context.Context, userID string, perms permissions.Permission) error {
|
||||
|
||||
user := ctx.Value("user").(*models.User)
|
||||
u.taoLogger.Log.Info("User.UpdatePermissions", zap.String("userID", userID), zap.Uint("permissions", uint(perms)), zap.String("by", user.Email))
|
||||
|
||||
_, err := u.db.ExecContext(ctx, queries.SetPermissionsUser, perms, userID)
|
||||
|
||||
if err != nil {
|
||||
|
||||
u.taoLogger.Log.Error("User.UpdatePermissions", zap.Error(err), zap.String("by", user.Email))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *userService) GetUserByID(ctx context.Context, userID string) (*models.User, error) {
|
||||
|
||||
userResult := &models.User{}
|
||||
if err := u.db.GetContext(ctx, userResult, queries.GetUserByID, userID); err != nil {
|
||||
u.taoLogger.Log.Error("User.GetUserByID", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return userResult, nil
|
||||
}
|
||||
|
||||
func (u *userService) GetUserByEmail(ctx context.Context, email string) (*models.User, error) {
|
||||
|
||||
userResult := &models.User{}
|
||||
if err := u.db.GetContext(ctx, userResult, queries.GetUserByEmail, email); err != nil {
|
||||
u.taoLogger.Log.Error("User.GetUserByEmail", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return userResult, nil
|
||||
}
|
||||
|
||||
func NewUserService(cfg *config.ServerConfig, db *sqlx.DB, taoLogger *logger.TaoLogger) UserService {
|
||||
return &userService{cfg, db, taoLogger}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue