Add User route and Refactor code
This commit is contained in:
parent
519749fd3a
commit
b311a41dc7
24 changed files with 902 additions and 489 deletions
|
|
@ -147,7 +147,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',
|
||||
})
|
||||
|
|
@ -156,7 +156,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"`
|
||||
}
|
||||
|
|
@ -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", zap.Error(err))
|
||||
d.taoLogger.Log.Error("Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
return d.currentRecipe
|
||||
}
|
||||
|
||||
|
|
@ -144,7 +141,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", 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
|
||||
|
|
@ -216,7 +213,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", 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
|
||||
}
|
||||
|
|
@ -260,7 +257,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", zap.Error(err))
|
||||
d.taoLogger.Log.Error("Error when read recipe file, Return default recipe", zap.Error(err))
|
||||
return d.currentRecipe.MaterialCode
|
||||
}
|
||||
|
||||
|
|
@ -326,13 +323,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
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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=
|
||||
|
|
|
|||
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}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package routers
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
|
|
@ -24,172 +25,36 @@ 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
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -197,10 +62,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")
|
||||
|
|
@ -209,17 +78,22 @@ 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{
|
||||
|
|
@ -231,96 +105,289 @@ func (rr *RecipeRouter) Route(r chi.Router) {
|
|||
"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
|
||||
|
||||
// FIXME: Request structure bug. Case-sensitive, likely bug at client
|
||||
// uncomment the below code to view the bug
|
||||
|
||||
// var change_request map[string]interface{}
|
||||
// err = json.NewDecoder(r.Body).Decode(&change_request)
|
||||
// if err != nil {
|
||||
// Log.Error("Decode in request failed: ", zap.Error(err))
|
||||
// }
|
||||
// Log.Debug("Request => ", zap.Any("request", change_request))
|
||||
|
||||
// Body
|
||||
var changes models.Recipe01
|
||||
err = json.NewDecoder(r.Body).Decode(&changes)
|
||||
if err != nil {
|
||||
Log.Error("Decode in request failed: ", zap.Error(err))
|
||||
}
|
||||
|
||||
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)))
|
||||
|
||||
file, _ := os.Create(path.Join("./cofffeemachineConfig", countryID, filename))
|
||||
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, 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",
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ====================== 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))
|
||||
|
||||
// check request structure
|
||||
|
||||
// FIXME: Request structure bug. Case-sensitive, likely bug at client
|
||||
// uncomment the below code to view the bug
|
||||
|
||||
// var change_request map[string]interface{}
|
||||
// err = json.NewDecoder(r.Body).Decode(&change_request)
|
||||
// if err != nil {
|
||||
// rr.taoLogger.Log.Error("Decode in request failed: ", zap.Error(err))
|
||||
// }
|
||||
// Log.Debug("Request => ", zap.Any("request", change_request))
|
||||
|
||||
// Body
|
||||
var changes models.Recipe01
|
||||
err = json.NewDecoder(r.Body).Decode(&changes)
|
||||
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
|
||||
}
|
||||
|
||||
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)))
|
||||
|
||||
file, _ := os.Create(path.Join("./cofffeemachineConfig", countryID, filename))
|
||||
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")
|
||||
if err := json.NewEncoder(w).Encode(&contracts.ResponseDefault{
|
||||
Status: http.StatusText(http.StatusOK),
|
||||
Message: "Recipe Updated",
|
||||
}); err != nil {
|
||||
rr.taoLogger.Log.Error("RecipeRouter.UpdateRecipe", zap.Error(err))
|
||||
http.Error(w, "Internal Error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
|
|||
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)
|
||||
}
|
||||
}
|
||||
205
server/server.go
205
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",
|
||||
s.taoLogger.Log.Warn("Merge - user tried to access while another user is requesting merge",
|
||||
zap.String("user", r.Context().Value("user").(*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 - user has access", zap.String("user", r.Context().Value("user").(*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
|
||||
}
|
||||
|
||||
|
|
@ -201,59 +235,59 @@ func (s *Server) createHandler() {
|
|||
|
||||
// Get who's requesting
|
||||
user := r.Context().Value("user").(*models.User)
|
||||
Log.Info("Request merge by", zap.String("user", user.Name))
|
||||
s.taoLogger.Log.Info("Request merge by", zap.String("user", user.Name))
|
||||
|
||||
// lookup for python exec
|
||||
py_exec, 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)
|
||||
}
|
||||
|
||||
Log.Info("Found python exec: ", zap.String("PythonPath", py_exec))
|
||||
s.taoLogger.Log.Info("Found python exec: ", zap.String("PythonPath", py_exec))
|
||||
// target api file
|
||||
merge_api, 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()
|
||||
// log.Println("Locate python api", merge_api.Name())
|
||||
Log.Info("Locate python api", zap.String("ApiName", merge_api.Name()))
|
||||
s.taoLogger.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)
|
||||
|
||||
// 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)
|
||||
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