Compare commits
2 Commits
0853424c4e
...
routing
| Author | SHA1 | Date | |
|---|---|---|---|
| 0f494d3abd | |||
|
|
528f12b065 |
2995
ADD_THIS_TO_SQL.sql
Normal file
2995
ADD_THIS_TO_SQL.sql
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,6 @@
|
||||
package restricted
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/service/menuService"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/i18n"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/nullable"
|
||||
@@ -26,18 +24,33 @@ func MenuHandlerRoutes(r fiber.Router) fiber.Router {
|
||||
handler := NewMenuHandler()
|
||||
|
||||
r.Get("/get-menu", handler.GetMenu)
|
||||
r.Get("/get-routes", handler.GetRouting)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (h *MenuHandler) GetMenu(c fiber.Ctx) error {
|
||||
id_lang, err := strconv.Atoi(c.Cookies("lang_id", "2"))
|
||||
if err != nil {
|
||||
lang_id, ok := c.Locals("langID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
|
||||
menu, err := h.menuService.GetMenu(uint(id_lang))
|
||||
menu, err := h.menuService.GetMenu(lang_id)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||
}
|
||||
|
||||
return c.JSON(response.Make(&menu, 0, i18n.T_(c, response.Message_OK)))
|
||||
}
|
||||
|
||||
func (h *MenuHandler) GetRouting(c fiber.Ctx) error {
|
||||
lang_id, ok := c.Locals("langID").(uint)
|
||||
if !ok {
|
||||
return c.Status(responseErrors.GetErrorStatus(responseErrors.ErrBadAttribute)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, responseErrors.ErrBadAttribute)))
|
||||
}
|
||||
menu, err := h.menuService.GetRoutes(lang_id)
|
||||
if err != nil {
|
||||
return c.Status(responseErrors.GetErrorStatus(err)).
|
||||
JSON(response.Make(nullable.GetNil(""), 0, responseErrors.GetErrorCode(c, err)))
|
||||
|
||||
@@ -110,6 +110,10 @@ func (s *Server) Setup() error {
|
||||
meiliSearch := s.restricted.Group("/meili-search")
|
||||
restricted.MeiliSearchHandlerRoutes(meiliSearch)
|
||||
|
||||
s.api.All("*", func(c fiber.Ctx) error {
|
||||
return c.SendStatus(fiber.StatusNotFound)
|
||||
})
|
||||
|
||||
// // Restricted routes example
|
||||
// restricted := s.api.Group("/restricted")
|
||||
// restricted.Use(middleware.AuthMiddleware())
|
||||
|
||||
@@ -88,12 +88,21 @@ type ScannedCategory struct {
|
||||
Position uint `gorm:"column:position"`
|
||||
ParentID uint `gorm:"column:id_parent"`
|
||||
IsRoot uint `gorm:"column:is_root_category"`
|
||||
LinkRewrite string `gorm:"column:link_rewrite"`
|
||||
IsoCode string `gorm:"column:iso_code"`
|
||||
}
|
||||
type Category struct {
|
||||
CategoryID uint `json:"category_id" form:"category_id"`
|
||||
Name string `json:"name" form:"name"`
|
||||
Active uint `json:"active" form:"active"`
|
||||
Subcategories []Category `json:"subcategories" form:"subcategories"`
|
||||
Label string `json:"label" form:"label"`
|
||||
// Active bool `json:"active" form:"active"`
|
||||
Params CategpryParams `json:"params" form:"params"`
|
||||
Children []Category `json:"children" form:"children"`
|
||||
}
|
||||
|
||||
type CategpryParams struct {
|
||||
CategoryID uint `json:"category_id" form:"category_id"`
|
||||
LinkRewrite string `json:"link_rewrite" form:"link_rewrite"`
|
||||
Locale string `json:"locale" form:"locale"`
|
||||
}
|
||||
|
||||
type FeatVal = map[uint][]uint
|
||||
|
||||
21
app/model/routing.go
Normal file
21
app/model/routing.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package model
|
||||
|
||||
type Route struct {
|
||||
ID uint `gorm:"primaryKey;autoIncrement"`
|
||||
Name string `gorm:"type:varchar(255);not null;unique"`
|
||||
Path *string `gorm:"type:varchar(255);default:null"`
|
||||
Component string `gorm:"type:varchar(255);not null;comment:path to component file"`
|
||||
Layout *string `gorm:"type:varchar(50);default:'default';comment:'default | empty'"`
|
||||
Meta *string `gorm:"type:longtext;default:'{}'"`
|
||||
IsActive *bool `gorm:"type:tinyint;default:1"`
|
||||
SortOrder *int `gorm:"type:int;default:0"`
|
||||
|
||||
ParentID *uint `gorm:"index"`
|
||||
Parent *Route `gorm:"constraint:OnUpdate:RESTRICT,OnDelete:SET NULL;foreignKey:ParentID"`
|
||||
|
||||
Children []Route `gorm:"foreignKey:ParentID"`
|
||||
}
|
||||
|
||||
func (Route) TableName() string {
|
||||
return "b2b_routes"
|
||||
}
|
||||
@@ -26,15 +26,14 @@ func (repo *CategoriesRepo) GetAllCategories(id_lang uint) ([]model.ScannedCateg
|
||||
ps_category.active AS active,
|
||||
ps_category_shop.position AS position,
|
||||
ps_category.id_parent AS id_parent,
|
||||
ps_category.is_root_category AS is_root_category
|
||||
ps_category.is_root_category AS is_root_category,
|
||||
ps_category_lang.link_rewrite AS link_rewrite,
|
||||
ps_lang.iso_code AS iso_code
|
||||
FROM ps_category
|
||||
LEFT JOIN ps_category_lang
|
||||
ON ps_category_lang.id_category = ps_category.id_category
|
||||
AND ps_category_lang.id_shop = ?
|
||||
AND ps_category_lang.id_lang = ?
|
||||
LEFT JOIN ps_category_shop
|
||||
ON ps_category_shop.id_category = ps_category.id_category
|
||||
AND ps_category_shop.id_shop = ?`,
|
||||
LEFT JOIN ps_category_lang ON ps_category_lang.id_category = ps_category.id_category AND ps_category_lang.id_shop = ? AND ps_category_lang.id_lang = ?
|
||||
LEFT JOIN ps_category_shop ON ps_category_shop.id_category = ps_category.id_category AND ps_category_shop.id_shop = ?
|
||||
JOIN ps_lang ON ps_lang.id_lang = ps_category_lang.id_lang
|
||||
`,
|
||||
constdata.SHOP_ID, id_lang, constdata.SHOP_ID).
|
||||
Scan(&allCategories).Error
|
||||
|
||||
|
||||
25
app/repos/routesRepo/routesRepo.go
Normal file
25
app/repos/routesRepo/routesRepo.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package routesrepo
|
||||
|
||||
import (
|
||||
"git.ma-al.com/goc_daniel/b2b/app/db"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
)
|
||||
|
||||
type UIRoutesRepo interface {
|
||||
GetRoutes(langId uint) ([]model.Route, error)
|
||||
}
|
||||
|
||||
type RoutesRepo struct{}
|
||||
|
||||
func New() UIRoutesRepo {
|
||||
return &RoutesRepo{}
|
||||
}
|
||||
|
||||
func (p *RoutesRepo) GetRoutes(langId uint) ([]model.Route, error) {
|
||||
routes := []model.Route{}
|
||||
err := db.DB.Find(&routes).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return routes, nil
|
||||
}
|
||||
@@ -51,15 +51,39 @@ func (s *MeiliService) CreateIndex(id_lang uint) error {
|
||||
|
||||
nextMeiliProduct.ProductID = products[i].ProductID
|
||||
nextMeiliProduct.Name = products[i].Name
|
||||
nextMeiliProduct.Description = cleanHTML(products[i].Description)
|
||||
nextMeiliProduct.DescriptionShort = cleanHTML(products[i].DescriptionShort)
|
||||
nextMeiliProduct.Usage = cleanHTML(products[i].Usage)
|
||||
|
||||
nextMeiliProduct.Description, err = cleanHTML(products[i].Description)
|
||||
if err != nil {
|
||||
fmt.Printf("nextMeiliProduct.Description: %v\n", nextMeiliProduct.Description)
|
||||
fmt.Printf("products[i].ProductID: %v\n", products[i].ProductID)
|
||||
fmt.Println("failed at description")
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
nextMeiliProduct.DescriptionShort, err = cleanHTML(products[i].DescriptionShort)
|
||||
if err != nil {
|
||||
fmt.Printf("nextMeiliProduct.DescriptionShort: %v\n", nextMeiliProduct.DescriptionShort)
|
||||
fmt.Printf("products[i].ProductID: %v\n", products[i].ProductID)
|
||||
fmt.Println("failed at description short")
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
nextMeiliProduct.Usage, err = cleanHTML(products[i].Usage)
|
||||
if err != nil {
|
||||
fmt.Printf("nextMeiliProduct.Usage: %v\n", nextMeiliProduct.Usage)
|
||||
fmt.Printf("products[i].ProductID: %v\n", products[i].ProductID)
|
||||
fmt.Println("failed at usage")
|
||||
fmt.Printf("err: %v\n", err)
|
||||
return err
|
||||
}
|
||||
|
||||
meiliProducts = append(meiliProducts, nextMeiliProduct)
|
||||
}
|
||||
|
||||
indexName := "meili_products_shop" + strconv.FormatInt(constdata.SHOP_ID, 10) + "_lang" + strconv.FormatInt(int64(id_lang), 10)
|
||||
primaryKey := "product_id"
|
||||
primaryKey := "ProductID"
|
||||
docOptions := &meilisearch.DocumentOptions{
|
||||
PrimaryKey: &primaryKey,
|
||||
SkipCreation: false,
|
||||
@@ -123,7 +147,7 @@ func (s *MeiliService) HealthCheck() (*meilisearch.Health, error) {
|
||||
}
|
||||
|
||||
// remove all tags from HTML text
|
||||
func cleanHTML(s string) string {
|
||||
func cleanHTML(s string) (string, error) {
|
||||
r := strings.NewReader(s)
|
||||
d := xml.NewDecoder(r)
|
||||
|
||||
@@ -137,19 +161,25 @@ func cleanHTML(s string) string {
|
||||
token, err := d.Token()
|
||||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
return text, err
|
||||
}
|
||||
|
||||
switch v := token.(type) {
|
||||
case xml.StartElement:
|
||||
if len(text) > 0 && text[len(text)-1] != '\n' {
|
||||
text += " \n "
|
||||
}
|
||||
case xml.EndElement:
|
||||
case xml.CharData:
|
||||
if strings.TrimSpace(string(v)) != "" {
|
||||
text += string(v)
|
||||
}
|
||||
case xml.Comment:
|
||||
case xml.ProcInst:
|
||||
case xml.Directive:
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
return text, nil
|
||||
}
|
||||
|
||||
@@ -5,23 +5,26 @@ import (
|
||||
|
||||
"git.ma-al.com/goc_daniel/b2b/app/model"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/repos/categoriesRepo"
|
||||
routesRepo "git.ma-al.com/goc_daniel/b2b/app/repos/routesRepo"
|
||||
"git.ma-al.com/goc_daniel/b2b/app/utils/responseErrors"
|
||||
)
|
||||
|
||||
type MenuService struct {
|
||||
categoriesRepo categoriesRepo.UICategoriesRepo
|
||||
routesRepo routesRepo.UIRoutesRepo
|
||||
}
|
||||
|
||||
func New() *MenuService {
|
||||
return &MenuService{
|
||||
categoriesRepo: categoriesRepo.New(),
|
||||
routesRepo: routesRepo.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *MenuService) GetMenu(id_lang uint) (model.Category, error) {
|
||||
func (s *MenuService) GetMenu(id_lang uint) (*model.Category, error) {
|
||||
all_categories, err := s.categoriesRepo.GetAllCategories(id_lang)
|
||||
if err != nil {
|
||||
return model.Category{}, err
|
||||
return &model.Category{}, err
|
||||
}
|
||||
|
||||
// find the root
|
||||
@@ -35,7 +38,7 @@ func (s *MenuService) GetMenu(id_lang uint) (model.Category, error) {
|
||||
}
|
||||
}
|
||||
if !root_found {
|
||||
return model.Category{}, responseErrors.ErrNoRootFound
|
||||
return &model.Category{}, responseErrors.ErrNoRootFound
|
||||
}
|
||||
|
||||
// now create the children and reorder them according to position
|
||||
@@ -57,25 +60,31 @@ func (s *MenuService) GetMenu(id_lang uint) (model.Category, error) {
|
||||
// finally, create the tree
|
||||
tree := s.createTree(root_index, &all_categories, &children_indices)
|
||||
|
||||
return tree, nil
|
||||
return &tree, nil
|
||||
}
|
||||
|
||||
func (s *MenuService) createTree(index int, all_categories *([]model.ScannedCategory), children_indices *(map[int][]ChildWithPosition)) model.Category {
|
||||
node := s.scannedToNormalCategory((*all_categories)[index])
|
||||
|
||||
for i := 0; i < len((*children_indices)[index]); i++ {
|
||||
node.Subcategories = append(node.Subcategories, s.createTree((*children_indices)[index][i].Index, all_categories, children_indices))
|
||||
node.Children = append(node.Children, s.createTree((*children_indices)[index][i].Index, all_categories, children_indices))
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (s *MenuService) GetRoutes(id_lang uint) ([]model.Route, error) {
|
||||
return s.routesRepo.GetRoutes(id_lang)
|
||||
}
|
||||
|
||||
func (s *MenuService) scannedToNormalCategory(scanned model.ScannedCategory) model.Category {
|
||||
var normal model.Category
|
||||
normal.Active = scanned.Active
|
||||
// normal.Active = scanned.Active
|
||||
normal.CategoryID = scanned.CategoryID
|
||||
normal.Name = scanned.Name
|
||||
normal.Subcategories = []model.Category{}
|
||||
normal.Label = scanned.Name
|
||||
// normal.Active = scanned.Active == 1
|
||||
normal.Params = model.CategpryParams{CategoryID: normal.CategoryID, LinkRewrite: scanned.LinkRewrite, Locale: scanned.IsoCode}
|
||||
normal.Children = []model.Category{}
|
||||
return normal
|
||||
}
|
||||
|
||||
|
||||
1
bo/components.d.ts
vendored
1
bo/components.d.ts
vendored
@@ -41,6 +41,7 @@ declare module 'vue' {
|
||||
UInput: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Input.vue')['default']
|
||||
UInputNumber: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/InputNumber.vue')['default']
|
||||
UModal: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Modal.vue')['default']
|
||||
UNavigationMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/NavigationMenu.vue')['default']
|
||||
UPagination: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Pagination.vue')['default']
|
||||
USelect: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/Select.vue')['default']
|
||||
USelectMenu: typeof import('./node_modules/@nuxt/ui/dist/runtime/components/SelectMenu.vue')['default']
|
||||
|
||||
@@ -1,14 +1,51 @@
|
||||
<script setup lang="ts">
|
||||
import TopBar from '@/components/TopBar.vue';
|
||||
import { getMenu } from '@/router/menu'
|
||||
import type { NavigationMenuItem } from '@nuxt/ui';
|
||||
import { ref } from 'vue';
|
||||
let menu = await getMenu() as NavigationMenuItem[]
|
||||
|
||||
const openAll = ref(false)
|
||||
|
||||
function adaptMenu(menu: NavigationMenuItem[]) {
|
||||
for (const item of menu) {
|
||||
if(item.children && item.children.length > 0){
|
||||
console.log(item);
|
||||
adaptMenu(item.children);
|
||||
item.open = openAll.value
|
||||
item.children.unshift({ label: item.label, icon: 'i-lucide-book-open', popover: item.label ,to: { name: 'category', params: item.params }})
|
||||
} else {
|
||||
item.to = { name: 'category', params: item.params };
|
||||
item.icon = 'i-lucide-file-text'
|
||||
}
|
||||
}
|
||||
return menu;
|
||||
}
|
||||
|
||||
menu = adaptMenu(menu)
|
||||
|
||||
const items = ref<NavigationMenuItem[][]>([
|
||||
[
|
||||
|
||||
...menu as NavigationMenuItem[]
|
||||
],
|
||||
|
||||
])
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-screen grid grid-rows-[auto_1fr_auto]">
|
||||
<!-- <header class="w-full bg-gray-100 text-primary shadow border-b-gray-300 p-4 mb-8">Header</header> -->
|
||||
<main class="p-10">
|
||||
<TopBar/>
|
||||
<router-view />
|
||||
<div class="mt-24 w-1/4 bg-accented rounded-2xl">
|
||||
<button @click="openAll = !openAll">open all</button>
|
||||
<UNavigationMenu arrow orientation="vertical" :items="items" class="p-4" :key="openAll">
|
||||
</UNavigationMenu>
|
||||
|
||||
</div>
|
||||
<slot></slot>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<main :key="i18n.locale.value">
|
||||
<TopBarLogin />
|
||||
<router-view />
|
||||
<slot></slot>
|
||||
</main>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import Empty from '@/layouts/empty.vue'
|
||||
import { currentLang, langs } from './langs'
|
||||
import { getSettings } from './settings'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import Default from '@/layouts/default.vue'
|
||||
import { getMenu } from './menu'
|
||||
|
||||
function isAuthenticated(): boolean {
|
||||
if (typeof document === 'undefined') return false
|
||||
@@ -12,6 +12,7 @@ function isAuthenticated(): boolean {
|
||||
|
||||
|
||||
await getSettings()
|
||||
const routes = await getMenu()
|
||||
|
||||
|
||||
const router = createRouter({
|
||||
@@ -24,6 +25,7 @@ const router = createRouter({
|
||||
{
|
||||
path: '/:locale',
|
||||
children: [
|
||||
{ path: 'category/:category_id-:link_rewrite', component: () => import('../views/CategoryView.vue'), name: 'category' },
|
||||
{
|
||||
path: '',
|
||||
component: Default,
|
||||
@@ -36,49 +38,88 @@ const router = createRouter({
|
||||
{ path: 'cart', component: () => import('../components/customer/PageCart.vue'), name: 'cart' },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: Empty,
|
||||
children: [
|
||||
{ path: 'login', component: () => import('@/views/LoginView.vue'), name: 'login', meta: { guest: true } },
|
||||
{ path: 'login', component: () => import('@/views/LoginView.vue'), name: 'login', meta: { guest: true, } },
|
||||
{ path: 'register', component: () => import('@/views/RegisterView.vue'), name: 'register', meta: { guest: true } },
|
||||
{ path: 'password-recovery', component: () => import('@/views/PasswordRecoveryView.vue'), name: 'password-recovery', meta: { guest: true } },
|
||||
{ path: 'reset-password', component: () => import('@/views/ResetPasswordForm.vue'), name: 'reset-password', meta: { guest: true } },
|
||||
{ path: 'verify-email', component: () => import('@/views/VerifyEmailView.vue'), name: 'verify-email', meta: { guest: true } },
|
||||
],
|
||||
// {
|
||||
// path: '',
|
||||
// component: Empty,
|
||||
// children: [
|
||||
// ],
|
||||
// },
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: () => import('@/views/NotFoundView.vue'),
|
||||
name: 'not-found',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '/:pathMatch(.*)*',
|
||||
component: () => import('@/views/NotFoundView.vue'),
|
||||
name: 'not-found',
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
router.beforeEach((to, from, next) => {
|
||||
// router.beforeEach((to, from, next) => {
|
||||
// const locale = to.params.locale as string
|
||||
// const localeLang = langs.find((x) => x.iso_code == locale)
|
||||
|
||||
// if (locale && langs.length > 0) {
|
||||
// const authStore = useAuthStore()
|
||||
// // console.log(authStore.isAuthenticated, to, from)
|
||||
// // if()
|
||||
// const validLocale = langs.find((l) => l.lang_code === locale)
|
||||
|
||||
// if (validLocale) {
|
||||
// currentLang.value = localeLang
|
||||
// if (!to.meta?.guest && !isAuthenticated()) {
|
||||
// return next({ name: 'login', params: { locale } })
|
||||
// }
|
||||
|
||||
// // return next()
|
||||
// return next()
|
||||
// } else if (locale) {
|
||||
// return next(`/${currentLang.value?.iso_code}${to.path.replace(`/${locale}`, '') || '/'}`)
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!locale && to.path !== '/') {
|
||||
// return next(`/${currentLang.value?.iso_code}${to.path}`)
|
||||
// }
|
||||
|
||||
// next()
|
||||
// })
|
||||
|
||||
router.beforeEach((to, from) => {
|
||||
const locale = to.params.locale as string
|
||||
const localeLang = langs.find((x) => x.iso_code == locale)
|
||||
const localeLang = langs.find((x) => x.iso_code === locale)
|
||||
|
||||
if (locale && langs.length > 0) {
|
||||
const authStore = useAuthStore()
|
||||
console.log(authStore.isAuthenticated, to, from)
|
||||
// if()
|
||||
const validLocale = langs.find((l) => l.lang_code === locale)
|
||||
|
||||
if (validLocale) {
|
||||
currentLang.value = localeLang
|
||||
if (!to.meta?.guest && !isAuthenticated()) {
|
||||
return next({ name: 'login', params: { locale } })
|
||||
|
||||
if (!to.meta?.guest && !authStore.isAuthenticated) {
|
||||
return { name: 'login', params: { locale } }
|
||||
}
|
||||
|
||||
return next()
|
||||
return true
|
||||
} else if (locale) {
|
||||
return next(`/${currentLang.value?.iso_code}${to.path.replace(`/${locale}`, '') || '/'}`)
|
||||
return `/${currentLang.value?.iso_code}${to.path.replace(`/${locale}`, '') || '/'}`
|
||||
}
|
||||
}
|
||||
|
||||
if (!locale && to.path !== '/') {
|
||||
return next(`/${currentLang.value?.iso_code}${to.path}`)
|
||||
return `/${currentLang.value?.iso_code}${to.path}`
|
||||
}
|
||||
|
||||
next()
|
||||
return true
|
||||
})
|
||||
|
||||
export default router
|
||||
|
||||
17
bo/src/router/menu.ts
Normal file
17
bo/src/router/menu.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useFetchJson } from "@/composable/useFetchJson";
|
||||
import type { MenuItem, Route } from "@/types/menu";
|
||||
|
||||
export const getMenu = async () => {
|
||||
const resp = await useFetchJson<MenuItem>('/api/v1/restricted/menu/get-menu');
|
||||
|
||||
return resp.items.children
|
||||
|
||||
}
|
||||
|
||||
|
||||
export const getRoutes = async () => {
|
||||
const resp = await useFetchJson<Route[]>('/api/v1/restricted/menu/get-routes');
|
||||
|
||||
return resp.items
|
||||
|
||||
}
|
||||
26
bo/src/types/menu.d.ts
vendored
Normal file
26
bo/src/types/menu.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
export interface MenuItem {
|
||||
category_id: number
|
||||
label: string
|
||||
params: Params
|
||||
children: MenuItem[]
|
||||
}
|
||||
|
||||
export interface Params {
|
||||
category_id?: number
|
||||
link_rewrite?: string
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export interface Route {
|
||||
ID: number
|
||||
Name: string
|
||||
Path: string
|
||||
Component: string
|
||||
Layout: string
|
||||
Meta: string
|
||||
IsActive: boolean
|
||||
SortOrder: number
|
||||
ParentID: any
|
||||
Parent: any
|
||||
Children: any
|
||||
}
|
||||
21
bo/src/views/CategoryView.vue
Normal file
21
bo/src/views/CategoryView.vue
Normal file
@@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<component :is="Default || 'div'">
|
||||
<div class="container mt-24">
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<h1>Category</h1>
|
||||
{{ $route.params }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { useRoute } from 'vue-router';
|
||||
import Default from '@/layouts/default.vue';
|
||||
// const route = useRoute()
|
||||
// console.log(route);
|
||||
|
||||
</script>
|
||||
@@ -4,8 +4,8 @@ import { useRouter, useRoute } from 'vue-router'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
import { useValidation } from '@/composable/useValidation'
|
||||
import type { FormError } from '@nuxt/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { i18n } from '@/plugins/02_i18n'
|
||||
import Empty from '@/layouts/empty.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -52,9 +52,11 @@ const PrivacyComponent = computed(() =>
|
||||
import(`@/components/terms/${i18n.locale.value}_PrivacyPolicyView.vue`).catch(() => import('@/components/terms/en_PrivacyPolicyView.vue')),
|
||||
),
|
||||
)
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="Empty || 'div'">
|
||||
<UDrawer v-model:open="showTherms" :overlay="false">
|
||||
<template #body>
|
||||
<component :is="TermsComponent" />
|
||||
@@ -163,4 +165,5 @@ const PrivacyComponent = computed(() =>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
7
bo/src/views/NotFoundView.vue
Normal file
7
bo/src/views/NotFoundView.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<div class="flex flex-col items-center justify-center h-screen">
|
||||
<h1 class="text-4xl font-bold text-gray-800">404</h1>
|
||||
<p class="mt-4 text-lg text-gray-600">Page not found</p>
|
||||
<router-link to="/" class="mt-6 text-blue-500 hover:underline">Go back home</router-link>
|
||||
</div>
|
||||
</template>
|
||||
@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import { useValidation } from '@/composable/useValidation'
|
||||
import type { FormError } from '@nuxt/ui'
|
||||
import { i18n } from '@/plugins/02_i18n'
|
||||
import Empty from '@/layouts/empty.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const authStore = useAuthStore()
|
||||
@@ -36,6 +37,7 @@ function validate(): FormError[] {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="Empty || 'div'">
|
||||
<div class="h-[100vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-15">
|
||||
<div
|
||||
@@ -84,11 +86,13 @@ function validate(): FormError[] {
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<div class="text-center flex flex-col gap-3 border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
||||
<div
|
||||
class="text-center flex flex-col gap-3 border-t dark:border-(--border-dark) border-(--border-light) pt-4">
|
||||
<button color="neutral" variant="outline" :loading="authStore.loading"
|
||||
class="w-full flex items-center gap-2 justify-center text-[15px] dark:text-white text-black cursor-pointer"
|
||||
@click="goToLogin">
|
||||
<UIcon name="mingcute:arrow-left-line" class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[16px]" />
|
||||
<UIcon name="mingcute:arrow-left-line"
|
||||
class="text-(--accent-blue-light) dark:text-(--accent-blue-dark) text-[16px]" />
|
||||
{{ $t('general.back_to_sign_in') }}
|
||||
</button>
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
@@ -102,4 +106,5 @@ function validate(): FormError[] {
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<template>
|
||||
<component :is="Empty || 'div'">
|
||||
<UDrawer v-model:open="showTherms" :overlay="false">
|
||||
<template #body>
|
||||
<component :is="TermsComponent" />
|
||||
@@ -110,6 +111,7 @@
|
||||
</UForm>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -120,6 +122,8 @@ import { useValidation } from '@/composable/useValidation'
|
||||
import type { FormError } from '@nuxt/ui'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { i18n } from '@/plugins/02_i18n'
|
||||
import Empty from '@/layouts/empty.vue'
|
||||
|
||||
|
||||
const { locale } = useI18n()
|
||||
const router = useRouter()
|
||||
|
||||
@@ -5,6 +5,7 @@ import { useAuthStore } from '@/stores/auth'
|
||||
import { useValidation } from '@/composable/useValidation'
|
||||
import type { FormError } from '@nuxt/ui'
|
||||
import { i18n } from '@/plugins/02_i18n'
|
||||
import Empty from '@/layouts/empty.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
@@ -49,6 +50,7 @@ function validate(): FormError[] {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="Empty || 'div'">
|
||||
<div class="h-[100vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-15">
|
||||
<div
|
||||
@@ -85,7 +87,8 @@ function validate(): FormError[] {
|
||||
class="w-full dark:text-white text-black">
|
||||
<UInput v-model="new_password" :type="showNewPassword ? 'text' : 'password'"
|
||||
:placeholder="$t('general.enter_your_new_password')" :disabled="authStore.loading"
|
||||
class="w-full dark:text-white text-black placeholder:text-(--placeholder)" :ui="{ trailing: 'pe-1' }">
|
||||
class="w-full dark:text-white text-black placeholder:text-(--placeholder)"
|
||||
:ui="{ trailing: 'pe-1' }">
|
||||
<template #trailing>
|
||||
<UIcon color="neutral" variant="link" size="sm"
|
||||
:name="showNewPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||
@@ -100,7 +103,8 @@ function validate(): FormError[] {
|
||||
class="w-full dark:text-white text-black">
|
||||
<UInput v-model="confirm_new_password" :type="showConfirmPassword ? 'text' : 'password'"
|
||||
:placeholder="$t('general.confirm_your_new_password')" :disabled="authStore.loading"
|
||||
class="w-full dark:text-white text-black placeholder:text-(--placeholder)" :ui="{ trailing: 'pe-1' }">
|
||||
class="w-full dark:text-white text-black placeholder:text-(--placeholder)"
|
||||
:ui="{ trailing: 'pe-1' }">
|
||||
<template #trailing>
|
||||
<UIcon color="neutral" variant="ghost" size="sm"
|
||||
:name="showConfirmPassword ? 'i-lucide-eye-off' : 'i-lucide-eye'"
|
||||
@@ -125,4 +129,5 @@ function validate(): FormError[] {
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useRouter, useRoute } from 'vue-router'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { useFetchJson } from '@/composable/useFetchJson'
|
||||
import { i18n } from '@/plugins/02_i18n'
|
||||
import Empty from '@/layouts/empty.vue'
|
||||
|
||||
const { t, te } = useI18n()
|
||||
const router = useRouter()
|
||||
@@ -66,6 +67,7 @@ function goToLogin() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component :is="Empty || 'div'">
|
||||
<div
|
||||
class="min-h-screen bg-gradient-to-br from-primary-50 via-white to-primary-100 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
|
||||
<div class="pt-20 pb-8 flex items-center justify-center px-4 sm:px-6 lg:px-8">
|
||||
@@ -133,7 +135,8 @@ function goToLogin() {
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
{{ $t('verify_email.already_registered') }}
|
||||
<button variant="link" size="sm" @click="goToLogin"
|
||||
class="cursor-pointer text-(--accent-blue-light) dark:text-(--accent-blue-dark)"> {{ $t('general.sign_in')
|
||||
class="cursor-pointer text-(--accent-blue-light) dark:text-(--accent-blue-dark)"> {{
|
||||
$t('general.sign_in')
|
||||
}}
|
||||
</button>
|
||||
</p>
|
||||
@@ -143,4 +146,5 @@ function goToLogin() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
2
go.mod
2
go.mod
@@ -11,6 +11,7 @@ require (
|
||||
github.com/gofiber/fiber/v3 v3.1.0
|
||||
github.com/golang-jwt/jwt/v5 v5.3.1
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/meilisearch/meilisearch-go v0.36.1
|
||||
github.com/openai/openai-go/v3 v3.28.0
|
||||
github.com/samber/lo v1.53.0
|
||||
golang.org/x/crypto v0.48.0
|
||||
@@ -29,7 +30,6 @@ require (
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/meilisearch/meilisearch-go v0.36.1 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
github.com/tidwall/match v1.1.1 // indirect
|
||||
github.com/tidwall/pretty v1.2.1 // indirect
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- +goose Up
|
||||
-- create routes table
|
||||
CREATE TABLE IF NOT EXISTS b2b_tracker_routes (
|
||||
CREATE TABLE IF NOT EXISTS b2b_routes (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL UNIQUE,
|
||||
path VARCHAR(255) NULL,
|
||||
@@ -13,11 +13,11 @@ CREATE TABLE IF NOT EXISTS b2b_tracker_routes (
|
||||
|
||||
CONSTRAINT fk_parent
|
||||
FOREIGN KEY (parent_id)
|
||||
REFERENCES b2b_tracker_routes(id)
|
||||
REFERENCES b2b_routes(id)
|
||||
ON DELETE SET NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
INSERT IGNORE INTO b2b_tracker_routes
|
||||
INSERT IGNORE INTO b2b_routes
|
||||
(name, path, component, layout, meta, is_active, sort_order, parent_id)
|
||||
VALUES
|
||||
('root', '', '', 'default', '{"trans": "route.root"}', 0, 0, 0),
|
||||
@@ -30,5 +30,5 @@ VALUES
|
||||
|
||||
-- +goose Down
|
||||
|
||||
DROP TABLE IF EXISTS b2b_tracker_routes;
|
||||
DROP TABLE IF EXISTS b2b_routes;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user