test update some permissions
This commit is contained in:
parent
ac64335d5b
commit
25ce65e425
15 changed files with 582 additions and 493 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import { NgModule, inject } from '@angular/core';
|
import {inject, NgModule} from '@angular/core';
|
||||||
import {
|
import {
|
||||||
ActivatedRouteSnapshot,
|
ActivatedRouteSnapshot,
|
||||||
CanActivateFn,
|
CanActivateFn,
|
||||||
|
|
@ -7,8 +7,9 @@ import {
|
||||||
RouterStateSnapshot,
|
RouterStateSnapshot,
|
||||||
Routes,
|
Routes,
|
||||||
} from '@angular/router';
|
} from '@angular/router';
|
||||||
import { UserService } from './core/services/user.service';
|
import {UserService} from './core/services/user.service';
|
||||||
import { map } from 'rxjs';
|
import {map} from 'rxjs';
|
||||||
|
import {UserPermissions} from "./core/auth/userPermissions";
|
||||||
|
|
||||||
const authGuard: CanActivateFn = (
|
const authGuard: CanActivateFn = (
|
||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
|
|
@ -31,13 +32,38 @@ const authGuard: CanActivateFn = (
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loginGuard: CanActivateFn = (
|
const permissionsGuard: (...requiredPermissions: UserPermissions[]) => CanActivateFn = (...requiredPermissions) => (
|
||||||
route: ActivatedRouteSnapshot,
|
route: ActivatedRouteSnapshot,
|
||||||
state: RouterStateSnapshot
|
state: RouterStateSnapshot
|
||||||
) => {
|
) => {
|
||||||
const userService: UserService = inject(UserService);
|
const userService: UserService = inject(UserService);
|
||||||
const router: Router = inject(Router);
|
const router: Router = inject(Router);
|
||||||
|
|
||||||
|
const user = userService.getCurrentUser()
|
||||||
|
if (user == null)
|
||||||
|
return router.createUrlTree(['/login'], {
|
||||||
|
queryParams: {
|
||||||
|
redirectUrl: state.url,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (requiredPermissions.every(permission => user.permissions.includes(permission))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return router.createUrlTree(['/unauthorized'], {
|
||||||
|
queryParams: {
|
||||||
|
redirectUrl: state.url,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const loginGuard: CanActivateFn = (
|
||||||
|
route: ActivatedRouteSnapshot
|
||||||
|
) => {
|
||||||
|
const userService: UserService = inject(UserService);
|
||||||
|
const router: Router = inject(Router);
|
||||||
|
|
||||||
return userService.isAuthenticated.pipe(
|
return userService.isAuthenticated.pipe(
|
||||||
map((isAuth) => {
|
map((isAuth) => {
|
||||||
if (!isAuth) {
|
if (!isAuth) {
|
||||||
|
|
@ -83,15 +109,15 @@ const routes: Routes = [
|
||||||
import('./features/recipes/recipes.component').then(
|
import('./features/recipes/recipes.component').then(
|
||||||
(m) => m.RecipesComponent
|
(m) => m.RecipesComponent
|
||||||
),
|
),
|
||||||
canActivate: [authGuard],
|
canActivate: [authGuard, permissionsGuard(UserPermissions.THAI_PERMISSION, UserPermissions.SUPER_ADMIN)],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'recipe/:productCode',
|
path: 'recipe/:productCode',
|
||||||
loadComponent: () =>
|
loadComponent: () =>
|
||||||
import(
|
import(
|
||||||
'./features/recipes/recipe-details/recipe-details.component'
|
'./features/recipes/recipe-details/recipe-details.component'
|
||||||
).then((m) => m.RecipeDetailsComponent),
|
).then((m) => m.RecipeDetailsComponent),
|
||||||
canActivate: [authGuard],
|
canActivate: [authGuard, permissionsGuard(UserPermissions.THAI_PERMISSION, UserPermissions.SUPER_ADMIN)],
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// path: 'log',
|
// path: 'log',
|
||||||
|
|
@ -100,9 +126,17 @@ const routes: Routes = [
|
||||||
// (m) => m.ChangelogComponent
|
// (m) => m.ChangelogComponent
|
||||||
// ),
|
// ),
|
||||||
// },
|
// },
|
||||||
|
{
|
||||||
|
path: 'unauthorized',
|
||||||
|
loadComponent: () => import('./core/auth/unauthorized.component').then((m) => m.UnauthorizedComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'notfound',
|
||||||
|
loadComponent: () => import('./core/notfound.component').then((m) => m.NotfoundComponent)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '**',
|
path: '**',
|
||||||
redirectTo: 'recipes',
|
redirectTo: 'notfound',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
@ -112,4 +146,5 @@ const routes: Routes = [
|
||||||
imports: [RouterModule.forRoot(routes)],
|
imports: [RouterModule.forRoot(routes)],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class AppRoutingModule {}
|
export class AppRoutingModule {
|
||||||
|
}
|
||||||
|
|
|
||||||
31
client/src/app/core/auth/unauthorized.component.ts
Normal file
31
client/src/app/core/auth/unauthorized.component.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-unauthorized',
|
||||||
|
standalone: true,
|
||||||
|
template: `
|
||||||
|
<div class="flex pt-[100px] justify-center h-screen">
|
||||||
|
<h1 class="text-2xl font-bold text-center text-red-500">
|
||||||
|
Unauthorized, You 🫵🏻 not have permissions
|
||||||
|
</h1>
|
||||||
|
</div>`
|
||||||
|
})
|
||||||
|
export class UnauthorizedComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private router: Router,
|
||||||
|
private activatedRoute: ActivatedRoute
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
const redirectUrl = this.activatedRoute.snapshot.queryParams['redirectUrl'];
|
||||||
|
if (redirectUrl) {
|
||||||
|
this.router.navigate([redirectUrl]).catch(() => {
|
||||||
|
// redirect to login page
|
||||||
|
void this.router.navigate(['/login']);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
client/src/app/core/auth/userPermissions.ts
Normal file
12
client/src/app/core/auth/userPermissions.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
export enum UserPermissions {
|
||||||
|
NO_PERMISSION,
|
||||||
|
THAI_PERMISSION = 1 << 0,
|
||||||
|
MALAY_PERMISSION = 1 << 1,
|
||||||
|
AUS_PERMISSION = 1 << 2,
|
||||||
|
SUPER_ADMIN = 1 << 3
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPermissions(perms: number) : UserPermissions[] {
|
||||||
|
return Object.values(UserPermissions)
|
||||||
|
.filter(permission => typeof permission === 'number' && (perms & permission) !== 0) as UserPermissions[];
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
|
import {getPermissions} from "../auth/userPermissions";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-callback',
|
selector: 'app-callback',
|
||||||
|
|
@ -19,18 +19,19 @@ export class CallbackComponent implements OnInit {
|
||||||
this.route.queryParams.subscribe((params) => {
|
this.route.queryParams.subscribe((params) => {
|
||||||
console.log(params);
|
console.log(params);
|
||||||
|
|
||||||
if (params['email'] && params['name'] && params['picture']) {
|
if (params['email'] && params['name'] && params['picture'] && params['permissions']) {
|
||||||
this.userService.setAuth({
|
this.userService.setAuth({
|
||||||
email: params['email'],
|
email: params['email'],
|
||||||
name: params['name'],
|
name: params['name'],
|
||||||
picture: params['picture'],
|
picture: params['picture'],
|
||||||
|
permissions: getPermissions(params['permissions'])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params['redirect_to']) {
|
if (params['redirect_to']) {
|
||||||
this.router.navigate([params['redirect_to']]);
|
void this.router.navigate([params['redirect_to']]);
|
||||||
} else {
|
} else {
|
||||||
this.router.navigate(['/']);
|
void this.router.navigate(['/']);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
|
import {UserPermissions} from "../auth/userPermissions";
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
email: string;
|
email: string;
|
||||||
name: string;
|
name: string;
|
||||||
picture: string;
|
picture: string;
|
||||||
|
permissions: UserPermissions[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
10
client/src/app/core/notfound.component.ts
Normal file
10
client/src/app/core/notfound.component.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-notfound',
|
||||||
|
standalone: true,
|
||||||
|
template: `<h1>Not Found!!!</h1>`
|
||||||
|
})
|
||||||
|
export class NotfoundComponent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -2,10 +2,9 @@ import { Injectable } from '@angular/core';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
Observable,
|
|
||||||
distinctUntilChanged,
|
distinctUntilChanged,
|
||||||
map,
|
map,
|
||||||
tap,
|
|
||||||
} from 'rxjs';
|
} from 'rxjs';
|
||||||
import { User } from '../models/user.model';
|
import { User } from '../models/user.model';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
|
|
@ -31,6 +30,10 @@ export class UserService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCurrentUser(): User | null {
|
||||||
|
return this.currentUserSubject.value
|
||||||
|
}
|
||||||
|
|
||||||
logout(): void {
|
logout(): void {
|
||||||
this.purgeAuth();
|
this.purgeAuth();
|
||||||
// post to api /revoke with cookie
|
// post to api /revoke with cookie
|
||||||
|
|
@ -43,21 +46,6 @@ export class UserService {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentUser(): Observable<{ user: User }> {
|
|
||||||
return this.http
|
|
||||||
.get<{ user: User }>(environment.api + '/auth/user', {
|
|
||||||
withCredentials: true,
|
|
||||||
})
|
|
||||||
.pipe(
|
|
||||||
tap({
|
|
||||||
next: ({ user }) => {
|
|
||||||
this.setAuth(user);
|
|
||||||
},
|
|
||||||
error: () => this.purgeAuth(),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setAuth(user: User): void {
|
setAuth(user: User): void {
|
||||||
window.localStorage.setItem('user', JSON.stringify(user));
|
window.localStorage.setItem('user', JSON.stringify(user));
|
||||||
void this.currentUserSubject.next(user);
|
void this.currentUserSubject.next(user);
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { CommonModule, DatePipe } from '@angular/common';
|
||||||
import { Component, EventEmitter, OnInit } from '@angular/core';
|
import { Component, EventEmitter, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
import { ActivatedRoute, Router, RouterLink } from '@angular/router';
|
||||||
import { Observable, first } from 'rxjs';
|
import {Observable, first, map} from 'rxjs';
|
||||||
import { RecipeService } from 'src/app/core/services/recipe.service';
|
import { RecipeService } from 'src/app/core/services/recipe.service';
|
||||||
import { ConfirmModal } from 'src/app/shared/modal/confirm/confirm-modal.component';
|
import { ConfirmModal } from 'src/app/shared/modal/confirm/confirm-modal.component';
|
||||||
import { animate, style, transition, trigger } from '@angular/animations';
|
import { animate, style, transition, trigger } from '@angular/animations';
|
||||||
|
|
@ -11,8 +11,7 @@ import {
|
||||||
RecipeDetail,
|
RecipeDetail,
|
||||||
RecipeDetailMat,
|
RecipeDetailMat,
|
||||||
} from 'src/app/core/models/recipe.model';
|
} from 'src/app/core/models/recipe.model';
|
||||||
import { Action, ActionRecord } from 'src/app/shared/actionRecord/actionRecord';
|
import { ActionRecord } from 'src/app/shared/actionRecord/actionRecord';
|
||||||
import { isEqual } from 'lodash';
|
|
||||||
import { UserService } from 'src/app/core/services/user.service';
|
import { UserService } from 'src/app/core/services/user.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
@ -119,10 +118,9 @@ export class RecipeDetailsComponent implements OnInit {
|
||||||
|
|
||||||
// get username
|
// get username
|
||||||
let username:string = ""
|
let username:string = ""
|
||||||
this._userService.getCurrentUser().subscribe((user) => {
|
this._userService.currentUser.pipe(map((user) => {
|
||||||
username = user.user.name;
|
if (user)
|
||||||
|
username = user.name;
|
||||||
|
|
||||||
|
|
||||||
let to_send = {
|
let to_send = {
|
||||||
edit_by: username,
|
edit_by: username,
|
||||||
|
|
@ -152,8 +150,8 @@ export class RecipeDetailsComponent implements OnInit {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
console.log('Sending changes');
|
console.log('Sending changes');
|
||||||
this._router.navigate(['/recipes']);
|
void this._router.navigate(['/recipes']);
|
||||||
})
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -198,6 +196,6 @@ export class RecipeDetailsComponent implements OnInit {
|
||||||
onRecipeListFormChange(repl: unknown[]) {
|
onRecipeListFormChange(repl: unknown[]) {
|
||||||
console.log('Recipe List Form Changed', repl);
|
console.log('Recipe List Form Changed', repl);
|
||||||
this.repl = repl as never[];
|
this.repl = repl as never[];
|
||||||
this.isValueChanged ||= repl != undefined ? true : false;
|
this.isValueChanged ||= repl != undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -1,173 +1,173 @@
|
||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DynamicCompare compares two values dynamically and returns true if they are equal.
|
// DynamicCompare compares two values dynamically and returns true if they are equal.
|
||||||
//
|
//
|
||||||
// Parameters:
|
// Parameters:
|
||||||
//
|
//
|
||||||
// - s: The first value to compare.
|
// - s: The first value to compare.
|
||||||
//
|
//
|
||||||
// - u: The second value to compare.
|
// - u: The second value to compare.
|
||||||
//
|
//
|
||||||
// Returns:
|
// Returns:
|
||||||
//
|
//
|
||||||
// - bool: True if the values are equal, false otherwise.
|
// - bool: True if the values are equal, false otherwise.
|
||||||
//
|
//
|
||||||
// - error: An error if the values cannot be compared.
|
// - error: An error if the values cannot be compared.
|
||||||
func DynamicCompare(s interface{}, u interface{}) (bool, error) {
|
func DynamicCompare(s interface{}, u interface{}) (bool, error) {
|
||||||
switch t := s.(type) {
|
switch t := s.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
u, ok := u.(bool)
|
u, ok := u.(bool)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("[bool] cannot compare %T and %T, %v and %v", t, u, s, u)
|
return false, fmt.Errorf("[bool] cannot compare %T and %T, %v and %v", t, u, s, u)
|
||||||
}
|
}
|
||||||
return t == u, nil
|
return t == u, nil
|
||||||
case string:
|
case string:
|
||||||
u, ok := u.(string)
|
u, ok := u.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("[string] cannot compare %T and %T, %v and %v", t, u, s, u)
|
return false, fmt.Errorf("[string] cannot compare %T and %T, %v and %v", t, u, s, u)
|
||||||
}
|
}
|
||||||
return t == u, nil
|
return t == u, nil
|
||||||
case int:
|
case int:
|
||||||
u, ok := u.(int)
|
u, ok := u.(int)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("[int] cannot compare %T and %T, %v and %v", t, u, s, u)
|
return false, fmt.Errorf("[int] cannot compare %T and %T, %v and %v", t, u, s, u)
|
||||||
}
|
}
|
||||||
return t == u, nil
|
return t == u, nil
|
||||||
case float64:
|
case float64:
|
||||||
u, ok := u.(float64)
|
u, ok := u.(float64)
|
||||||
// Log.Debug("[helpers] DynamicCompare", zap.Any("u", u), zap.Any("ok", ok), zap.Any("test_compare*(t==u)", t == u))
|
// Log.Debug("[helpers] DynamicCompare", zap.Any("user", u), zap.Any("ok", ok), zap.Any("test_compare*(t==u)", t == u))
|
||||||
|
|
||||||
if t == u {
|
if t == u {
|
||||||
return t == u, nil
|
return t == u, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return false, fmt.Errorf("[float64] cannot compare %T and %T, %v and %v", t, u, s, u)
|
return false, fmt.Errorf("[float64] cannot compare %T and %T, %v and %v", t, u, s, u)
|
||||||
}
|
}
|
||||||
return t == u, nil
|
return t == u, nil
|
||||||
case nil:
|
case nil:
|
||||||
return t == u, nil
|
return t == u, nil
|
||||||
case []interface{}:
|
case []interface{}:
|
||||||
for i := range t {
|
for i := range t {
|
||||||
if ok, err := DynamicCompare(t[i], u); err != nil {
|
if ok, err := DynamicCompare(t[i], u); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
for _, v := range t {
|
for _, v := range t {
|
||||||
if ok, err := DynamicCompare(v, u); err != nil {
|
if ok, err := DynamicCompare(v, u); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
} else if !ok {
|
} else if !ok {
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("[unknown] not in case. Cannot compare %T and %T, %v and %v", t, u, s, u)
|
return false, fmt.Errorf("[unknown] not in case. Cannot compare %T and %T, %v and %v", t, u, s, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u == nil {
|
if u == nil {
|
||||||
return false, fmt.Errorf("[empty] the compared value is nil")
|
return false, fmt.Errorf("[empty] the compared value is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, fmt.Errorf("[unknown] unexpected error. [old] %v and [new] %v", s, u)
|
return false, fmt.Errorf("[unknown] unexpected error. [old] %v and [new] %v", s, u)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTempFile(filename string, user string, suffix int) string {
|
func GetTempFile(filename string, user string, suffix int) string {
|
||||||
|
|
||||||
// Check if the temp file exist
|
// Check if the temp file exist
|
||||||
_, err := os.Stat(filename)
|
_, err := os.Stat(filename)
|
||||||
|
|
||||||
// Log.Debug("[helpers] GetTempFile", zap.Any("filename", filename), zap.Any("suffix", suffix), zap.Any("err", err))
|
// Log.Debug("[helpers] GetTempFile", zap.Any("filename", filename), zap.Any("suffix", suffix), zap.Any("err", err))
|
||||||
|
|
||||||
// file not exists
|
// file not exists
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
// Create temp file
|
// Create temp file
|
||||||
if suffix == 0 {
|
if suffix == 0 {
|
||||||
|
|
||||||
return strings.Replace(filename, ".json", "_"+user+".tmp"+strconv.Itoa(suffix), 1)
|
return strings.Replace(filename, ".json", "_"+user+".tmp"+strconv.Itoa(suffix), 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// change extension from json to tmp
|
// change extension from json to tmp
|
||||||
filename = strings.Replace(filename, ".json", "_"+user+".tmp"+strconv.Itoa(suffix), 1)
|
filename = strings.Replace(filename, ".json", "_"+user+".tmp"+strconv.Itoa(suffix), 1)
|
||||||
|
|
||||||
return filename
|
return filename
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if strings.Contains(filename, ".tmp") {
|
if strings.Contains(filename, ".tmp") {
|
||||||
return GetTempFile(strings.Replace(filename, "_"+user+".tmp"+strconv.Itoa(suffix-1), "_"+user+".tmp"+strconv.Itoa(suffix), 1), user, suffix+1)
|
return GetTempFile(strings.Replace(filename, "_"+user+".tmp"+strconv.Itoa(suffix-1), "_"+user+".tmp"+strconv.Itoa(suffix), 1), user, suffix+1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// recursive call
|
// recursive call
|
||||||
return GetTempFile(strings.Replace(filename, ".json", "_"+user+".tmp"+strconv.Itoa(suffix), 1), user, suffix+1)
|
return GetTempFile(strings.Replace(filename, ".json", "_"+user+".tmp"+strconv.Itoa(suffix), 1), user, suffix+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// func PackTempToRealFile(data *data.Data, countryID string, filename string) {
|
// func PackTempToRealFile(data *data.Data, countryID string, filename string) {
|
||||||
|
|
||||||
// // list file that end with .tmp*
|
// // list file that end with .tmp*
|
||||||
// files, err := filepath.Glob(filename + ".tmp*")
|
// files, err := filepath.Glob(filename + ".tmp*")
|
||||||
|
|
||||||
// // for all files, read and get configNumber
|
// // for all files, read and get configNumber
|
||||||
|
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// Log.Error("[helpers] PackTempToRealFile", zap.Error(err))
|
// Log.Error("[helpers] PackTempToRealFile", zap.Error(err))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // get configNumber from actual filename.json
|
// // get configNumber from actual filename.json
|
||||||
|
|
||||||
// //
|
// //
|
||||||
// base_recipe := data.GetRecipe(countryID, filename)
|
// base_recipe := data.GetRecipe(countryID, filename)
|
||||||
|
|
||||||
// // read file and apply tmp file from 0 to tmpX.
|
// // read file and apply tmp file from 0 to tmpX.
|
||||||
// // - if there is more than 1 user that access this file at the same time,
|
// // - if there is more than 1 user that access this file at the same time,
|
||||||
// // pack in order, and if conflict, stop
|
// // pack in order, and if conflict, stop
|
||||||
|
|
||||||
// if len(files) == 0 {
|
// if len(files) == 0 {
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // TODO: must check the changes
|
// // TODO: must check the changes
|
||||||
|
|
||||||
// for _, file := range files {
|
// for _, file := range files {
|
||||||
// var tmpdata models.Recipe
|
// var tmpdata models.Recipe
|
||||||
// tmpfile, err := os.Open(file)
|
// tmpfile, err := os.Open(file)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
// _ = json.NewDecoder(tmpfile).Decode(&tmpdata)
|
// _ = json.NewDecoder(tmpfile).Decode(&tmpdata)
|
||||||
// // apply change
|
// // apply change
|
||||||
|
|
||||||
// // = tmpdata.Recipe01
|
// // = tmpdata.Recipe01
|
||||||
|
|
||||||
// for key, val := range tmpdata.Recipe01 {
|
// for key, val := range tmpdata.Recipe01 {
|
||||||
|
|
||||||
// test_bol, err := DynamicCompare(base_recipe.Recipe01[key], val)
|
// test_bol, err := DynamicCompare(base_recipe.Recipe01[key], val)
|
||||||
|
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// Log.Error("[helpers] PackTempToRealFile", zap.Error(err))
|
// Log.Error("[helpers] PackTempToRealFile", zap.Error(err))
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// if !test_bol {
|
// if !test_bol {
|
||||||
// base_recipe.Recipe01[key] = val
|
// base_recipe.Recipe01[key] = val
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// // verify changes between tmpX and actual filename.json
|
// // verify changes between tmpX and actual filename.json
|
||||||
|
|
||||||
// // if changes, rename tmpX to filename (version +1) .json
|
// // if changes, rename tmpX to filename (version +1) .json
|
||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
@ -22,7 +23,7 @@ func main() {
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-shutdownCtx.Done()
|
<-shutdownCtx.Done()
|
||||||
if shutdownCtx.Err() == context.DeadlineExceeded {
|
if errors.Is(shutdownCtx.Err(), context.DeadlineExceeded) {
|
||||||
log.Println("Shutdown timeout, force exit")
|
log.Println("Shutdown timeout, force exit")
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,87 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
"net/http"
|
"net/http"
|
||||||
"recipe-manager/enums/permissions"
|
"recipe-manager/enums/permissions"
|
||||||
"recipe-manager/models"
|
"recipe-manager/models"
|
||||||
|
"recipe-manager/services/oauth"
|
||||||
|
"recipe-manager/services/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Authorize(p []permissions.Permission, nextRoute http.HandlerFunc) http.HandlerFunc {
|
// ========================== ValidatePermissions =========================================
|
||||||
|
func Authorize(oauthService oauth.OAuthService, userService user.UserService, nextRoute http.Handler) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
user := r.Context().Value("user").(*models.User)
|
token := &oauth2.Token{}
|
||||||
|
|
||||||
|
if cookie, err := r.Cookie("access_token"); err == nil {
|
||||||
|
token.AccessToken = cookie.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, err := oauthService.GetUserInfo(r.Context(), token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// if have refresh token, set refresh token to token
|
||||||
|
if cookie, err := r.Cookie("refresh_token"); err == nil {
|
||||||
|
token.RefreshToken = cookie.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
newToken, err := oauthService.RefreshToken(r.Context(), token)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfo, err = oauthService.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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
nextRoute.ServeHTTP(w, r.WithContext(ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================== Permissions =========================================
|
||||||
|
|
||||||
|
func ValidatePermissions(p []permissions.Permission, nextRoute http.HandlerFunc) http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
u := r.Context().Value("user").(*models.User)
|
||||||
|
|
||||||
for _, pm := range p {
|
for _, pm := range p {
|
||||||
if !user.Permissions.IsHavePermission(pm) {
|
if !u.Permissions.IsHavePermission(pm) {
|
||||||
// If not have permission response unauthorized
|
// If not have permission response unauthorized
|
||||||
w.WriteHeader(http.StatusUnauthorized)
|
w.WriteHeader(http.StatusUnauthorized)
|
||||||
err := json.NewEncoder(w).Encode("Unauthorized")
|
err := json.NewEncoder(w).Encode("Unauthorized")
|
||||||
|
|
@ -28,10 +96,10 @@ func Authorize(p []permissions.Permission, nextRoute http.HandlerFunc) http.Hand
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func OwnOrAuthorize(p []permissions.Permission, nextRoute http.HandlerFunc) http.HandlerFunc {
|
func ValidateOwnerOrPermissions(p []permissions.Permission, nextRoute http.HandlerFunc) http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
reqUserID := chi.URLParam(r, "id")
|
reqUserID := chi.URLParam(r, "id")
|
||||||
user := r.Context().Value("user").(*models.User)
|
u := r.Context().Value("user").(*models.User)
|
||||||
|
|
||||||
if reqUserID == "" {
|
if reqUserID == "" {
|
||||||
// If not have permission response unauthorized
|
// If not have permission response unauthorized
|
||||||
|
|
@ -43,11 +111,11 @@ func OwnOrAuthorize(p []permissions.Permission, nextRoute http.HandlerFunc) http
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if reqUserID == user.ID {
|
if reqUserID == u.ID {
|
||||||
nextRoute.ServeHTTP(w, r)
|
nextRoute.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
Authorize(p, nextRoute)
|
ValidatePermissions(p, nextRoute)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,14 +27,14 @@ func (ur *UserRouter) Route(r chi.Router) {
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
r.Route("/users", func(r chi.Router) {
|
r.Route("/users", func(r chi.Router) {
|
||||||
r.Get("/", middlewares.Authorize([]permissions.Permission{permissions.SuperAdmin}, ur.getUsers))
|
r.Get("/", middlewares.ValidatePermissions([]permissions.Permission{permissions.SuperAdmin}, ur.getUsers))
|
||||||
|
|
||||||
r.Post("/", middlewares.Authorize([]permissions.Permission{permissions.SuperAdmin}, ur.createUser))
|
r.Post("/", middlewares.ValidatePermissions([]permissions.Permission{permissions.SuperAdmin}, ur.createUser))
|
||||||
})
|
})
|
||||||
|
|
||||||
// User
|
// User
|
||||||
r.Route("/user", func(r chi.Router) {
|
r.Route("/user", func(r chi.Router) {
|
||||||
r.Get("/{id}", middlewares.OwnOrAuthorize([]permissions.Permission{permissions.SuperAdmin}, ur.getUser))
|
r.Get("/{id}", middlewares.ValidateOwnerOrPermissions([]permissions.Permission{permissions.SuperAdmin}, ur.getUser))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,7 +56,7 @@ func (ur *UserRouter) createUser(w http.ResponseWriter, r *http.Request) {
|
||||||
ur.taoLogger.Log.Error("UserRouter.CreateUser", zap.Error(err))
|
ur.taoLogger.Log.Error("UserRouter.CreateUser", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
ur.taoLogger.Log.Info("UserRouter.CreateUser", zap.Reflect("u", u))
|
ur.taoLogger.Log.Info("UserRouter.CreateUser", zap.Reflect("user", u))
|
||||||
|
|
||||||
if err := ur.userService.CreateNewUser(ctx, u.Name, u.Email, u.Picture, u.Permissions); err != nil {
|
if err := ur.userService.CreateNewUser(ctx, u.Name, u.Email, u.Picture, u.Permissions); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
"recipe-manager/config"
|
"recipe-manager/config"
|
||||||
"recipe-manager/data"
|
"recipe-manager/data"
|
||||||
"recipe-manager/enums/permissions"
|
"recipe-manager/enums/permissions"
|
||||||
|
"recipe-manager/middlewares"
|
||||||
"recipe-manager/models"
|
"recipe-manager/models"
|
||||||
"recipe-manager/routers"
|
"recipe-manager/routers"
|
||||||
"recipe-manager/services/logger"
|
"recipe-manager/services/logger"
|
||||||
|
|
@ -29,7 +30,6 @@ import (
|
||||||
"github.com/go-chi/cors"
|
"github.com/go-chi/cors"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
"golang.org/x/oauth2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
@ -116,7 +116,7 @@ func (s *Server) createHandler() {
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// Recipe Service
|
// Recipe Service
|
||||||
recipeService := recipe.NewRecipeService(s.data)
|
recipeService := recipe.NewRecipeService(s.data, s.taoLogger)
|
||||||
|
|
||||||
// User Service
|
// User Service
|
||||||
userService := user.NewUserService(s.cfg, s.database, s.taoLogger)
|
userService := user.NewUserService(s.cfg, s.database, s.taoLogger)
|
||||||
|
|
@ -134,63 +134,7 @@ func (s *Server) createHandler() {
|
||||||
r.Group(func(r chi.Router) {
|
r.Group(func(r chi.Router) {
|
||||||
|
|
||||||
r.Use(func(next http.Handler) http.Handler {
|
r.Use(func(next http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return middlewares.Authorize(s.oauth, userService, next)
|
||||||
token := &oauth2.Token{}
|
|
||||||
|
|
||||||
if cookie, err := r.Cookie("access_token"); err == nil {
|
|
||||||
token.AccessToken = cookie.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfo, err := s.oauth.GetUserInfo(r.Context(), token)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// if have refresh token, set refresh token to token
|
|
||||||
if cookie, err := r.Cookie("refresh_token"); err == nil {
|
|
||||||
token.RefreshToken = cookie.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
newToken, err := s.oauth.RefreshToken(r.Context(), token)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
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))
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Post("/merge", func(w http.ResponseWriter, r *http.Request) {
|
r.Post("/merge", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
@ -198,10 +142,10 @@ func (s *Server) createHandler() {
|
||||||
// locking
|
// locking
|
||||||
if !pyAPIhandler(w, r) {
|
if !pyAPIhandler(w, r) {
|
||||||
s.taoLogger.Log.Warn("Merge - u tried to access while another u is requesting merge",
|
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))
|
zap.String("user", r.Context().Value("user").(*models.User).Name))
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
s.taoLogger.Log.Debug("Merge - u has access", zap.String("u", r.Context().Value("u").(*models.User).Name))
|
s.taoLogger.Log.Debug("Merge - u has access", zap.String("user", r.Context().Value("user").(*models.User).Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
var targetMap map[string]interface{}
|
var targetMap map[string]interface{}
|
||||||
|
|
@ -234,8 +178,8 @@ func (s *Server) createHandler() {
|
||||||
dev_path := repo_path + dev_version + ".json"
|
dev_path := repo_path + dev_version + ".json"
|
||||||
|
|
||||||
// Get who's requesting
|
// Get who's requesting
|
||||||
u := r.Context().Value("u").(*models.User)
|
u := r.Context().Value("user").(*models.User)
|
||||||
s.taoLogger.Log.Info("Request merge by", zap.String("u", u.Name))
|
s.taoLogger.Log.Info("Request merge by", zap.String("user", u.Name))
|
||||||
|
|
||||||
// lookup for python exec
|
// lookup for python exec
|
||||||
pyExec, err := exec.LookPath("python")
|
pyExec, err := exec.LookPath("python")
|
||||||
|
|
|
||||||
|
|
@ -1,204 +1,202 @@
|
||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"recipe-manager/contracts"
|
"recipe-manager/contracts"
|
||||||
"recipe-manager/data"
|
"recipe-manager/data"
|
||||||
"recipe-manager/models"
|
"recipe-manager/models"
|
||||||
"recipe-manager/services/logger"
|
"recipe-manager/services/logger"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
type RecipeService interface {
|
||||||
Log = logger.GetInstance()
|
GetRecipeDashboard(request *contracts.RecipeDashboardRequest) (contracts.RecipeDashboardResponse, error)
|
||||||
)
|
GetRecipeOverview(request *contracts.RecipeOverviewRequest) (contracts.RecipeOverviewResponse, error)
|
||||||
|
|
||||||
type RecipeService interface {
|
GetRecipeDetail(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailResponse, error)
|
||||||
GetRecipeDashboard(request *contracts.RecipeDashboardRequest) (contracts.RecipeDashboardResponse, error)
|
GetRecipeDetailMat(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailMatListResponse, error)
|
||||||
GetRecipeOverview(request *contracts.RecipeOverviewRequest) (contracts.RecipeOverviewResponse, error)
|
}
|
||||||
|
|
||||||
GetRecipeDetail(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailResponse, error)
|
type recipeService struct {
|
||||||
GetRecipeDetailMat(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailMatListResponse, error)
|
db *data.Data
|
||||||
}
|
taoLogger *logger.TaoLogger
|
||||||
|
}
|
||||||
type recipeService struct {
|
|
||||||
db *data.Data
|
// GetRecipeDetail implements RecipeService.
|
||||||
}
|
func (rs *recipeService) GetRecipeDetail(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailResponse, error) {
|
||||||
|
|
||||||
// GetRecipeDetail implements RecipeService.
|
rs.taoLogger.Log.Debug("GetRecipeDetail", zap.Any("request", request))
|
||||||
func (rs *recipeService) GetRecipeDetail(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailResponse, error) {
|
|
||||||
|
recipe, err := rs.db.GetRecipe01ByProductCode(request.Filename, request.Country, request.ProductCode)
|
||||||
Log.Debug("GetRecipeDetail", zap.Any("request", request))
|
|
||||||
|
if err != nil {
|
||||||
recipe, err := rs.db.GetRecipe01ByProductCode(request.Filename, request.Country, request.ProductCode)
|
return contracts.RecipeDetailResponse{}, err
|
||||||
|
}
|
||||||
if err != nil {
|
|
||||||
return contracts.RecipeDetailResponse{}, err
|
// DEBUG: picture
|
||||||
}
|
rs.taoLogger.Log.Debug("GetRecipeDetail", zap.String("picture", recipe.UriData))
|
||||||
|
|
||||||
// DEBUG: picture
|
result := contracts.RecipeDetailResponse{
|
||||||
Log.Debug("GetRecipeDetail", zap.String("picture", recipe.UriData))
|
Name: recipe.Name,
|
||||||
|
OtherName: recipe.OtherName,
|
||||||
result := contracts.RecipeDetailResponse{
|
Description: recipe.Description,
|
||||||
Name: recipe.Name,
|
OtherDescription: recipe.OtherDescription,
|
||||||
OtherName: recipe.OtherName,
|
LastUpdated: recipe.LastChange,
|
||||||
Description: recipe.Description,
|
Picture: recipe.UriData[len("img="):], // remove "img=" prefix
|
||||||
OtherDescription: recipe.OtherDescription,
|
}
|
||||||
LastUpdated: recipe.LastChange,
|
|
||||||
Picture: recipe.UriData[len("img="):], // remove "img=" prefix
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
// GetRecipeDetailMat implements RecipeService.
|
||||||
}
|
func (rs *recipeService) GetRecipeDetailMat(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailMatListResponse, error) {
|
||||||
|
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
||||||
// GetRecipeDetailMat implements RecipeService.
|
|
||||||
func (rs *recipeService) GetRecipeDetailMat(request *contracts.RecipeDetailRequest) (contracts.RecipeDetailMatListResponse, error) {
|
if err != nil {
|
||||||
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
return contracts.RecipeDetailMatListResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
||||||
|
}
|
||||||
if err != nil {
|
|
||||||
return contracts.RecipeDetailMatListResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
recipe, err := rs.db.GetRecipe01ByProductCode(request.Filename, request.Country, request.ProductCode)
|
||||||
}
|
|
||||||
|
if err != nil {
|
||||||
recipe, err := rs.db.GetRecipe01ByProductCode(request.Filename, request.Country, request.ProductCode)
|
return contracts.RecipeDetailMatListResponse{}, err
|
||||||
|
}
|
||||||
if err != nil {
|
|
||||||
return contracts.RecipeDetailMatListResponse{}, err
|
matIds := []uint64{}
|
||||||
}
|
for _, v := range recipe.Recipes {
|
||||||
|
if v.IsUse {
|
||||||
matIds := []uint64{}
|
matIds = append(matIds, uint64(v.MaterialPathId))
|
||||||
for _, v := range recipe.Recipes {
|
}
|
||||||
if v.IsUse {
|
}
|
||||||
matIds = append(matIds, uint64(v.MaterialPathId))
|
|
||||||
}
|
matsCode := rs.db.GetMaterialCode(matIds, countryID, request.Filename)
|
||||||
}
|
|
||||||
|
result := contracts.RecipeDetailMatListResponse{
|
||||||
matsCode := rs.db.GetMaterialCode(matIds, countryID, request.Filename)
|
Result: []contracts.RecipeDetailMat{},
|
||||||
|
}
|
||||||
result := contracts.RecipeDetailMatListResponse{
|
|
||||||
Result: []contracts.RecipeDetailMat{},
|
for _, v := range recipe.Recipes {
|
||||||
}
|
for _, mat := range matsCode {
|
||||||
|
if v.MaterialPathId == int(mat.MaterialID) {
|
||||||
for _, v := range recipe.Recipes {
|
result.Result = append(result.Result, contracts.RecipeDetailMat{
|
||||||
for _, mat := range matsCode {
|
IsUse: v.IsUse,
|
||||||
if v.MaterialPathId == int(mat.MaterialID) {
|
MaterialID: mat.MaterialID,
|
||||||
result.Result = append(result.Result, contracts.RecipeDetailMat{
|
Name: mat.PackageDescription,
|
||||||
IsUse: v.IsUse,
|
MixOrder: v.MixOrder,
|
||||||
MaterialID: mat.MaterialID,
|
FeedParameter: v.FeedParameter,
|
||||||
Name: mat.PackageDescription,
|
FeedPattern: v.FeedPattern,
|
||||||
MixOrder: v.MixOrder,
|
MaterialPathId: v.MaterialPathId,
|
||||||
FeedParameter: v.FeedParameter,
|
PowderGram: v.PowderGram,
|
||||||
FeedPattern: v.FeedPattern,
|
PowderTime: v.PowderTime,
|
||||||
MaterialPathId: v.MaterialPathId,
|
StirTime: v.StirTime,
|
||||||
PowderGram: v.PowderGram,
|
SyrupGram: v.SyrupGram,
|
||||||
PowderTime: v.PowderTime,
|
SyrupTime: v.SyrupTime,
|
||||||
StirTime: v.StirTime,
|
WaterCold: v.WaterCold,
|
||||||
SyrupGram: v.SyrupGram,
|
WaterYield: v.WaterYield,
|
||||||
SyrupTime: v.SyrupTime,
|
})
|
||||||
WaterCold: v.WaterCold,
|
break
|
||||||
WaterYield: v.WaterYield,
|
}
|
||||||
})
|
}
|
||||||
break
|
}
|
||||||
}
|
|
||||||
}
|
// sort by id
|
||||||
}
|
// sort.Slice(result.Result, func(i, j int) bool {
|
||||||
|
// return result.Result[i].MaterialID < result.Result[j].MaterialID
|
||||||
// sort by id
|
// })
|
||||||
// sort.Slice(result.Result, func(i, j int) bool {
|
|
||||||
// return result.Result[i].MaterialID < result.Result[j].MaterialID
|
return result, nil
|
||||||
// })
|
}
|
||||||
|
|
||||||
return result, nil
|
func (rs *recipeService) GetRecipeDashboard(request *contracts.RecipeDashboardRequest) (contracts.RecipeDashboardResponse, error) {
|
||||||
}
|
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
||||||
|
|
||||||
func (rs *recipeService) GetRecipeDashboard(request *contracts.RecipeDashboardRequest) (contracts.RecipeDashboardResponse, error) {
|
if err != nil {
|
||||||
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
return contracts.RecipeDashboardResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
||||||
|
}
|
||||||
if err != nil {
|
|
||||||
return contracts.RecipeDashboardResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
recipe := rs.db.GetRecipe(countryID, request.Filename)
|
||||||
}
|
|
||||||
|
result := contracts.RecipeDashboardResponse{
|
||||||
recipe := rs.db.GetRecipe(countryID, request.Filename)
|
ConfigNumber: recipe.MachineSetting.ConfigNumber,
|
||||||
|
LastUpdated: recipe.Timestamp,
|
||||||
result := contracts.RecipeDashboardResponse{
|
Filename: request.Filename,
|
||||||
ConfigNumber: recipe.MachineSetting.ConfigNumber,
|
}
|
||||||
LastUpdated: recipe.Timestamp,
|
|
||||||
Filename: request.Filename,
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
func (rs *recipeService) GetRecipeOverview(request *contracts.RecipeOverviewRequest) (contracts.RecipeOverviewResponse, error) {
|
||||||
}
|
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
||||||
|
|
||||||
func (rs *recipeService) GetRecipeOverview(request *contracts.RecipeOverviewRequest) (contracts.RecipeOverviewResponse, error) {
|
if err != nil {
|
||||||
countryID, err := rs.db.GetCountryIDByName(request.Country)
|
return contracts.RecipeOverviewResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
||||||
|
}
|
||||||
if err != nil {
|
recipe := rs.db.GetRecipe(countryID, request.Filename)
|
||||||
return contracts.RecipeOverviewResponse{}, fmt.Errorf("country name: %s not found", request.Country)
|
recipeFilter := recipe.Recipe01
|
||||||
}
|
|
||||||
recipe := rs.db.GetRecipe(countryID, request.Filename)
|
result := contracts.RecipeOverviewResponse{}
|
||||||
recipeFilter := recipe.Recipe01
|
|
||||||
|
if request.Search != "" {
|
||||||
result := contracts.RecipeOverviewResponse{}
|
var searchResult []models.Recipe01
|
||||||
|
for _, v := range recipeFilter {
|
||||||
if request.Search != "" {
|
if strings.Contains(strings.ToLower(v.ProductCode), strings.ToLower(request.Search)) ||
|
||||||
searchResult := []models.Recipe01{}
|
strings.Contains(strings.ToLower(v.Name), strings.ToLower(request.Search)) ||
|
||||||
for _, v := range recipeFilter {
|
strings.Contains(strings.ToLower(v.OtherName), strings.ToLower(request.Search)) {
|
||||||
if strings.Contains(strings.ToLower(v.ProductCode), strings.ToLower(request.Search)) ||
|
searchResult = append(searchResult, v)
|
||||||
strings.Contains(strings.ToLower(v.Name), strings.ToLower(request.Search)) ||
|
}
|
||||||
strings.Contains(strings.ToLower(v.OtherName), strings.ToLower(request.Search)) {
|
}
|
||||||
searchResult = append(searchResult, v)
|
recipeFilter = searchResult
|
||||||
}
|
}
|
||||||
}
|
|
||||||
recipeFilter = searchResult
|
if len(request.MatIds) > 0 {
|
||||||
}
|
var matIdsFiltered []models.Recipe01
|
||||||
|
for _, v := range recipeFilter {
|
||||||
if len(request.MatIds) > 0 {
|
for _, matID := range request.MatIds {
|
||||||
matIdsFiltered := []models.Recipe01{}
|
for _, recipe := range v.Recipes {
|
||||||
for _, v := range recipeFilter {
|
if recipe.IsUse && recipe.MaterialPathId == matID {
|
||||||
for _, matID := range request.MatIds {
|
matIdsFiltered = append(matIdsFiltered, v)
|
||||||
for _, recipe := range v.Recipes {
|
}
|
||||||
if recipe.IsUse && recipe.MaterialPathId == matID {
|
}
|
||||||
matIdsFiltered = append(matIdsFiltered, v)
|
}
|
||||||
}
|
}
|
||||||
}
|
recipeFilter = matIdsFiltered
|
||||||
}
|
}
|
||||||
}
|
|
||||||
recipeFilter = matIdsFiltered
|
// Map to contracts.RecipeOverview
|
||||||
}
|
for _, v := range recipeFilter {
|
||||||
|
result.Result = append(result.Result, contracts.RecipeOverview{
|
||||||
// Map to contracts.RecipeOverview
|
ID: v.ID,
|
||||||
for _, v := range recipeFilter {
|
ProductCode: v.ProductCode,
|
||||||
result.Result = append(result.Result, contracts.RecipeOverview{
|
Name: v.Name,
|
||||||
ID: v.ID,
|
OtherName: v.OtherName,
|
||||||
ProductCode: v.ProductCode,
|
Description: v.Description,
|
||||||
Name: v.Name,
|
LastUpdated: v.LastChange,
|
||||||
OtherName: v.OtherName,
|
})
|
||||||
Description: v.Description,
|
}
|
||||||
LastUpdated: v.LastChange,
|
|
||||||
})
|
result.TotalCount = len(result.Result)
|
||||||
}
|
|
||||||
|
result.HasMore = result.TotalCount >= request.Take+request.Skip
|
||||||
result.TotalCount = len(result.Result)
|
if result.HasMore {
|
||||||
|
result.Result = result.Result[request.Skip : request.Take+request.Skip]
|
||||||
result.HasMore = result.TotalCount >= request.Take+request.Skip
|
sort.Slice(result.Result, func(i, j int) bool {
|
||||||
if result.HasMore {
|
return result.Result[i].ID < result.Result[j].ID
|
||||||
result.Result = result.Result[request.Skip : request.Take+request.Skip]
|
})
|
||||||
sort.Slice(result.Result, func(i, j int) bool {
|
} else if result.TotalCount > request.Skip {
|
||||||
return result.Result[i].ID < result.Result[j].ID
|
result.Result = result.Result[request.Skip:]
|
||||||
})
|
} else {
|
||||||
} else if result.TotalCount > request.Skip {
|
result.Result = []contracts.RecipeOverview{}
|
||||||
result.Result = result.Result[request.Skip:]
|
}
|
||||||
} else {
|
|
||||||
result.Result = []contracts.RecipeOverview{}
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
func NewRecipeService(db *data.Data, taoLogger *logger.TaoLogger) RecipeService {
|
||||||
}
|
return &recipeService{
|
||||||
|
db: db,
|
||||||
func NewRecipeService(db *data.Data) RecipeService {
|
taoLogger: taoLogger,
|
||||||
return &recipeService{
|
}
|
||||||
db: db,
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue