Compare commits
59 Commits
38bb84b692
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 49e02d3d79 | |||
| 1927dd6a8c | |||
|
d90e22b641
|
|||
| ad521e04ae | |||
|
4f7632af53
|
|||
|
ca080f4e2a
|
|||
|
5a5239e335
|
|||
|
314995e5f9
|
|||
|
8e11ba4631
|
|||
|
dfd5532b20
|
|||
|
986f63c0af
|
|||
|
154c929859
|
|||
|
f779435cf0
|
|||
|
5f6eb9f2a2
|
|||
|
3f44d2d9c2
|
|||
|
b8f89ab655
|
|||
|
83df018d34
|
|||
|
7b3fe24b7c
|
|||
|
75c4edfa3d
|
|||
|
a060901cc3
|
|||
|
8e41514d05
|
|||
|
9aff7d8f26
|
|||
|
2f26b2ddb5
|
|||
|
96d76b3657
|
|||
|
4e45a9b6d0
|
|||
|
27ac4d9b4a
|
|||
|
a60a796345
|
|||
|
14f50ecdb2
|
|||
|
b1c78dce28
|
|||
|
585ec46282
|
|||
|
8f69b61799
|
|||
|
64bab332c9
|
|||
|
38401a5f69
|
|||
|
f03d472c30
|
|||
|
2d6f6700f0
|
|||
|
2e11fc5d9c
|
|||
|
ac428946e7
|
|||
|
e4329dfc2b
|
|||
|
5dbbdc62e6
|
|||
|
200614a5c9
|
|||
|
4ac5b1c101
|
|||
|
b7e6009706
|
|||
|
fd262239e4
|
|||
|
cf761d218d
|
|||
|
110627f27e
|
|||
|
64392c32c6
|
|||
|
3f8f2547be
|
|||
|
632fa6cf8e
|
|||
|
d04f8cdc44
|
|||
|
97f5677a97
|
|||
|
2ed4a4da02
|
|||
|
100fe32f8e
|
|||
|
231f591767
|
|||
|
0e7aaed154
|
|||
|
89c2d11f19
|
|||
|
cd93491d98
|
|||
|
9b83ab565a
|
|||
|
5e17bbd965
|
|||
|
de0d05df0a
|
@@ -1 +1,2 @@
|
|||||||
TZ=Asia/Shanghai
|
TZ=Asia/Shanghai
|
||||||
|
LOG_LEVEL=debug
|
||||||
|
|||||||
53
.gitea/workflows/check.yaml
Normal file
53
.gitea/workflows/check.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
name: Check build frontend and backend
|
||||||
|
run-name: ${{ gitea.actor }} is building nixcn-cms check
|
||||||
|
on: [push]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-frontend:
|
||||||
|
name: Build PNPM Frontend
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
|
||||||
|
- name: Install Corepack
|
||||||
|
run: npm install corepack
|
||||||
|
|
||||||
|
- name: Enable Corepack
|
||||||
|
run: corepack enable
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Build frontend
|
||||||
|
run: pnpm build
|
||||||
|
|
||||||
|
build-backend:
|
||||||
|
name: Build Go Backend
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Check out repository code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.25.5"
|
||||||
|
cache: false
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: go mod tidy
|
||||||
|
|
||||||
|
- name: Generate go dependencies
|
||||||
|
run: go generate .
|
||||||
|
|
||||||
|
- name: Build backend
|
||||||
|
run: go build -v -o server main.go
|
||||||
|
|
||||||
|
- name: Run Tests
|
||||||
|
run: go test ./...
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -46,3 +46,6 @@ go.work.sum
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
__MACOSX
|
__MACOSX
|
||||||
._*
|
._*
|
||||||
|
|
||||||
|
# go gen
|
||||||
|
*_gen.go
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
FROM docker.io/node:22-alpine AS client-build
|
FROM docker.io/node:22-alpine AS client-cms-build
|
||||||
|
RUN apk add just -y
|
||||||
RUN npm install -g corepack && \
|
RUN npm install -g corepack && \
|
||||||
corepack enable
|
corepack enable
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ENV VITE_APP_BASE_URL=$CLIENT_BASE_URL
|
ENV VITE_APP_BASE_URL=$CLIENT_BASE_URL
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN cd /app/client && \
|
RUN just build-client-cms
|
||||||
pnpm install -r --frozen-lockfile && \
|
|
||||||
pnpm run build
|
|
||||||
|
|
||||||
FROM docker.io/busybox:1.37 AS client
|
FROM docker.io/busybox:1.37 AS client-cms
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
COPY --from=client-build /app/client/dist .
|
COPY --from=client-build /app/.outputs/client/cms/dist .
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
ENTRYPOINT ["httpd", "-f", "-p", "3000", "-h", "/app", "-v"]
|
ENTRYPOINT ["httpd", "-f", "-p", "3000", "-h", "/app", "-v"]
|
||||||
|
|
||||||
|
|||||||
8
api/auth/handler.go
Normal file
8
api/auth/handler.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApiHandler(r *gin.RouterGroup) {
|
||||||
|
}
|
||||||
11
api/event/handler.go
Normal file
11
api/event/handler.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package event
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/middleware"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApiHandler(r *gin.RouterGroup) {
|
||||||
|
r.Use(middleware.ApiVersionCheck(), middleware.JWTAuth(), middleware.Permission(10))
|
||||||
|
}
|
||||||
17
api/handler.go
Normal file
17
api/handler.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/api/auth"
|
||||||
|
"nixcn-cms/api/event"
|
||||||
|
"nixcn-cms/api/kyc"
|
||||||
|
"nixcn-cms/api/user"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Handler(r *gin.RouterGroup) {
|
||||||
|
auth.ApiHandler(r.Group("/auth"))
|
||||||
|
user.ApiHandler(r.Group("/user"))
|
||||||
|
event.ApiHandler(r.Group("/event"))
|
||||||
|
kyc.ApiHandler(r.Group("/kyc"))
|
||||||
|
}
|
||||||
11
api/kyc/handler.go
Normal file
11
api/kyc/handler.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package kyc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/middleware"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ApiHandler(r *gin.RouterGroup) {
|
||||||
|
r.Use(middleware.ApiVersionCheck(), middleware.JWTAuth(), middleware.Permission(10))
|
||||||
|
}
|
||||||
@@ -2,6 +2,5 @@ package user
|
|||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import "github.com/gin-gonic/gin"
|
||||||
|
|
||||||
func Create(c *gin.Context) {
|
func (self *UserHandler) Create(c *gin.Context) {
|
||||||
|
|
||||||
}
|
}
|
||||||
24
api/user/full.go
Normal file
24
api/user/full.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
|
"nixcn-cms/service"
|
||||||
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self *UserHandler) Full(c *gin.Context) {
|
||||||
|
userTablePayload := &service.UserTablePayload{
|
||||||
|
Context: c,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := self.svc.GetUserFullTable(userTablePayload)
|
||||||
|
|
||||||
|
if result.Common.Exception.Original != exception.CommonSuccess {
|
||||||
|
utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String(), result.Data)
|
||||||
|
}
|
||||||
24
api/user/handler.go
Normal file
24
api/user/handler.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/middleware"
|
||||||
|
"nixcn-cms/service"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserHandler struct {
|
||||||
|
svc service.UserService
|
||||||
|
}
|
||||||
|
|
||||||
|
func ApiHandler(r *gin.RouterGroup) {
|
||||||
|
userSvc := service.NewUserService()
|
||||||
|
userHandler := &UserHandler{userSvc}
|
||||||
|
|
||||||
|
r.Use(middleware.ApiVersionCheck(), middleware.JWTAuth(), middleware.Permission(5))
|
||||||
|
r.GET("/info", userHandler.Info)
|
||||||
|
r.PATCH("/update", userHandler.Update)
|
||||||
|
r.GET("/list", middleware.Permission(20), userHandler.List)
|
||||||
|
r.POST("/full", middleware.Permission(40), userHandler.Full)
|
||||||
|
r.POST("/create", middleware.Permission(50), userHandler.Create)
|
||||||
|
}
|
||||||
56
api/user/info.go
Normal file
56
api/user/info.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
|
"nixcn-cms/service"
|
||||||
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self *UserHandler) Info(c *gin.Context) {
|
||||||
|
userIdOrig, ok := c.Get("user_id")
|
||||||
|
if !ok {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceInfo).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorMissingUserId).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
utils.HttpResponse(c, 403, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userId, err := uuid.Parse(userIdOrig.(string))
|
||||||
|
if err != nil {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceInfo).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUuidParseFailed).
|
||||||
|
SetError(err).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
UserInfoPayload := &service.UserInfoPayload{
|
||||||
|
Context: c,
|
||||||
|
UserId: userId,
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := self.svc.GetUserInfo(UserInfoPayload)
|
||||||
|
|
||||||
|
if result.Common.Exception.Original != exception.CommonSuccess {
|
||||||
|
utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String(), result.Data)
|
||||||
|
}
|
||||||
46
api/user/list.go
Normal file
46
api/user/list.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
|
"nixcn-cms/service"
|
||||||
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self *UserHandler) List(c *gin.Context) {
|
||||||
|
type ListQuery struct {
|
||||||
|
Limit *string `form:"limit"`
|
||||||
|
Offset *string `form:"offset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var query ListQuery
|
||||||
|
if err := c.ShouldBindQuery(&query); err != nil {
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusClient).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceList).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
|
||||||
|
utils.HttpResponse(c, 400, exception)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userListPayload := &service.UserListPayload{
|
||||||
|
Context: c,
|
||||||
|
Limit: query.Limit,
|
||||||
|
Offset: query.Offset,
|
||||||
|
}
|
||||||
|
|
||||||
|
result := self.svc.ListUsers(userListPayload)
|
||||||
|
|
||||||
|
if result.Common.Exception.Original != exception.CommonSuccess {
|
||||||
|
utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String(), result.Data)
|
||||||
|
}
|
||||||
69
api/user/update.go
Normal file
69
api/user/update.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
|
"nixcn-cms/service"
|
||||||
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (self *UserHandler) Update(c *gin.Context) {
|
||||||
|
userIdOrig, ok := c.Get("user_id")
|
||||||
|
if !ok {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceUpdate).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorMissingUserId).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
utils.HttpResponse(c, 403, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userId, err := uuid.Parse(userIdOrig.(string))
|
||||||
|
if err != nil {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceUpdate).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUuidParseFailed).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userInfoPayload := &service.UserInfoPayload{
|
||||||
|
Context: c,
|
||||||
|
UserId: userId,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = c.ShouldBindJSON(&userInfoPayload.Data)
|
||||||
|
if err != nil {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceUpdate).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result := self.svc.UpdateUserInfo(userInfoPayload)
|
||||||
|
|
||||||
|
if result.Common.Exception.Original != exception.CommonSuccess {
|
||||||
|
utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.HttpResponse(c, result.Common.HttpCode, result.Common.Exception.String(), result.Data)
|
||||||
|
}
|
||||||
0
charts/.gitkeep
Normal file
0
charts/.gitkeep
Normal file
@@ -3,7 +3,7 @@ import pluginQuery from '@tanstack/eslint-plugin-query';
|
|||||||
|
|
||||||
export default antfu({
|
export default antfu({
|
||||||
gitignore: true,
|
gitignore: true,
|
||||||
ignores: ['**/node_modules/**', '**/dist/**', 'bun.lock', '**/routeTree.gen.ts', '**/ui/**'],
|
ignores: ['**/node_modules/**', '**/dist/**', 'bun.lock', '**/routeTree.gen.ts', '**/ui/**', 'src/components/editor/**/*'],
|
||||||
react: true,
|
react: true,
|
||||||
stylistic: {
|
stylistic: {
|
||||||
semi: true,
|
semi: true,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
"@tanstack/react-table": "^8.21.3",
|
"@tanstack/react-table": "^8.21.3",
|
||||||
"@tanstack/zod-adapter": "^1.143.4",
|
"@tanstack/zod-adapter": "^1.143.4",
|
||||||
"@tanstack/zod-form-adapter": "^0.42.1",
|
"@tanstack/zod-form-adapter": "^0.42.1",
|
||||||
|
"@uiw/react-md-editor": "^4.0.11",
|
||||||
"axios": "^1.13.2",
|
"axios": "^1.13.2",
|
||||||
"base-64": "^1.0.0",
|
"base-64": "^1.0.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
@@ -44,6 +45,7 @@
|
|||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"culori": "^4.0.2",
|
"culori": "^4.0.2",
|
||||||
"immer": "^11.1.0",
|
"immer": "^11.1.0",
|
||||||
|
"lodash-es": "^4.17.22",
|
||||||
"lucide-react": "^0.562.0",
|
"lucide-react": "^0.562.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"qrcode": "^1.5.4",
|
"qrcode": "^1.5.4",
|
||||||
@@ -69,6 +71,7 @@
|
|||||||
"@tanstack/router-plugin": "^1.141.7",
|
"@tanstack/router-plugin": "^1.141.7",
|
||||||
"@types/base-64": "^1.0.2",
|
"@types/base-64": "^1.0.2",
|
||||||
"@types/culori": "^4.0.1",
|
"@types/culori": "^4.0.1",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
"@types/node": "^25.0.3",
|
"@types/node": "^25.0.3",
|
||||||
"@types/qrcode": "^1.5.6",
|
"@types/qrcode": "^1.5.6",
|
||||||
"@types/react": "^19.2.5",
|
"@types/react": "^19.2.5",
|
||||||
@@ -82,6 +85,7 @@
|
|||||||
"lint-staged": "^16.2.7",
|
"lint-staged": "^16.2.7",
|
||||||
"simple-git-hooks": "^2.13.1",
|
"simple-git-hooks": "^2.13.1",
|
||||||
"tw-animate-css": "^1.4.0",
|
"tw-animate-css": "^1.4.0",
|
||||||
|
"type-fest": "^5.4.1",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"typescript-eslint": "^8.46.4",
|
"typescript-eslint": "^8.46.4",
|
||||||
"vite": "^7.2.4",
|
"vite": "^7.2.4",
|
||||||
|
|||||||
529
client/cms/pnpm-lock.yaml
generated
529
client/cms/pnpm-lock.yaml
generated
@@ -89,6 +89,9 @@ importers:
|
|||||||
'@tanstack/zod-form-adapter':
|
'@tanstack/zod-form-adapter':
|
||||||
specifier: ^0.42.1
|
specifier: ^0.42.1
|
||||||
version: 0.42.1(zod@4.3.5)
|
version: 0.42.1(zod@4.3.5)
|
||||||
|
'@uiw/react-md-editor':
|
||||||
|
specifier: ^4.0.11
|
||||||
|
version: 4.0.11(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
axios:
|
axios:
|
||||||
specifier: ^1.13.2
|
specifier: ^1.13.2
|
||||||
version: 1.13.2
|
version: 1.13.2
|
||||||
@@ -110,6 +113,9 @@ importers:
|
|||||||
immer:
|
immer:
|
||||||
specifier: ^11.1.0
|
specifier: ^11.1.0
|
||||||
version: 11.1.3
|
version: 11.1.3
|
||||||
|
lodash-es:
|
||||||
|
specifier: ^4.17.22
|
||||||
|
version: 4.17.22
|
||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.562.0
|
specifier: ^0.562.0
|
||||||
version: 0.562.0(react@19.2.3)
|
version: 0.562.0(react@19.2.3)
|
||||||
@@ -180,6 +186,9 @@ importers:
|
|||||||
'@types/culori':
|
'@types/culori':
|
||||||
specifier: ^4.0.1
|
specifier: ^4.0.1
|
||||||
version: 4.0.1
|
version: 4.0.1
|
||||||
|
'@types/lodash-es':
|
||||||
|
specifier: ^4.17.12
|
||||||
|
version: 4.17.12
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^25.0.3
|
specifier: ^25.0.3
|
||||||
version: 25.0.9
|
version: 25.0.9
|
||||||
@@ -219,6 +228,9 @@ importers:
|
|||||||
tw-animate-css:
|
tw-animate-css:
|
||||||
specifier: ^1.4.0
|
specifier: ^1.4.0
|
||||||
version: 1.4.0
|
version: 1.4.0
|
||||||
|
type-fest:
|
||||||
|
specifier: ^5.4.1
|
||||||
|
version: 5.4.1
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ~5.9.3
|
specifier: ~5.9.3
|
||||||
version: 5.9.3
|
version: 5.9.3
|
||||||
@@ -1254,66 +1266,79 @@ packages:
|
|||||||
resolution: {integrity: sha512-AEXMESUDWWGqD6LwO/HkqCZgUE1VCJ1OhbvYGsfqX2Y6w5quSXuyoy/Fg3nRqiwro+cJYFxiw5v4kB2ZDLhxrw==}
|
resolution: {integrity: sha512-AEXMESUDWWGqD6LwO/HkqCZgUE1VCJ1OhbvYGsfqX2Y6w5quSXuyoy/Fg3nRqiwro+cJYFxiw5v4kB2ZDLhxrw==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm-musleabihf@4.55.2':
|
'@rollup/rollup-linux-arm-musleabihf@4.55.2':
|
||||||
resolution: {integrity: sha512-ZV7EljjBDwBBBSv570VWj0hiNTdHt9uGznDtznBB4Caj3ch5rgD4I2K1GQrtbvJ/QiB+663lLgOdcADMNVC29Q==}
|
resolution: {integrity: sha512-ZV7EljjBDwBBBSv570VWj0hiNTdHt9uGznDtznBB4Caj3ch5rgD4I2K1GQrtbvJ/QiB+663lLgOdcADMNVC29Q==}
|
||||||
cpu: [arm]
|
cpu: [arm]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-gnu@4.55.2':
|
'@rollup/rollup-linux-arm64-gnu@4.55.2':
|
||||||
resolution: {integrity: sha512-uvjwc8NtQVPAJtq4Tt7Q49FOodjfbf6NpqXyW/rjXoV+iZ3EJAHLNAnKT5UJBc6ffQVgmXTUL2ifYiLABlGFqA==}
|
resolution: {integrity: sha512-uvjwc8NtQVPAJtq4Tt7Q49FOodjfbf6NpqXyW/rjXoV+iZ3EJAHLNAnKT5UJBc6ffQVgmXTUL2ifYiLABlGFqA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-arm64-musl@4.55.2':
|
'@rollup/rollup-linux-arm64-musl@4.55.2':
|
||||||
resolution: {integrity: sha512-s3KoWVNnye9mm/2WpOZ3JeUiediUVw6AvY/H7jNA6qgKA2V2aM25lMkVarTDfiicn/DLq3O0a81jncXszoyCFA==}
|
resolution: {integrity: sha512-s3KoWVNnye9mm/2WpOZ3JeUiediUVw6AvY/H7jNA6qgKA2V2aM25lMkVarTDfiicn/DLq3O0a81jncXszoyCFA==}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-gnu@4.55.2':
|
'@rollup/rollup-linux-loong64-gnu@4.55.2':
|
||||||
resolution: {integrity: sha512-gi21faacK+J8aVSyAUptML9VQN26JRxe484IbF+h3hpG+sNVoMXPduhREz2CcYr5my0NE3MjVvQ5bMKX71pfVA==}
|
resolution: {integrity: sha512-gi21faacK+J8aVSyAUptML9VQN26JRxe484IbF+h3hpG+sNVoMXPduhREz2CcYr5my0NE3MjVvQ5bMKX71pfVA==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-loong64-musl@4.55.2':
|
'@rollup/rollup-linux-loong64-musl@4.55.2':
|
||||||
resolution: {integrity: sha512-qSlWiXnVaS/ceqXNfnoFZh4IiCA0EwvCivivTGbEu1qv2o+WTHpn1zNmCTAoOG5QaVr2/yhCoLScQtc/7RxshA==}
|
resolution: {integrity: sha512-qSlWiXnVaS/ceqXNfnoFZh4IiCA0EwvCivivTGbEu1qv2o+WTHpn1zNmCTAoOG5QaVr2/yhCoLScQtc/7RxshA==}
|
||||||
cpu: [loong64]
|
cpu: [loong64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-gnu@4.55.2':
|
'@rollup/rollup-linux-ppc64-gnu@4.55.2':
|
||||||
resolution: {integrity: sha512-rPyuLFNoF1B0+wolH277E780NUKf+KoEDb3OyoLbAO18BbeKi++YN6gC/zuJoPPDlQRL3fIxHxCxVEWiem2yXw==}
|
resolution: {integrity: sha512-rPyuLFNoF1B0+wolH277E780NUKf+KoEDb3OyoLbAO18BbeKi++YN6gC/zuJoPPDlQRL3fIxHxCxVEWiem2yXw==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-ppc64-musl@4.55.2':
|
'@rollup/rollup-linux-ppc64-musl@4.55.2':
|
||||||
resolution: {integrity: sha512-g+0ZLMook31iWV4PvqKU0i9E78gaZgYpSrYPed/4Bu+nGTgfOPtfs1h11tSSRPXSjC5EzLTjV/1A7L2Vr8pJoQ==}
|
resolution: {integrity: sha512-g+0ZLMook31iWV4PvqKU0i9E78gaZgYpSrYPed/4Bu+nGTgfOPtfs1h11tSSRPXSjC5EzLTjV/1A7L2Vr8pJoQ==}
|
||||||
cpu: [ppc64]
|
cpu: [ppc64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-gnu@4.55.2':
|
'@rollup/rollup-linux-riscv64-gnu@4.55.2':
|
||||||
resolution: {integrity: sha512-i+sGeRGsjKZcQRh3BRfpLsM3LX3bi4AoEVqmGDyc50L6KfYsN45wVCSz70iQMwPWr3E5opSiLOwsC9WB4/1pqg==}
|
resolution: {integrity: sha512-i+sGeRGsjKZcQRh3BRfpLsM3LX3bi4AoEVqmGDyc50L6KfYsN45wVCSz70iQMwPWr3E5opSiLOwsC9WB4/1pqg==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-riscv64-musl@4.55.2':
|
'@rollup/rollup-linux-riscv64-musl@4.55.2':
|
||||||
resolution: {integrity: sha512-C1vLcKc4MfFV6I0aWsC7B2Y9QcsiEcvKkfxprwkPfLaN8hQf0/fKHwSF2lcYzA9g4imqnhic729VB9Fo70HO3Q==}
|
resolution: {integrity: sha512-C1vLcKc4MfFV6I0aWsC7B2Y9QcsiEcvKkfxprwkPfLaN8hQf0/fKHwSF2lcYzA9g4imqnhic729VB9Fo70HO3Q==}
|
||||||
cpu: [riscv64]
|
cpu: [riscv64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-linux-s390x-gnu@4.55.2':
|
'@rollup/rollup-linux-s390x-gnu@4.55.2':
|
||||||
resolution: {integrity: sha512-68gHUK/howpQjh7g7hlD9DvTTt4sNLp1Bb+Yzw2Ki0xvscm2cOdCLZNJNhd2jW8lsTPrHAHuF751BygifW4bkQ==}
|
resolution: {integrity: sha512-68gHUK/howpQjh7g7hlD9DvTTt4sNLp1Bb+Yzw2Ki0xvscm2cOdCLZNJNhd2jW8lsTPrHAHuF751BygifW4bkQ==}
|
||||||
cpu: [s390x]
|
cpu: [s390x]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-gnu@4.55.2':
|
'@rollup/rollup-linux-x64-gnu@4.55.2':
|
||||||
resolution: {integrity: sha512-1e30XAuaBP1MAizaOBApsgeGZge2/Byd6wV4a8oa6jPdHELbRHBiw7wvo4dp7Ie2PE8TZT4pj9RLGZv9N4qwlw==}
|
resolution: {integrity: sha512-1e30XAuaBP1MAizaOBApsgeGZge2/Byd6wV4a8oa6jPdHELbRHBiw7wvo4dp7Ie2PE8TZT4pj9RLGZv9N4qwlw==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@rollup/rollup-linux-x64-musl@4.55.2':
|
'@rollup/rollup-linux-x64-musl@4.55.2':
|
||||||
resolution: {integrity: sha512-4BJucJBGbuGnH6q7kpPqGJGzZnYrpAzRd60HQSt3OpX/6/YVgSsJnNzR8Ot74io50SeVT4CtCWe/RYIAymFPwA==}
|
resolution: {integrity: sha512-4BJucJBGbuGnH6q7kpPqGJGzZnYrpAzRd60HQSt3OpX/6/YVgSsJnNzR8Ot74io50SeVT4CtCWe/RYIAymFPwA==}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@rollup/rollup-openbsd-x64@4.55.2':
|
'@rollup/rollup-openbsd-x64@4.55.2':
|
||||||
resolution: {integrity: sha512-cT2MmXySMo58ENv8p6/O6wI/h/gLnD3D6JoajwXFZH6X9jz4hARqUhWpGuQhOgLNXscfZYRQMJvZDtWNzMAIDw==}
|
resolution: {integrity: sha512-cT2MmXySMo58ENv8p6/O6wI/h/gLnD3D6JoajwXFZH6X9jz4hARqUhWpGuQhOgLNXscfZYRQMJvZDtWNzMAIDw==}
|
||||||
@@ -1472,24 +1497,28 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
|
'@tailwindcss/oxide-linux-arm64-musl@4.1.18':
|
||||||
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
|
resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
|
'@tailwindcss/oxide-linux-x64-gnu@4.1.18':
|
||||||
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
|
resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
|
'@tailwindcss/oxide-linux-x64-musl@4.1.18':
|
||||||
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
|
resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
|
'@tailwindcss/oxide-wasm32-wasi@4.1.18':
|
||||||
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
|
resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==}
|
||||||
@@ -1724,12 +1753,21 @@ packages:
|
|||||||
'@types/estree@1.0.8':
|
'@types/estree@1.0.8':
|
||||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||||
|
|
||||||
|
'@types/hast@2.3.10':
|
||||||
|
resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==}
|
||||||
|
|
||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==}
|
||||||
|
|
||||||
'@types/json-schema@7.0.15':
|
'@types/json-schema@7.0.15':
|
||||||
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
|
||||||
|
|
||||||
|
'@types/lodash-es@4.17.12':
|
||||||
|
resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==}
|
||||||
|
|
||||||
|
'@types/lodash@4.17.23':
|
||||||
|
resolution: {integrity: sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==}
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==}
|
||||||
|
|
||||||
@@ -1739,6 +1777,9 @@ packages:
|
|||||||
'@types/node@25.0.9':
|
'@types/node@25.0.9':
|
||||||
resolution: {integrity: sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==}
|
resolution: {integrity: sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==}
|
||||||
|
|
||||||
|
'@types/prismjs@1.26.5':
|
||||||
|
resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==}
|
||||||
|
|
||||||
'@types/qrcode@1.5.6':
|
'@types/qrcode@1.5.6':
|
||||||
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
||||||
|
|
||||||
@@ -1818,6 +1859,21 @@ packages:
|
|||||||
resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==}
|
resolution: {integrity: sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
|
|
||||||
|
'@uiw/copy-to-clipboard@1.0.19':
|
||||||
|
resolution: {integrity: sha512-AYxzFUBkZrhtExb2QC0C4lFH2+BSx6JVId9iqeGHakBuosqiQHUQaNZCvIBeM97Ucp+nJ22flOh8FBT2pKRRAA==}
|
||||||
|
|
||||||
|
'@uiw/react-markdown-preview@5.1.5':
|
||||||
|
resolution: {integrity: sha512-DNOqx1a6gJR7Btt57zpGEKTfHRlb7rWbtctMRO2f82wWcuoJsxPBrM+JWebDdOD0LfD8oe2CQvW2ICQJKHQhZg==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
|
'@uiw/react-md-editor@4.0.11':
|
||||||
|
resolution: {integrity: sha512-F0OR5O1v54EkZYvJj3ew0I7UqLiPeU34hMAY4MdXS3hI86rruYi5DHVkG/VuvLkUZW7wIETM2QFtZ459gKIjQA==}
|
||||||
|
peerDependencies:
|
||||||
|
react: '>=16.8.0'
|
||||||
|
react-dom: '>=16.8.0'
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0':
|
'@ungap/structured-clone@1.3.0':
|
||||||
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
|
||||||
|
|
||||||
@@ -1936,6 +1992,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==}
|
resolution: {integrity: sha512-kX8h7K2srmDyYnXRIppo4AH/wYgzWVCs+eKr3RusRSQ5PvRYoEFmR/I0PbdTjKFAoKqp5+kbxnNTFO9jOfSVJg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
bcp-47-match@2.0.3:
|
||||||
|
resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==}
|
||||||
|
|
||||||
binary-extensions@2.3.0:
|
binary-extensions@2.3.0:
|
||||||
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -2106,6 +2165,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
|
css-selector-parser@3.3.0:
|
||||||
|
resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==}
|
||||||
|
|
||||||
cssesc@3.0.0:
|
cssesc@3.0.0:
|
||||||
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -2213,6 +2275,10 @@ packages:
|
|||||||
dijkstrajs@1.0.3:
|
dijkstrajs@1.0.3:
|
||||||
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||||
|
|
||||||
|
direction@2.0.1:
|
||||||
|
resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
dom-helpers@5.2.1:
|
dom-helpers@5.2.1:
|
||||||
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
|
||||||
|
|
||||||
@@ -2244,6 +2310,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
|
||||||
engines: {node: '>=0.12'}
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
|
entities@6.0.1:
|
||||||
|
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
entities@7.0.0:
|
entities@7.0.0:
|
||||||
resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==}
|
resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==}
|
||||||
engines: {node: '>=0.12'}
|
engines: {node: '>=0.12'}
|
||||||
@@ -2724,12 +2794,54 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
hast-util-from-html@2.0.3:
|
||||||
|
resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==}
|
||||||
|
|
||||||
|
hast-util-from-parse5@8.0.3:
|
||||||
|
resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==}
|
||||||
|
|
||||||
|
hast-util-has-property@3.0.0:
|
||||||
|
resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==}
|
||||||
|
|
||||||
|
hast-util-heading-rank@3.0.0:
|
||||||
|
resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==}
|
||||||
|
|
||||||
|
hast-util-is-element@3.0.0:
|
||||||
|
resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==}
|
||||||
|
|
||||||
|
hast-util-parse-selector@3.1.1:
|
||||||
|
resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==}
|
||||||
|
|
||||||
|
hast-util-parse-selector@4.0.0:
|
||||||
|
resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==}
|
||||||
|
|
||||||
|
hast-util-raw@9.1.0:
|
||||||
|
resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==}
|
||||||
|
|
||||||
|
hast-util-select@6.0.4:
|
||||||
|
resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==}
|
||||||
|
|
||||||
|
hast-util-to-html@9.0.5:
|
||||||
|
resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==}
|
||||||
|
|
||||||
hast-util-to-jsx-runtime@2.3.6:
|
hast-util-to-jsx-runtime@2.3.6:
|
||||||
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
|
resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==}
|
||||||
|
|
||||||
|
hast-util-to-parse5@8.0.1:
|
||||||
|
resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==}
|
||||||
|
|
||||||
|
hast-util-to-string@3.0.1:
|
||||||
|
resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==}
|
||||||
|
|
||||||
hast-util-whitespace@3.0.0:
|
hast-util-whitespace@3.0.0:
|
||||||
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==}
|
||||||
|
|
||||||
|
hastscript@7.2.0:
|
||||||
|
resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==}
|
||||||
|
|
||||||
|
hastscript@9.0.1:
|
||||||
|
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
|
||||||
|
|
||||||
hermes-estree@0.25.1:
|
hermes-estree@0.25.1:
|
||||||
resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
|
resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
|
||||||
|
|
||||||
@@ -2742,6 +2854,9 @@ packages:
|
|||||||
html-url-attributes@3.0.1:
|
html-url-attributes@3.0.1:
|
||||||
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==}
|
||||||
|
|
||||||
|
html-void-elements@3.0.0:
|
||||||
|
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
|
||||||
|
|
||||||
ieee754@1.2.1:
|
ieee754@1.2.1:
|
||||||
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
|
||||||
|
|
||||||
@@ -2922,24 +3037,28 @@ packages:
|
|||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-arm64-musl@1.30.2:
|
lightningcss-linux-arm64-musl@1.30.2:
|
||||||
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-linux-x64-gnu@1.30.2:
|
lightningcss-linux-x64-gnu@1.30.2:
|
||||||
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [glibc]
|
||||||
|
|
||||||
lightningcss-linux-x64-musl@1.30.2:
|
lightningcss-linux-x64-musl@1.30.2:
|
||||||
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==}
|
||||||
engines: {node: '>= 12.0.0'}
|
engines: {node: '>= 12.0.0'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
|
libc: [musl]
|
||||||
|
|
||||||
lightningcss-win32-arm64-msvc@1.30.2:
|
lightningcss-win32-arm64-msvc@1.30.2:
|
||||||
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==}
|
||||||
@@ -2981,6 +3100,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
lodash-es@4.17.22:
|
||||||
|
resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==}
|
||||||
|
|
||||||
lodash.merge@4.6.2:
|
lodash.merge@4.6.2:
|
||||||
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
|
||||||
|
|
||||||
@@ -3274,9 +3396,15 @@ packages:
|
|||||||
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
parse-numeric-range@1.3.0:
|
||||||
|
resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==}
|
||||||
|
|
||||||
parse-statements@1.0.11:
|
parse-statements@1.0.11:
|
||||||
resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==}
|
resolution: {integrity: sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==}
|
||||||
|
|
||||||
|
parse5@7.3.0:
|
||||||
|
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
|
||||||
|
|
||||||
path-exists@4.0.0:
|
path-exists@4.0.0:
|
||||||
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3349,6 +3477,9 @@ packages:
|
|||||||
prop-types@15.8.1:
|
prop-types@15.8.1:
|
||||||
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
|
||||||
|
|
||||||
|
property-information@6.5.0:
|
||||||
|
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
|
||||||
|
|
||||||
property-information@7.1.0:
|
property-information@7.1.0:
|
||||||
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
|
||||||
|
|
||||||
@@ -3390,6 +3521,12 @@ packages:
|
|||||||
'@types/react': '>=18'
|
'@types/react': '>=18'
|
||||||
react: '>=18'
|
react: '>=18'
|
||||||
|
|
||||||
|
react-markdown@9.0.3:
|
||||||
|
resolution: {integrity: sha512-Yk7Z94dbgYTOrdk41Z74GoKA7rThnsbbqBTRYuxoe08qvfQ9tJVhmAKw6BJS/ZORG7kTy/s1QvYzSuaoBA1qfw==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/react': '>=18'
|
||||||
|
react: '>=18'
|
||||||
|
|
||||||
react-refresh@0.18.0:
|
react-refresh@0.18.0:
|
||||||
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
|
resolution: {integrity: sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -3462,6 +3599,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
|
resolution: {integrity: sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g==}
|
||||||
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||||
|
|
||||||
|
refractor@4.9.0:
|
||||||
|
resolution: {integrity: sha512-nEG1SPXFoGGx+dcjftjv8cAjEusIh6ED1xhf5DG3C0x/k+rmZ2duKnc3QLpt6qeHv5fPb8uwN3VWN2BT7fr3Og==}
|
||||||
|
|
||||||
regexp-ast-analysis@0.7.1:
|
regexp-ast-analysis@0.7.1:
|
||||||
resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==}
|
resolution: {integrity: sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A==}
|
||||||
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
|
||||||
@@ -3474,12 +3614,58 @@ packages:
|
|||||||
resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==}
|
resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
rehype-attr@3.0.3:
|
||||||
|
resolution: {integrity: sha512-Up50Xfra8tyxnkJdCzLBIBtxOcB2M1xdeKe1324U06RAvSjYm7ULSeoM+b/nYPQPVd7jsXJ9+39IG1WAJPXONw==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
rehype-autolink-headings@7.1.0:
|
||||||
|
resolution: {integrity: sha512-rItO/pSdvnvsP4QRB1pmPiNHUskikqtPojZKJPPPAVx9Hj8i8TwMBhofrrAYRhYOOBZH9tgmG5lPqDLuIWPWmw==}
|
||||||
|
|
||||||
|
rehype-ignore@2.0.3:
|
||||||
|
resolution: {integrity: sha512-IzhP6/u/6sm49sdktuYSmeIuObWB+5yC/5eqVws8BhuGA9kY25/byz6uCy/Ravj6lXUShEd2ofHM5MyAIj86Sg==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
rehype-parse@9.0.1:
|
||||||
|
resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==}
|
||||||
|
|
||||||
|
rehype-prism-plus@2.0.0:
|
||||||
|
resolution: {integrity: sha512-FeM/9V2N7EvDZVdR2dqhAzlw5YI49m9Tgn7ZrYJeYHIahM6gcXpH0K1y2gNnKanZCydOMluJvX2cB9z3lhY8XQ==}
|
||||||
|
|
||||||
|
rehype-prism-plus@2.0.1:
|
||||||
|
resolution: {integrity: sha512-Wglct0OW12tksTUseAPyWPo3srjBOY7xKlql/DPKi7HbsdZTyaLCAoO58QBKSczFQxElTsQlOY3JDOFzB/K++Q==}
|
||||||
|
|
||||||
|
rehype-raw@7.0.0:
|
||||||
|
resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==}
|
||||||
|
|
||||||
|
rehype-rewrite@4.0.4:
|
||||||
|
resolution: {integrity: sha512-L/FO96EOzSA6bzOam4DVu61/PB3AGKcSPXpa53yMIozoxH4qg1+bVZDF8zh1EsuxtSauAhzt5cCnvoplAaSLrw==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
rehype-slug@6.0.0:
|
||||||
|
resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==}
|
||||||
|
|
||||||
|
rehype-stringify@10.0.1:
|
||||||
|
resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==}
|
||||||
|
|
||||||
|
rehype@13.0.2:
|
||||||
|
resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==}
|
||||||
|
|
||||||
|
remark-gfm@4.0.1:
|
||||||
|
resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==}
|
||||||
|
|
||||||
|
remark-github-blockquote-alert@1.3.1:
|
||||||
|
resolution: {integrity: sha512-OPNnimcKeozWN1w8KVQEuHOxgN3L4rah8geMOLhA5vN9wITqU4FWD+G26tkEsCGHiOVDbISx+Se5rGZ+D1p0Jg==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
remark-parse@11.0.0:
|
remark-parse@11.0.0:
|
||||||
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
|
resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==}
|
||||||
|
|
||||||
remark-rehype@11.1.2:
|
remark-rehype@11.1.2:
|
||||||
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
|
resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==}
|
||||||
|
|
||||||
|
remark-stringify@11.0.0:
|
||||||
|
resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==}
|
||||||
|
|
||||||
require-directory@2.1.1:
|
require-directory@2.1.1:
|
||||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -3650,6 +3836,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==}
|
resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
|
|
||||||
|
tagged-tag@1.0.0:
|
||||||
|
resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
tailwind-merge@3.4.0:
|
tailwind-merge@3.4.0:
|
||||||
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
|
resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==}
|
||||||
|
|
||||||
@@ -3721,6 +3911,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
type-fest@5.4.1:
|
||||||
|
resolution: {integrity: sha512-xygQcmneDyzsEuKZrFbRMne5HDqMs++aFzefrJTgEIKjQ3rekM+RPfFCVq2Gp1VIDqddoYeppCj4Pcb+RZW0GQ==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
typescript-eslint@8.53.1:
|
typescript-eslint@8.53.1:
|
||||||
resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==}
|
resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -3742,6 +3936,9 @@ packages:
|
|||||||
unified@11.0.5:
|
unified@11.0.5:
|
||||||
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
|
||||||
|
|
||||||
|
unist-util-filter@5.0.1:
|
||||||
|
resolution: {integrity: sha512-pHx7D4Zt6+TsfwylH9+lYhBhzyhEnCXs/lbq/Hstxno5z4gVdyc2WEW0asfjGKPyG4pEKrnBv5hdkO6+aRnQJw==}
|
||||||
|
|
||||||
unist-util-is@6.0.1:
|
unist-util-is@6.0.1:
|
||||||
resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
|
resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
|
||||||
|
|
||||||
@@ -3807,6 +4004,9 @@ packages:
|
|||||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc
|
||||||
|
|
||||||
|
vfile-location@5.0.3:
|
||||||
|
resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==}
|
||||||
|
|
||||||
vfile-message@4.0.3:
|
vfile-message@4.0.3:
|
||||||
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
|
resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==}
|
||||||
|
|
||||||
@@ -3867,6 +4067,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
eslint: ^8.57.0 || ^9.0.0
|
eslint: ^8.57.0 || ^9.0.0
|
||||||
|
|
||||||
|
web-namespaces@2.0.1:
|
||||||
|
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
|
||||||
|
|
||||||
webpack-virtual-modules@0.6.2:
|
webpack-virtual-modules@0.6.2:
|
||||||
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==}
|
||||||
|
|
||||||
@@ -5386,12 +5589,22 @@ snapshots:
|
|||||||
|
|
||||||
'@types/estree@1.0.8': {}
|
'@types/estree@1.0.8': {}
|
||||||
|
|
||||||
|
'@types/hast@2.3.10':
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 2.0.11
|
||||||
|
|
||||||
'@types/hast@3.0.4':
|
'@types/hast@3.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
|
|
||||||
'@types/json-schema@7.0.15': {}
|
'@types/json-schema@7.0.15': {}
|
||||||
|
|
||||||
|
'@types/lodash-es@4.17.12':
|
||||||
|
dependencies:
|
||||||
|
'@types/lodash': 4.17.23
|
||||||
|
|
||||||
|
'@types/lodash@4.17.23': {}
|
||||||
|
|
||||||
'@types/mdast@4.0.4':
|
'@types/mdast@4.0.4':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
@@ -5402,6 +5615,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
undici-types: 7.16.0
|
undici-types: 7.16.0
|
||||||
|
|
||||||
|
'@types/prismjs@1.26.5': {}
|
||||||
|
|
||||||
'@types/qrcode@1.5.6':
|
'@types/qrcode@1.5.6':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 25.0.9
|
'@types/node': 25.0.9
|
||||||
@@ -5511,6 +5726,41 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.53.1
|
'@typescript-eslint/types': 8.53.1
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
|
|
||||||
|
'@uiw/copy-to-clipboard@1.0.19': {}
|
||||||
|
|
||||||
|
'@uiw/react-markdown-preview@5.1.5(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.28.6
|
||||||
|
'@uiw/copy-to-clipboard': 1.0.19
|
||||||
|
react: 19.2.3
|
||||||
|
react-dom: 19.2.3(react@19.2.3)
|
||||||
|
react-markdown: 9.0.3(@types/react@19.2.8)(react@19.2.3)
|
||||||
|
rehype-attr: 3.0.3
|
||||||
|
rehype-autolink-headings: 7.1.0
|
||||||
|
rehype-ignore: 2.0.3
|
||||||
|
rehype-prism-plus: 2.0.0
|
||||||
|
rehype-raw: 7.0.0
|
||||||
|
rehype-rewrite: 4.0.4
|
||||||
|
rehype-slug: 6.0.0
|
||||||
|
remark-gfm: 4.0.1
|
||||||
|
remark-github-blockquote-alert: 1.3.1
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@uiw/react-md-editor@4.0.11(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.28.6
|
||||||
|
'@uiw/react-markdown-preview': 5.1.5(@types/react@19.2.8)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||||
|
react: 19.2.3
|
||||||
|
react-dom: 19.2.3(react@19.2.3)
|
||||||
|
rehype: 13.0.2
|
||||||
|
rehype-prism-plus: 2.0.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@types/react'
|
||||||
|
- supports-color
|
||||||
|
|
||||||
'@ungap/structured-clone@1.3.0': {}
|
'@ungap/structured-clone@1.3.0': {}
|
||||||
|
|
||||||
'@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))':
|
'@vitejs/plugin-react@5.1.2(vite@7.3.1(@types/node@25.0.9)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))':
|
||||||
@@ -5642,6 +5892,8 @@ snapshots:
|
|||||||
|
|
||||||
baseline-browser-mapping@2.9.15: {}
|
baseline-browser-mapping@2.9.15: {}
|
||||||
|
|
||||||
|
bcp-47-match@2.0.3: {}
|
||||||
|
|
||||||
binary-extensions@2.3.0: {}
|
binary-extensions@2.3.0: {}
|
||||||
|
|
||||||
birecord@0.1.1: {}
|
birecord@0.1.1: {}
|
||||||
@@ -5798,6 +6050,8 @@ snapshots:
|
|||||||
shebang-command: 2.0.0
|
shebang-command: 2.0.0
|
||||||
which: 2.0.2
|
which: 2.0.2
|
||||||
|
|
||||||
|
css-selector-parser@3.3.0: {}
|
||||||
|
|
||||||
cssesc@3.0.0: {}
|
cssesc@3.0.0: {}
|
||||||
|
|
||||||
csstype@3.2.3: {}
|
csstype@3.2.3: {}
|
||||||
@@ -5874,6 +6128,8 @@ snapshots:
|
|||||||
|
|
||||||
dijkstrajs@1.0.3: {}
|
dijkstrajs@1.0.3: {}
|
||||||
|
|
||||||
|
direction@2.0.1: {}
|
||||||
|
|
||||||
dom-helpers@5.2.1:
|
dom-helpers@5.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.28.6
|
'@babel/runtime': 7.28.6
|
||||||
@@ -5905,6 +6161,8 @@ snapshots:
|
|||||||
|
|
||||||
entities@4.5.0: {}
|
entities@4.5.0: {}
|
||||||
|
|
||||||
|
entities@6.0.1: {}
|
||||||
|
|
||||||
entities@7.0.0: {}
|
entities@7.0.0: {}
|
||||||
|
|
||||||
environment@1.1.0: {}
|
environment@1.1.0: {}
|
||||||
@@ -6503,6 +6761,94 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
hast-util-from-html@2.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
devlop: 1.1.0
|
||||||
|
hast-util-from-parse5: 8.0.3
|
||||||
|
parse5: 7.3.0
|
||||||
|
vfile: 6.0.3
|
||||||
|
vfile-message: 4.0.3
|
||||||
|
|
||||||
|
hast-util-from-parse5@8.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
devlop: 1.1.0
|
||||||
|
hastscript: 9.0.1
|
||||||
|
property-information: 7.1.0
|
||||||
|
vfile: 6.0.3
|
||||||
|
vfile-location: 5.0.3
|
||||||
|
web-namespaces: 2.0.1
|
||||||
|
|
||||||
|
hast-util-has-property@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
hast-util-heading-rank@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
hast-util-is-element@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
hast-util-parse-selector@3.1.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 2.3.10
|
||||||
|
|
||||||
|
hast-util-parse-selector@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
hast-util-raw@9.1.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
'@ungap/structured-clone': 1.3.0
|
||||||
|
hast-util-from-parse5: 8.0.3
|
||||||
|
hast-util-to-parse5: 8.0.1
|
||||||
|
html-void-elements: 3.0.0
|
||||||
|
mdast-util-to-hast: 13.2.1
|
||||||
|
parse5: 7.3.0
|
||||||
|
unist-util-position: 5.0.0
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
vfile: 6.0.3
|
||||||
|
web-namespaces: 2.0.1
|
||||||
|
zwitch: 2.0.4
|
||||||
|
|
||||||
|
hast-util-select@6.0.4:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
bcp-47-match: 2.0.3
|
||||||
|
comma-separated-tokens: 2.0.3
|
||||||
|
css-selector-parser: 3.3.0
|
||||||
|
devlop: 1.1.0
|
||||||
|
direction: 2.0.1
|
||||||
|
hast-util-has-property: 3.0.0
|
||||||
|
hast-util-to-string: 3.0.1
|
||||||
|
hast-util-whitespace: 3.0.0
|
||||||
|
nth-check: 2.1.1
|
||||||
|
property-information: 7.1.0
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
zwitch: 2.0.4
|
||||||
|
|
||||||
|
hast-util-to-html@9.0.5:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
ccount: 2.0.1
|
||||||
|
comma-separated-tokens: 2.0.3
|
||||||
|
hast-util-whitespace: 3.0.0
|
||||||
|
html-void-elements: 3.0.0
|
||||||
|
mdast-util-to-hast: 13.2.1
|
||||||
|
property-information: 7.1.0
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
stringify-entities: 4.0.4
|
||||||
|
zwitch: 2.0.4
|
||||||
|
|
||||||
hast-util-to-jsx-runtime@2.3.6:
|
hast-util-to-jsx-runtime@2.3.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
@@ -6523,10 +6869,40 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
hast-util-to-parse5@8.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
comma-separated-tokens: 2.0.3
|
||||||
|
devlop: 1.1.0
|
||||||
|
property-information: 7.1.0
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
web-namespaces: 2.0.1
|
||||||
|
zwitch: 2.0.4
|
||||||
|
|
||||||
|
hast-util-to-string@3.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
hast-util-whitespace@3.0.0:
|
hast-util-whitespace@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/hast': 3.0.4
|
'@types/hast': 3.0.4
|
||||||
|
|
||||||
|
hastscript@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 2.3.10
|
||||||
|
comma-separated-tokens: 2.0.3
|
||||||
|
hast-util-parse-selector: 3.1.1
|
||||||
|
property-information: 6.5.0
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
|
||||||
|
hastscript@9.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
comma-separated-tokens: 2.0.3
|
||||||
|
hast-util-parse-selector: 4.0.0
|
||||||
|
property-information: 7.1.0
|
||||||
|
space-separated-tokens: 2.0.2
|
||||||
|
|
||||||
hermes-estree@0.25.1: {}
|
hermes-estree@0.25.1: {}
|
||||||
|
|
||||||
hermes-parser@0.25.1:
|
hermes-parser@0.25.1:
|
||||||
@@ -6537,6 +6913,8 @@ snapshots:
|
|||||||
|
|
||||||
html-url-attributes@3.0.1: {}
|
html-url-attributes@3.0.1: {}
|
||||||
|
|
||||||
|
html-void-elements@3.0.0: {}
|
||||||
|
|
||||||
ieee754@1.2.1: {}
|
ieee754@1.2.1: {}
|
||||||
|
|
||||||
ignore@5.3.2: {}
|
ignore@5.3.2: {}
|
||||||
@@ -6733,6 +7111,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
|
|
||||||
|
lodash-es@4.17.22: {}
|
||||||
|
|
||||||
lodash.merge@4.6.2: {}
|
lodash.merge@4.6.2: {}
|
||||||
|
|
||||||
lodash@4.17.21: {}
|
lodash@4.17.21: {}
|
||||||
@@ -7253,8 +7633,14 @@ snapshots:
|
|||||||
json-parse-even-better-errors: 2.3.1
|
json-parse-even-better-errors: 2.3.1
|
||||||
lines-and-columns: 1.2.4
|
lines-and-columns: 1.2.4
|
||||||
|
|
||||||
|
parse-numeric-range@1.3.0: {}
|
||||||
|
|
||||||
parse-statements@1.0.11: {}
|
parse-statements@1.0.11: {}
|
||||||
|
|
||||||
|
parse5@7.3.0:
|
||||||
|
dependencies:
|
||||||
|
entities: 6.0.1
|
||||||
|
|
||||||
path-exists@4.0.0: {}
|
path-exists@4.0.0: {}
|
||||||
|
|
||||||
path-key@3.1.1: {}
|
path-key@3.1.1: {}
|
||||||
@@ -7317,6 +7703,8 @@ snapshots:
|
|||||||
object-assign: 4.1.1
|
object-assign: 4.1.1
|
||||||
react-is: 16.13.1
|
react-is: 16.13.1
|
||||||
|
|
||||||
|
property-information@6.5.0: {}
|
||||||
|
|
||||||
property-information@7.1.0: {}
|
property-information@7.1.0: {}
|
||||||
|
|
||||||
proxy-from-env@1.1.0: {}
|
proxy-from-env@1.1.0: {}
|
||||||
@@ -7362,6 +7750,23 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
react-markdown@9.0.3(@types/react@19.2.8)(react@19.2.3):
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@types/react': 19.2.8
|
||||||
|
devlop: 1.1.0
|
||||||
|
hast-util-to-jsx-runtime: 2.3.6
|
||||||
|
html-url-attributes: 3.0.1
|
||||||
|
mdast-util-to-hast: 13.2.1
|
||||||
|
react: 19.2.3
|
||||||
|
remark-parse: 11.0.0
|
||||||
|
remark-rehype: 11.1.2
|
||||||
|
unified: 11.0.5
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
vfile: 6.0.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
react-refresh@0.18.0: {}
|
react-refresh@0.18.0: {}
|
||||||
|
|
||||||
react-remove-scroll-bar@2.3.8(@types/react@19.2.8)(react@19.2.3):
|
react-remove-scroll-bar@2.3.8(@types/react@19.2.8)(react@19.2.3):
|
||||||
@@ -7443,6 +7848,13 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
|
|
||||||
|
refractor@4.9.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 2.3.10
|
||||||
|
'@types/prismjs': 1.26.5
|
||||||
|
hastscript: 7.2.0
|
||||||
|
parse-entities: 4.0.2
|
||||||
|
|
||||||
regexp-ast-analysis@0.7.1:
|
regexp-ast-analysis@0.7.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint-community/regexpp': 4.12.2
|
'@eslint-community/regexpp': 4.12.2
|
||||||
@@ -7454,6 +7866,98 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
jsesc: 3.1.0
|
jsesc: 3.1.0
|
||||||
|
|
||||||
|
rehype-attr@3.0.3:
|
||||||
|
dependencies:
|
||||||
|
unified: 11.0.5
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
|
rehype-autolink-headings@7.1.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
'@ungap/structured-clone': 1.3.0
|
||||||
|
hast-util-heading-rank: 3.0.0
|
||||||
|
hast-util-is-element: 3.0.0
|
||||||
|
unified: 11.0.5
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
|
rehype-ignore@2.0.3:
|
||||||
|
dependencies:
|
||||||
|
hast-util-select: 6.0.4
|
||||||
|
unified: 11.0.5
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
|
rehype-parse@9.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
hast-util-from-html: 2.0.3
|
||||||
|
unified: 11.0.5
|
||||||
|
|
||||||
|
rehype-prism-plus@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
hast-util-to-string: 3.0.1
|
||||||
|
parse-numeric-range: 1.3.0
|
||||||
|
refractor: 4.9.0
|
||||||
|
rehype-parse: 9.0.1
|
||||||
|
unist-util-filter: 5.0.1
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
|
rehype-prism-plus@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
hast-util-to-string: 3.0.1
|
||||||
|
parse-numeric-range: 1.3.0
|
||||||
|
refractor: 4.9.0
|
||||||
|
rehype-parse: 9.0.1
|
||||||
|
unist-util-filter: 5.0.1
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
|
rehype-raw@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
hast-util-raw: 9.1.0
|
||||||
|
vfile: 6.0.3
|
||||||
|
|
||||||
|
rehype-rewrite@4.0.4:
|
||||||
|
dependencies:
|
||||||
|
hast-util-select: 6.0.4
|
||||||
|
unified: 11.0.5
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
|
rehype-slug@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
github-slugger: 2.0.0
|
||||||
|
hast-util-heading-rank: 3.0.0
|
||||||
|
hast-util-to-string: 3.0.1
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
|
rehype-stringify@10.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
hast-util-to-html: 9.0.5
|
||||||
|
unified: 11.0.5
|
||||||
|
|
||||||
|
rehype@13.0.2:
|
||||||
|
dependencies:
|
||||||
|
'@types/hast': 3.0.4
|
||||||
|
rehype-parse: 9.0.1
|
||||||
|
rehype-stringify: 10.0.1
|
||||||
|
unified: 11.0.5
|
||||||
|
|
||||||
|
remark-gfm@4.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/mdast': 4.0.4
|
||||||
|
mdast-util-gfm: 3.1.0
|
||||||
|
micromark-extension-gfm: 3.0.0
|
||||||
|
remark-parse: 11.0.0
|
||||||
|
remark-stringify: 11.0.0
|
||||||
|
unified: 11.0.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
remark-github-blockquote-alert@1.3.1:
|
||||||
|
dependencies:
|
||||||
|
unist-util-visit: 5.0.0
|
||||||
|
|
||||||
remark-parse@11.0.0:
|
remark-parse@11.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/mdast': 4.0.4
|
'@types/mdast': 4.0.4
|
||||||
@@ -7471,6 +7975,12 @@ snapshots:
|
|||||||
unified: 11.0.5
|
unified: 11.0.5
|
||||||
vfile: 6.0.3
|
vfile: 6.0.3
|
||||||
|
|
||||||
|
remark-stringify@11.0.0:
|
||||||
|
dependencies:
|
||||||
|
'@types/mdast': 4.0.4
|
||||||
|
mdast-util-to-markdown: 2.1.2
|
||||||
|
unified: 11.0.5
|
||||||
|
|
||||||
require-directory@2.1.1: {}
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
require-main-filename@2.0.0: {}
|
require-main-filename@2.0.0: {}
|
||||||
@@ -7639,6 +8149,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@pkgr/core': 0.2.9
|
'@pkgr/core': 0.2.9
|
||||||
|
|
||||||
|
tagged-tag@1.0.0: {}
|
||||||
|
|
||||||
tailwind-merge@3.4.0: {}
|
tailwind-merge@3.4.0: {}
|
||||||
|
|
||||||
tailwindcss@4.1.18: {}
|
tailwindcss@4.1.18: {}
|
||||||
@@ -7699,6 +8211,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
prelude-ls: 1.2.1
|
prelude-ls: 1.2.1
|
||||||
|
|
||||||
|
type-fest@5.4.1:
|
||||||
|
dependencies:
|
||||||
|
tagged-tag: 1.0.0
|
||||||
|
|
||||||
typescript-eslint@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
typescript-eslint@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
@@ -7726,6 +8242,12 @@ snapshots:
|
|||||||
trough: 2.2.0
|
trough: 2.2.0
|
||||||
vfile: 6.0.3
|
vfile: 6.0.3
|
||||||
|
|
||||||
|
unist-util-filter@5.0.1:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
unist-util-is: 6.0.1
|
||||||
|
unist-util-visit-parents: 6.0.2
|
||||||
|
|
||||||
unist-util-is@6.0.1:
|
unist-util-is@6.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
@@ -7798,6 +8320,11 @@ snapshots:
|
|||||||
- '@types/react'
|
- '@types/react'
|
||||||
- '@types/react-dom'
|
- '@types/react-dom'
|
||||||
|
|
||||||
|
vfile-location@5.0.3:
|
||||||
|
dependencies:
|
||||||
|
'@types/unist': 3.0.3
|
||||||
|
vfile: 6.0.3
|
||||||
|
|
||||||
vfile-message@4.0.3:
|
vfile-message@4.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
@@ -7864,6 +8391,8 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
web-namespaces@2.0.1: {}
|
||||||
|
|
||||||
webpack-virtual-modules@0.6.2: {}
|
webpack-virtual-modules@0.6.2: {}
|
||||||
|
|
||||||
which-module@2.0.1: {}
|
which-module@2.0.1: {}
|
||||||
|
|||||||
@@ -19,18 +19,25 @@ import {
|
|||||||
import {
|
import {
|
||||||
Input,
|
Input,
|
||||||
} from '@/components/ui/input';
|
} from '@/components/ui/input';
|
||||||
|
import { useUpdateUser } from '@/hooks/data/useUpdateUser';
|
||||||
|
import { useUserInfo } from '@/hooks/data/useUserInfo';
|
||||||
|
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
email: z.string(),
|
username: z.string().min(5),
|
||||||
nickname: z.string().min(1),
|
nickname: z.string().min(1),
|
||||||
subtitle: z.string().min(1),
|
subtitle: z.string().min(1),
|
||||||
|
avatar: z.url().min(1),
|
||||||
});
|
});
|
||||||
export function EditProfileDialog() {
|
export function EditProfileDialog() {
|
||||||
|
const { data: user } = useUserInfo();
|
||||||
|
const { mutateAsync } = useUpdateUser();
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: '',
|
avatar: user.avatar,
|
||||||
nickname: '',
|
username: user.username,
|
||||||
subtitle: '',
|
nickname: user.nickname,
|
||||||
|
subtitle: user.subtitle,
|
||||||
},
|
},
|
||||||
validators: {
|
validators: {
|
||||||
onBlur: formSchema,
|
onBlur: formSchema,
|
||||||
@@ -39,13 +46,12 @@ export function EditProfileDialog() {
|
|||||||
value,
|
value,
|
||||||
}) => {
|
}) => {
|
||||||
try {
|
try {
|
||||||
toast(
|
await mutateAsync(value);
|
||||||
<code className="text-white">{JSON.stringify(value, null, 2)}</code>,
|
toast.success('个人资料更新成功');
|
||||||
);
|
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
console.error('Form submission error', error);
|
console.error('Form submission error', error);
|
||||||
toast.error('Failed to submit the form. Please try again.');
|
toast.error('更新个人资料失败,请重试');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -53,60 +59,94 @@ export function EditProfileDialog() {
|
|||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button variant="outline" className="w-full mt-4" size="lg">编辑个人资料</Button>
|
<Button variant="outline" className="w-full" size="lg">编辑个人资料</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<form
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
onSubmit={(e) => {
|
<form
|
||||||
e.preventDefault();
|
onSubmit={(e) => {
|
||||||
e.stopPropagation();
|
e.preventDefault();
|
||||||
void form.handleSubmit();
|
e.stopPropagation();
|
||||||
}}
|
void form.handleSubmit();
|
||||||
>
|
}}
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
className="grid gap-4"
|
||||||
|
>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>编辑个人资料</DialogTitle>
|
<DialogTitle>编辑个人资料</DialogTitle>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<Field>
|
<form.Field name="username">
|
||||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
{field => (
|
||||||
<Input
|
<Field>
|
||||||
id="email"
|
<FieldLabel htmlFor="username">用户名</FieldLabel>
|
||||||
name="email"
|
<Input
|
||||||
placeholder="noa@requiem.garden"
|
id="username"
|
||||||
value={form.getFieldValue('email')}
|
name="username"
|
||||||
onChange={e => form.setFieldValue('email', e.target.value)}
|
placeholder={user.username}
|
||||||
/>
|
value={field.state.value}
|
||||||
<FieldError />
|
onBlur={field.handleBlur}
|
||||||
</Field>
|
onChange={e => field.handleChange(e.target.value)}
|
||||||
<Field>
|
/>
|
||||||
<FieldLabel htmlFor="nickname">昵称</FieldLabel>
|
<FieldError errors={field.state.meta.errors} />
|
||||||
<Input
|
</Field>
|
||||||
id="nickname"
|
)}
|
||||||
name="nickname"
|
</form.Field>
|
||||||
placeholder="Noa Virellia"
|
<form.Field name="nickname">
|
||||||
value={form.getFieldValue('nickname')}
|
{field => (
|
||||||
onChange={e => form.setFieldValue('nickname', e.target.value)}
|
<Field>
|
||||||
/>
|
<FieldLabel htmlFor="nickname">昵称</FieldLabel>
|
||||||
<FieldError />
|
<Input
|
||||||
</Field>
|
id="nickname"
|
||||||
<Field>
|
name="nickname"
|
||||||
<FieldLabel htmlFor="subtitle">副标题</FieldLabel>
|
placeholder={user.nickname}
|
||||||
<Input
|
value={field.state.value}
|
||||||
id="subtitle"
|
onBlur={field.handleBlur}
|
||||||
name="subtitle"
|
onChange={e => field.handleChange(e.target.value)}
|
||||||
placeholder="天生骄傲"
|
/>
|
||||||
value={form.getFieldValue('subtitle')}
|
<FieldError errors={field.state.meta.errors} />
|
||||||
onChange={e => form.setFieldValue('subtitle', e.target.value)}
|
</Field>
|
||||||
/>
|
)}
|
||||||
<FieldError />
|
</form.Field>
|
||||||
</Field>
|
<form.Field name="subtitle">
|
||||||
|
{field => (
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="subtitle">副标题</FieldLabel>
|
||||||
|
<Input
|
||||||
|
id="subtitle"
|
||||||
|
name="subtitle"
|
||||||
|
placeholder={user.subtitle}
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
onChange={e => field.handleChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
<FieldError errors={field.state.meta.errors} />
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
<form.Field name="avatar">
|
||||||
|
{field => (
|
||||||
|
<Field>
|
||||||
|
<FieldLabel htmlFor="avatar">头像链接</FieldLabel>
|
||||||
|
<Input
|
||||||
|
id="avatar"
|
||||||
|
name="avatar"
|
||||||
|
placeholder={user.avatar}
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
onChange={e => field.handleChange(e.target.value)}
|
||||||
|
/>
|
||||||
|
<FieldError errors={field.state.meta.errors} />
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button variant="outline">取消</Button>
|
<Button variant="outline">取消</Button>
|
||||||
</DialogClose>
|
</DialogClose>
|
||||||
<Button type="submit">保存</Button>
|
<DialogClose asChild>
|
||||||
|
<Button type="submit">保存</Button>
|
||||||
|
</DialogClose>
|
||||||
</DialogFooter>
|
</DialogFooter>
|
||||||
</DialogContent>
|
</form>
|
||||||
</form>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
import {
|
|
||||||
useForm,
|
|
||||||
} from '@tanstack/react-form';
|
|
||||||
import {
|
|
||||||
toast,
|
|
||||||
} from 'sonner';
|
|
||||||
import {
|
|
||||||
z,
|
|
||||||
} from 'zod';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
} from '@/components/ui/button';
|
|
||||||
import {
|
|
||||||
Field,
|
|
||||||
FieldError,
|
|
||||||
FieldLabel,
|
|
||||||
} from '@/components/ui/field';
|
|
||||||
import {
|
|
||||||
Input,
|
|
||||||
} from '@/components/ui/input';
|
|
||||||
|
|
||||||
const formSchema = z.object({
|
|
||||||
email: z.string(),
|
|
||||||
nickname: z.string().min(1),
|
|
||||||
subtitle: z.string().min(1),
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function EditProfileForm() {
|
|
||||||
const form = useForm({
|
|
||||||
defaultValues: {
|
|
||||||
email: '',
|
|
||||||
nickname: '',
|
|
||||||
subtitle: '',
|
|
||||||
},
|
|
||||||
validators: {
|
|
||||||
onBlur: formSchema,
|
|
||||||
},
|
|
||||||
onSubmit: async ({
|
|
||||||
value,
|
|
||||||
}) => {
|
|
||||||
try {
|
|
||||||
toast(
|
|
||||||
<code className="text-white">{JSON.stringify(value, null, 2)}</code>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
console.error('Form submission error', error);
|
|
||||||
toast.error('Failed to submit the form. Please try again.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form
|
|
||||||
onSubmit={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
void form.handleSubmit();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel htmlFor="email">Email</FieldLabel>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="noa@requiem.garden"
|
|
||||||
|
|
||||||
value={form.getFieldValue('email')}
|
|
||||||
onChange={e => form.setFieldValue('email', e.target.value)}
|
|
||||||
/>
|
|
||||||
<FieldError />
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel htmlFor="nickname">昵称</FieldLabel>
|
|
||||||
<Input
|
|
||||||
id="nickname"
|
|
||||||
name="nickname"
|
|
||||||
placeholder="Noa Virellia"
|
|
||||||
|
|
||||||
value={form.getFieldValue('nickname')}
|
|
||||||
onChange={e => form.setFieldValue('nickname', e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FieldError />
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel htmlFor="subtitle">副标题</FieldLabel>
|
|
||||||
<Input
|
|
||||||
id="subtitle"
|
|
||||||
name="subtitle"
|
|
||||||
placeholder="天生骄傲"
|
|
||||||
|
|
||||||
value={form.getFieldValue('subtitle')}
|
|
||||||
onChange={e => form.setFieldValue('subtitle', e.target.value)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<FieldError />
|
|
||||||
</Field>
|
|
||||||
<Button type="submit">提交</Button>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,34 +1,81 @@
|
|||||||
import { Mail } from 'lucide-react';
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
|
import { isNil } from 'lodash-es';
|
||||||
|
import { Mail, Pencil } from 'lucide-react';
|
||||||
|
import { useState } from 'react';
|
||||||
import Markdown from 'react-markdown';
|
import Markdown from 'react-markdown';
|
||||||
|
import { toast } from 'sonner';
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
|
import { useUpdateUser } from '@/hooks/data/useUpdateUser';
|
||||||
import { useUserInfo } from '@/hooks/data/useUserInfo';
|
import { useUserInfo } from '@/hooks/data/useUserInfo';
|
||||||
import { base64ToUtf8 } from '@/lib/utils';
|
import { base64ToUtf8, utf8ToBase64 } from '@/lib/utils';
|
||||||
|
import { Button } from '../ui/button';
|
||||||
import { EditProfileDialog } from './edit-profile-dialog';
|
import { EditProfileDialog } from './edit-profile-dialog';
|
||||||
|
|
||||||
export function MainProfile() {
|
export function MainProfile() {
|
||||||
const { data: user } = useUserInfo();
|
const { data: user } = useUserInfo();
|
||||||
|
const [bio, setBio] = useState<string | undefined>(() => base64ToUtf8(user.bio));
|
||||||
|
const [enableBioEdit, setEnableBioEdit] = useState(false);
|
||||||
|
const { mutateAsync } = useUpdateUser();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full">
|
<div className="flex flex-col justify-center w-full lg:w-auto h-full lg:h-auto lg:flex-row lg:gap-8">
|
||||||
<div className="flex w-full flex-row gap-4 mt-2">
|
<div className="flex w-full flex-row mt-2 lg:mt-0 lg:flex-col lg:w-max">
|
||||||
<Avatar className="size-16 rounded-full border-2 border-muted">
|
<div className="flex flex-col w-full gap-3">
|
||||||
<AvatarImage src={user.avatar} alt={user.nickname} />
|
<div className="flex flex-col w-full gap-3">
|
||||||
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
<div className="flex flex-row gap-3 w-full lg:flex-col">
|
||||||
</Avatar>
|
<Avatar className="size-16 rounded-full border-2 border-muted lg:size-64">
|
||||||
<div className="flex flex-1 flex-col justify-center">
|
<AvatarImage src={user.avatar} alt={user.nickname} />
|
||||||
<span className="font-semibold text-2xl" aria-hidden="true">{user.nickname}</span>
|
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
|
||||||
<span className="text-[20px] text-muted-foreground" aria-hidden="true">{user.subtitle}</span>
|
</Avatar>
|
||||||
|
<div className="flex flex-1 flex-col justify-center lg:mt-3">
|
||||||
|
<span className="font-semibold text-2xl" aria-hidden="true">{user.nickname}</span>
|
||||||
|
<span className="text-[20px] text-muted-foreground" aria-hidden="true">{user.subtitle}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row gap-2 items-center text-sm px-1 lg:px-0">
|
||||||
|
<Mail className="h-4 w-4 stroke-muted-foreground" />
|
||||||
|
{user.email}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<EditProfileDialog />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<EditProfileDialog />
|
<section className="relative rounded-md border border-muted w-full flex-1 lg:flex-auto min-h-72 lg:h-full mt-4 lg:mt-0 prose dark:prose-invert max-w-[1012px] self-center">
|
||||||
<section className="px-2 mt-4">
|
|
||||||
<div className="flex flex-row gap-2 items-center text-sm">
|
|
||||||
<Mail className="h-4 w-4 stroke-muted-foreground" />
|
|
||||||
{user.email}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="rounded-md border border-muted w-full min-h-72 mt-4 p-6 prose dark:prose-invert max-w-[1012px] self-center">
|
|
||||||
{/* Bio */}
|
{/* Bio */}
|
||||||
<Markdown>{base64ToUtf8(user.bio)}</Markdown>
|
{enableBioEdit
|
||||||
|
? (
|
||||||
|
<MDEditor
|
||||||
|
value={bio}
|
||||||
|
onChange={setBio}
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: <div className="p-6 prose dark:prose-invert"><Markdown>{bio}</Markdown></div>}
|
||||||
|
<Button
|
||||||
|
className="absolute bottom-4 right-4"
|
||||||
|
// eslint-disable-next-line ts/no-misused-promises
|
||||||
|
onClick={async () => {
|
||||||
|
if (!enableBioEdit) {
|
||||||
|
setEnableBioEdit(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (!isNil(bio)) {
|
||||||
|
try {
|
||||||
|
await mutateAsync({ bio: utf8ToBase64(bio) });
|
||||||
|
setEnableBioEdit(false);
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
toast.error('个人简介更新失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
size="icon-sm"
|
||||||
|
variant={enableBioEdit ? 'default' : 'outline'}
|
||||||
|
>
|
||||||
|
<Pencil />
|
||||||
|
</Button>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
22
client/cms/src/hooks/data/useUpdateUser.ts
Normal file
22
client/cms/src/hooks/data/useUpdateUser.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import { axiosClient } from '@/lib/axios';
|
||||||
|
|
||||||
|
interface UpdateUserPayload {
|
||||||
|
avatar?: string;
|
||||||
|
bio?: string;
|
||||||
|
nickname?: string;
|
||||||
|
subtitle?: string;
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useUpdateUser() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async (payload: UpdateUserPayload) => {
|
||||||
|
return axiosClient.patch<{ status: string }>('/user/update', payload);
|
||||||
|
},
|
||||||
|
onSuccess: async () => {
|
||||||
|
await queryClient.invalidateQueries({ queryKey: ['userInfo'] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ export function useUserInfo() {
|
|||||||
queryKey: ['userInfo'],
|
queryKey: ['userInfo'],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const response = await axiosClient.get<{
|
const response = await axiosClient.get<{
|
||||||
|
username: string;
|
||||||
user_id: string;
|
user_id: string;
|
||||||
email: string;
|
email: string;
|
||||||
type: string;
|
type: string;
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
import type { AxiosRequestConfig } from 'axios';
|
import type { AxiosError, AxiosRequestConfig } from 'axios';
|
||||||
import axios, { AxiosError } from 'axios';
|
import type { JsonValue } from 'type-fest';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { isNil } from 'lodash-es';
|
||||||
import { router } from '@/lib/router';
|
import { router } from '@/lib/router';
|
||||||
import { doRefreshToken, getRefreshToken, getToken, setRefreshToken, setToken } from './token';
|
import { clearTokens, doRefreshToken, getRefreshToken, getToken, setRefreshToken, setToken } from './token';
|
||||||
|
|
||||||
|
export const HEADER_API_VERSION = {
|
||||||
|
'X-Api-Version': 'latest',
|
||||||
|
};
|
||||||
|
|
||||||
export const axiosClient = axios.create({
|
export const axiosClient = axios.create({
|
||||||
baseURL: '/api/v1/',
|
baseURL: '/api/v1/',
|
||||||
|
headers: HEADER_API_VERSION,
|
||||||
});
|
});
|
||||||
|
|
||||||
axiosClient.interceptors.request.use((config) => {
|
axiosClient.interceptors.request.use((config) => {
|
||||||
@@ -18,27 +25,43 @@ axiosClient.interceptors.request.use((config) => {
|
|||||||
|
|
||||||
type RetryConfig = AxiosRequestConfig & { _retry?: boolean };
|
type RetryConfig = AxiosRequestConfig & { _retry?: boolean };
|
||||||
|
|
||||||
axiosClient.interceptors.response.use(undefined, async (error: AxiosError) => {
|
interface ResponseData {
|
||||||
|
code: number;
|
||||||
|
error_id: string;
|
||||||
|
status: string;
|
||||||
|
data: JsonValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
axiosClient.interceptors.response.use(async (response) => {
|
||||||
|
const data = response.data as ResponseData;
|
||||||
|
if (data.code !== 200) {
|
||||||
|
return Promise.reject(data);
|
||||||
|
}
|
||||||
|
response.data = data.data;
|
||||||
|
return response;
|
||||||
|
}, async (error: AxiosError) => {
|
||||||
const originalRequest = error.config as RetryConfig | undefined;
|
const originalRequest = error.config as RetryConfig | undefined;
|
||||||
if (!error.response || error.response.status !== 401 || !originalRequest) {
|
if (!error.response || error.response.status !== 401 || !originalRequest) {
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
|
if (error.response.status === 401 && originalRequest.url !== '/auth/refresh' && !isNil(getRefreshToken())) {
|
||||||
if (error.response.status === 401 && getRefreshToken() !== null) {
|
|
||||||
try {
|
try {
|
||||||
const maybeRefreshTokenValue = await doRefreshToken();
|
const maybeRefreshTokenResponse = await doRefreshToken();
|
||||||
const { access_token, refresh_token } = maybeRefreshTokenValue.data;
|
if (maybeRefreshTokenResponse.status !== 200) {
|
||||||
|
throw new Error('Failed to refresh token');
|
||||||
|
}
|
||||||
|
const { access_token, refresh_token } = maybeRefreshTokenResponse.data;
|
||||||
originalRequest.headers = originalRequest.headers ?? {};
|
originalRequest.headers = originalRequest.headers ?? {};
|
||||||
originalRequest.headers.Authorization = `Bearer ${access_token}`;
|
originalRequest.headers.Authorization = `Bearer ${access_token}`;
|
||||||
setToken(access_token);
|
setToken(access_token);
|
||||||
setRefreshToken(refresh_token);
|
setRefreshToken(refresh_token);
|
||||||
return await axiosClient(originalRequest);
|
return await axiosClient(originalRequest);
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line unused-imports/no-unused-vars
|
||||||
catch (e) {
|
catch (e) {
|
||||||
if (e instanceof AxiosError && e.status === 401) {
|
// Should remove token (tokens are out of date)
|
||||||
await router.navigate({ to: '/authorize' });
|
clearTokens();
|
||||||
return Promise.reject(error);
|
await router.navigate({ to: '/authorize' });
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import axios from 'axios';
|
import { axiosClient, HEADER_API_VERSION } from './axios';
|
||||||
|
|
||||||
export function setToken(token: string) {
|
export function setToken(token: string) {
|
||||||
localStorage.setItem('token', token);
|
localStorage.setItem('token', token);
|
||||||
@@ -30,11 +30,17 @@ export function clearTokens() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function doSetTokenByCode(code: string) {
|
export async function doSetTokenByCode(code: string) {
|
||||||
const { data } = await axios.post<{ access_token: string; refresh_token: string }>('/api/v1/auth/token', { code });
|
return new Promise<void>((resolve, reject) => {
|
||||||
setToken(data.access_token);
|
axiosClient.post<{ access_token: string; refresh_token: string }>('/auth/token', { code }, { headers: HEADER_API_VERSION }).then(({ data }) => {
|
||||||
setRefreshToken(data.refresh_token);
|
setToken(data.access_token);
|
||||||
|
setRefreshToken(data.refresh_token);
|
||||||
|
resolve();
|
||||||
|
}).catch((error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function doRefreshToken() {
|
export async function doRefreshToken() {
|
||||||
return axios.post<{ access_token: string; refresh_token: string }>('/api/v1/auth/refresh', { refresh_token: getRefreshToken() });
|
return axiosClient.post<{ access_token: string; refresh_token: string }>('/auth/refresh', { refresh_token: getRefreshToken() }, { headers: HEADER_API_VERSION });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const Route = createFileRoute('/_sidebarLayout/profile')({
|
|||||||
|
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-h-[560px] flex-col gap-6 px-4 py-6">
|
<div className="flex h-full flex-col gap-6 px-4 py-6">
|
||||||
<MainProfile />
|
<MainProfile />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { createFileRoute } from '@tanstack/react-router';
|
import { createFileRoute } from '@tanstack/react-router';
|
||||||
import { zodValidator } from '@tanstack/zod-adapter';
|
import { zodValidator } from '@tanstack/zod-adapter';
|
||||||
|
import { isNil } from 'lodash-es';
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
import { LoginForm } from '@/components/login-form';
|
import { LoginForm } from '@/components/login-form';
|
||||||
|
import { axiosClient } from '@/lib/axios';
|
||||||
import { generateOAuthState } from '@/lib/random';
|
import { generateOAuthState } from '@/lib/random';
|
||||||
import { getToken } from '@/lib/token';
|
import { getToken } from '@/lib/token';
|
||||||
|
|
||||||
@@ -22,15 +24,21 @@ export const Route = createFileRoute('/authorize')({
|
|||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const token = getToken();
|
const token = getToken();
|
||||||
const oauthParams = Route.useSearch();
|
const oauthParams = Route.useSearch();
|
||||||
if (token !== null) {
|
/**
|
||||||
const base = new URL(window.location.origin);
|
* Auth by Token Flow
|
||||||
const url = new URL('/api/v1/auth/redirect', base);
|
*/
|
||||||
url.searchParams.set('client_id', oauthParams.client_id);
|
if (!isNil(token)) {
|
||||||
url.searchParams.set('response_type', oauthParams.response_type);
|
axiosClient.post<{ redirect_uri: string }>('/auth/exchange', {
|
||||||
url.searchParams.set('redirect_uri', oauthParams.redirect_uri);
|
client_id: oauthParams.client_id,
|
||||||
url.searchParams.set('state', oauthParams.state);
|
redirect_uri: oauthParams.redirect_uri,
|
||||||
window.location.href = url.toString();
|
state: oauthParams.state,
|
||||||
return null;
|
}).then((res) => {
|
||||||
|
window.location.href = res.data.redirect_uri;
|
||||||
|
}).catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
return 'Token exchange failed';
|
||||||
|
});
|
||||||
|
return 'Redirecting';
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className="bg-background flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
<div className="bg-background flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
import { createFileRoute, useNavigate } from '@tanstack/react-router';
|
||||||
import { useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
import { doSetTokenByCode } from '@/lib/token';
|
import { doSetTokenByCode } from '@/lib/token';
|
||||||
|
|
||||||
@@ -16,10 +16,15 @@ function RouteComponent() {
|
|||||||
const { code } = Route.useSearch();
|
const { code } = Route.useSearch();
|
||||||
const [status, setStatus] = useState('Loading...');
|
const [status, setStatus] = useState('Loading...');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
doSetTokenByCode(code).then(() => {
|
|
||||||
void navigate({ to: '/' });
|
useEffect(() => {
|
||||||
}).catch((_) => {
|
doSetTokenByCode(code).then(() => {
|
||||||
setStatus('Error getting token');
|
void navigate({ to: '/' });
|
||||||
});
|
}).catch((_) => {
|
||||||
|
setStatus('Error getting token');
|
||||||
|
});
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
return <div>{status}</div>;
|
return <div>{status}</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import path from "node:path";
|
import path from 'node:path';
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
import { tanstackRouter } from "@tanstack/router-plugin/vite";
|
import { tanstackRouter } from '@tanstack/router-plugin/vite';
|
||||||
import react from "@vitejs/plugin-react";
|
import react from '@vitejs/plugin-react';
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from 'vite';
|
||||||
import svgr from "vite-plugin-svgr";
|
import svgr from 'vite-plugin-svgr';
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
tanstackRouter({
|
tanstackRouter({
|
||||||
target: "react",
|
target: 'react',
|
||||||
autoCodeSplitting: true,
|
autoCodeSplitting: true,
|
||||||
}),
|
}),
|
||||||
react(),
|
react(),
|
||||||
@@ -18,15 +18,15 @@ export default defineConfig({
|
|||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": path.resolve(__dirname, "./src"),
|
'@': path.resolve(__dirname, './src'),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {
|
||||||
"/api": "http://10.0.0.250:8000",
|
'/api': 'http://10.0.0.10:8000',
|
||||||
},
|
},
|
||||||
host: "0.0.0.0",
|
host: '0.0.0.0',
|
||||||
port: 5173,
|
port: 5173,
|
||||||
allowedHosts: ["nix.org.cn", "nixos.party"],
|
allowedHosts: ['nix.org.cn', 'nixos.party'],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
11
cmd/gen_exception/definitions/common.yaml
Normal file
11
cmd/gen_exception/definitions/common.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
common:
|
||||||
|
success: "00000"
|
||||||
|
error:
|
||||||
|
invalid_input: "00001"
|
||||||
|
unauthorized: "00002"
|
||||||
|
internal: "00003"
|
||||||
|
permission_denied: "00004"
|
||||||
|
uuid_parse_failed: "00005"
|
||||||
|
database: "00006"
|
||||||
|
missing_user_id: "00007"
|
||||||
|
user_not_found: "00008"
|
||||||
23
cmd/gen_exception/definitions/endpoint.yaml
Normal file
23
cmd/gen_exception/definitions/endpoint.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
endpoint:
|
||||||
|
auth:
|
||||||
|
service:
|
||||||
|
redirect: "01"
|
||||||
|
magic: "02"
|
||||||
|
token: "03"
|
||||||
|
refresh: "04"
|
||||||
|
exchange: "05"
|
||||||
|
event:
|
||||||
|
service:
|
||||||
|
info: "01"
|
||||||
|
checkin: "02"
|
||||||
|
checkin_query: "03"
|
||||||
|
checkin_submit: "04"
|
||||||
|
user:
|
||||||
|
service:
|
||||||
|
info: "01"
|
||||||
|
update: "02"
|
||||||
|
list: "03"
|
||||||
|
full: "04"
|
||||||
|
create: "05"
|
||||||
|
middleware:
|
||||||
|
service: "01"
|
||||||
6
cmd/gen_exception/definitions/middleware.yaml
Normal file
6
cmd/gen_exception/definitions/middleware.yaml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
middleware:
|
||||||
|
service:
|
||||||
|
gin_logger: "901"
|
||||||
|
jwt: "902"
|
||||||
|
permission: "903"
|
||||||
|
api_version: "904"
|
||||||
4
cmd/gen_exception/definitions/service.yaml
Normal file
4
cmd/gen_exception/definitions/service.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
service:
|
||||||
|
auth: "001"
|
||||||
|
user: "002"
|
||||||
|
event: "003"
|
||||||
34
cmd/gen_exception/definitions/specific.yaml
Normal file
34
cmd/gen_exception/definitions/specific.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
api:
|
||||||
|
version:
|
||||||
|
not_found: "00001"
|
||||||
|
auth:
|
||||||
|
redirect:
|
||||||
|
token_invalid: "00001"
|
||||||
|
client_not_found: "00002"
|
||||||
|
uri_mismatch: "00003"
|
||||||
|
invalid_uri: "00004"
|
||||||
|
magic:
|
||||||
|
turnstile_failed: "00001"
|
||||||
|
code_gen_failed: "00002"
|
||||||
|
invalid_external_url: "00003"
|
||||||
|
invalid_email_config: "00004"
|
||||||
|
token:
|
||||||
|
invalid_token: "00001"
|
||||||
|
gen_failed: "00002"
|
||||||
|
refresh:
|
||||||
|
invalid_token: "00001"
|
||||||
|
renew_failed: "00002"
|
||||||
|
exchange:
|
||||||
|
get_user_id_failed: "00001"
|
||||||
|
code_gen_failed: "00002"
|
||||||
|
invalid_redirect_uri: "00003"
|
||||||
|
user:
|
||||||
|
list:
|
||||||
|
meilisearch_failed: "00001"
|
||||||
|
event:
|
||||||
|
info:
|
||||||
|
not_found: "00001"
|
||||||
|
checkin:
|
||||||
|
gen_code_failed: "00001"
|
||||||
|
checkin_query:
|
||||||
|
record_not_found: "00001"
|
||||||
5
cmd/gen_exception/definitions/status.yaml
Normal file
5
cmd/gen_exception/definitions/status.yaml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
status:
|
||||||
|
success: "2"
|
||||||
|
user: "4"
|
||||||
|
server: "5"
|
||||||
|
client: "6"
|
||||||
3
cmd/gen_exception/definitions/types.yaml
Normal file
3
cmd/gen_exception/definitions/types.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
type:
|
||||||
|
common: "1"
|
||||||
|
specific: "0"
|
||||||
8
cmd/gen_exception/exception.tmpl
Normal file
8
cmd/gen_exception/exception.tmpl
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
// Code generated by gen-exception; DO NOT EDIT.
|
||||||
|
package exception
|
||||||
|
|
||||||
|
const (
|
||||||
|
{{- range .Items }}
|
||||||
|
{{ .Name }} = "{{ .Value }}"
|
||||||
|
{{- end }}
|
||||||
|
)
|
||||||
95
cmd/gen_exception/main.go
Normal file
95
cmd/gen_exception/main.go
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/language"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ErrorItem struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}
|
||||||
|
|
||||||
|
type TplData struct {
|
||||||
|
Items []ErrorItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func toCamel(s string) string {
|
||||||
|
caser := cases.Title(language.English)
|
||||||
|
s = strings.ReplaceAll(s, "-", "_")
|
||||||
|
parts := strings.Split(s, "_")
|
||||||
|
for i := range parts {
|
||||||
|
parts[i] = caser.String(parts[i])
|
||||||
|
}
|
||||||
|
return strings.Join(parts, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func recursiveParse(prefix string, raw any, items *[]ErrorItem) {
|
||||||
|
switch v := raw.(type) {
|
||||||
|
case map[string]any:
|
||||||
|
for key, val := range v {
|
||||||
|
recursiveParse(prefix+toCamel(key), val, items)
|
||||||
|
}
|
||||||
|
case string:
|
||||||
|
*items = append(*items, ErrorItem{
|
||||||
|
Name: prefix,
|
||||||
|
Value: v,
|
||||||
|
})
|
||||||
|
case int, int64:
|
||||||
|
*items = append(*items, ErrorItem{
|
||||||
|
Name: prefix,
|
||||||
|
Value: fmt.Sprintf("%v", v),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
yamlDir := "cmd/gen_exception/definitions"
|
||||||
|
outputDir := "internal/exception"
|
||||||
|
tplPath := "cmd/gen_exception/exception.tmpl"
|
||||||
|
|
||||||
|
if _, err := os.Stat(tplPath); os.IsNotExist(err) {
|
||||||
|
log.Fatalf("Cannot found tmpl %s", tplPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
funcMap := template.FuncMap{"ToCamel": toCamel}
|
||||||
|
tmpl := template.Must(template.New("exception.tmpl").Funcs(funcMap).ParseFiles(tplPath))
|
||||||
|
|
||||||
|
os.MkdirAll(outputDir, 0755)
|
||||||
|
|
||||||
|
files, _ := filepath.Glob(filepath.Join(yamlDir, "*.yaml"))
|
||||||
|
for _, yamlFile := range files {
|
||||||
|
content, err := os.ReadFile(yamlFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Read file error: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawData any
|
||||||
|
if err := yaml.Unmarshal(content, &rawData); err != nil {
|
||||||
|
log.Printf("Unmarshal error in %s: %v", yamlFile, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []ErrorItem
|
||||||
|
recursiveParse("", rawData, &items)
|
||||||
|
|
||||||
|
baseName := strings.TrimSuffix(filepath.Base(yamlFile), filepath.Ext(yamlFile))
|
||||||
|
outputFileName := baseName + "_gen.go"
|
||||||
|
outputPath := filepath.Join(outputDir, outputFileName)
|
||||||
|
|
||||||
|
f, _ := os.Create(outputPath)
|
||||||
|
tmpl.Execute(f, TplData{Items: items})
|
||||||
|
f.Close()
|
||||||
|
|
||||||
|
fmt.Printf("Generated: %s (%d constants)\n", outputPath, len(items))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,22 +3,26 @@ server:
|
|||||||
address: :8000
|
address: :8000
|
||||||
external_url: https://example.com
|
external_url: https://example.com
|
||||||
debug_mode: false
|
debug_mode: false
|
||||||
file_logger: false
|
log_level: debug
|
||||||
|
service_name: nixcn-cms-backend
|
||||||
database:
|
database:
|
||||||
type: postgres
|
type: postgres
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
name: postgres
|
name: postgres
|
||||||
username: postgres
|
username: postgres
|
||||||
password: postgres
|
password: postgres
|
||||||
|
service_name: nixcn-cms-postgres
|
||||||
cache:
|
cache:
|
||||||
hosts: ["127.0.0.1:6379"]
|
hosts: ["127.0.0.1:6379"]
|
||||||
master: ""
|
master: ""
|
||||||
username: ""
|
username: ""
|
||||||
password: ""
|
password: ""
|
||||||
db: 0
|
db: 0
|
||||||
|
service_name: nixcn-cms-redis
|
||||||
search:
|
search:
|
||||||
host: 127.0.0.1
|
host: 127.0.0.1
|
||||||
api_key: ""
|
api_key: ""
|
||||||
|
service_name: nixcn-cms-meilisearch
|
||||||
email:
|
email:
|
||||||
host:
|
host:
|
||||||
port:
|
port:
|
||||||
@@ -26,13 +30,6 @@ email:
|
|||||||
password:
|
password:
|
||||||
security:
|
security:
|
||||||
insecure_skip_verify:
|
insecure_skip_verify:
|
||||||
from:
|
|
||||||
auth:
|
|
||||||
oauth2:
|
|
||||||
tenant_id:
|
|
||||||
client_id:
|
|
||||||
client_secret:
|
|
||||||
scope:
|
|
||||||
secrets:
|
secrets:
|
||||||
turnstile_secret: example
|
turnstile_secret: example
|
||||||
client_secret_key: aes_32_byte_string
|
client_secret_key: aes_32_byte_string
|
||||||
@@ -45,3 +42,5 @@ ttl:
|
|||||||
kyc:
|
kyc:
|
||||||
ali_access_key_id: example
|
ali_access_key_id: example
|
||||||
ali_access_key_secret: example
|
ali_access_key_secret: example
|
||||||
|
tracer:
|
||||||
|
otel_controller_endpoint: localhost:4317
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ func Init() {
|
|||||||
conf := &config{}
|
conf := &config{}
|
||||||
if err := viper.ReadInConfig(); err != nil {
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
// Dont generate config when using dev mode
|
// Dont generate config when using dev mode
|
||||||
log.Fatalln("Can't read config!")
|
log.Fatalln("[Config] Can't read config!")
|
||||||
}
|
}
|
||||||
if err := viper.Unmarshal(conf); err != nil {
|
if err := viper.Unmarshal(conf); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatalln("[Condig] Can't unmarshal config!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type config struct {
|
|||||||
Secrets secrets `yaml:"secrets"`
|
Secrets secrets `yaml:"secrets"`
|
||||||
TTL ttl `yaml:"ttl"`
|
TTL ttl `yaml:"ttl"`
|
||||||
KYC kyc `yaml:"kyc"`
|
KYC kyc `yaml:"kyc"`
|
||||||
|
Tracer tracer `yaml:"tracer"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type server struct {
|
type server struct {
|
||||||
@@ -16,47 +17,41 @@ type server struct {
|
|||||||
Address string `yaml:"address"`
|
Address string `yaml:"address"`
|
||||||
ExternalUrl string `yaml:"external_url"`
|
ExternalUrl string `yaml:"external_url"`
|
||||||
DebugMode string `yaml:"debug_mode"`
|
DebugMode string `yaml:"debug_mode"`
|
||||||
FileLogger string `yaml:"file_logger"`
|
LogLevel string `yaml:"log_level"`
|
||||||
|
ServiceName string `yaml:"service_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type database struct {
|
type database struct {
|
||||||
Type string `yaml:"type"`
|
Type string `yaml:"type"`
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
|
ServiceName string `yaml:"service_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type cache struct {
|
type cache struct {
|
||||||
Hosts []string `yaml:"hosts"`
|
Hosts []string `yaml:"hosts"`
|
||||||
Master string `yaml:"master"`
|
Master string `yaml:"master"`
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
Password string `yaml:"passowrd"`
|
Password string `yaml:"passowrd"`
|
||||||
DB int `yaml:"db"`
|
DB int `yaml:"db"`
|
||||||
|
ServiceName string `yaml:"service_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type search struct {
|
type search struct {
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
ApiKey string `yaml:"api_key"`
|
ApiKey string `yaml:"api_key"`
|
||||||
}
|
ServiceName string `yaml:"service_name"`
|
||||||
|
|
||||||
type _email_oauth2 struct {
|
|
||||||
Tenantid string `yaml:"tenant_id"`
|
|
||||||
ClientId string `yaml:"client_id"`
|
|
||||||
ClientSecret string `yaml:"client_secret"`
|
|
||||||
Scope string `yaml:"scope"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type email struct {
|
type email struct {
|
||||||
Host string `yaml:"host"`
|
Host string `yaml:"host"`
|
||||||
Port string `yaml:"port"`
|
Port string `yaml:"port"`
|
||||||
Username string `yaml:"username"`
|
Username string `yaml:"username"`
|
||||||
Password string `yaml:"password"`
|
Password string `yaml:"password"`
|
||||||
Security string `yaml:"security"`
|
Security string `yaml:"security"`
|
||||||
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
|
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
|
||||||
From string `yaml:"from"`
|
|
||||||
Auth string `yaml:"auth"`
|
|
||||||
Oauth2 _email_oauth2 `yaml:"oauth2"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type secrets struct {
|
type secrets struct {
|
||||||
@@ -76,3 +71,7 @@ type kyc struct {
|
|||||||
AliAccessKeyId string `yaml:"ali_access_key_id"`
|
AliAccessKeyId string `yaml:"ali_access_key_id"`
|
||||||
AliAccessKeySecret string `yaml:"ali_access_key_secret"`
|
AliAccessKeySecret string `yaml:"ali_access_key_secret"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tracer struct {
|
||||||
|
OtelControllerEndpoint string `yaml:"otel_controller_endpoint"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,10 +34,10 @@ type AttendanceSearchDoc struct {
|
|||||||
CheckinAt time.Time `json:"checkin_at"`
|
CheckinAt time.Time `json:"checkin_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) GetAttendance(userId, eventId uuid.UUID) (*Attendance, error) {
|
func (self *Attendance) GetAttendance(ctx context.Context, userId, eventId uuid.UUID) (*Attendance, error) {
|
||||||
var checkin Attendance
|
var checkin Attendance
|
||||||
|
|
||||||
err := Database.
|
err := Database.WithContext(ctx).
|
||||||
Where("user_id = ? AND event_id = ?", userId, eventId).
|
Where("user_id = ? AND event_id = ?", userId, eventId).
|
||||||
First(&checkin).Error
|
First(&checkin).Error
|
||||||
|
|
||||||
@@ -57,10 +57,10 @@ type AttendanceUsers struct {
|
|||||||
CheckinAt time.Time `json:"checkin_at"`
|
CheckinAt time.Time `json:"checkin_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) GetUsersByEventID(eventID uuid.UUID) (*[]AttendanceUsers, error) {
|
func (self *Attendance) GetUsersByEventID(ctx context.Context, eventID uuid.UUID) (*[]AttendanceUsers, error) {
|
||||||
var result []AttendanceUsers
|
var result []AttendanceUsers
|
||||||
|
|
||||||
err := Database.
|
err := Database.WithContext(ctx).
|
||||||
Model(&Attendance{}).
|
Model(&Attendance{}).
|
||||||
Select("user_id, checkin_at").
|
Select("user_id, checkin_at").
|
||||||
Where("event_id = ?", eventID).
|
Where("event_id = ?", eventID).
|
||||||
@@ -75,10 +75,10 @@ type AttendanceEvent struct {
|
|||||||
CheckinAt time.Time `json:"checkin_at"`
|
CheckinAt time.Time `json:"checkin_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) GetEventsByUserID(userID uuid.UUID) (*[]AttendanceEvent, error) {
|
func (self *Attendance) GetEventsByUserID(ctx context.Context, userID uuid.UUID) (*[]AttendanceEvent, error) {
|
||||||
var result []AttendanceEvent
|
var result []AttendanceEvent
|
||||||
|
|
||||||
err := Database.
|
err := Database.WithContext(ctx).
|
||||||
Model(&Attendance{}).
|
Model(&Attendance{}).
|
||||||
Select("event_id, checkin_at").
|
Select("event_id, checkin_at").
|
||||||
Where("user_id = ?", userID).
|
Where("user_id = ?", userID).
|
||||||
@@ -88,12 +88,12 @@ func (self *Attendance) GetEventsByUserID(userID uuid.UUID) (*[]AttendanceEvent,
|
|||||||
return &result, err
|
return &result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) Create() error {
|
func (self *Attendance) Create(ctx context.Context) error {
|
||||||
self.UUID = uuid.New()
|
self.UUID = uuid.New()
|
||||||
self.AttendanceId = uuid.New()
|
self.AttendanceId = uuid.New()
|
||||||
|
|
||||||
// DB transaction for strong consistency
|
// DB transaction for strong consistency
|
||||||
err := Database.Transaction(func(tx *gorm.DB) error {
|
err := Database.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if err := tx.Create(&self).Error; err != nil {
|
if err := tx.Create(&self).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -103,17 +103,17 @@ func (self *Attendance) Create() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := self.UpdateSearchIndex(); err != nil {
|
if err := self.UpdateSearchIndex(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) Update(attendanceId uuid.UUID, checkinTime *time.Time) (*Attendance, error) {
|
func (self *Attendance) Update(ctx context.Context, attendanceId uuid.UUID, checkinTime *time.Time) (*Attendance, error) {
|
||||||
var attendance Attendance
|
var attendance Attendance
|
||||||
|
|
||||||
err := Database.Transaction(func(tx *gorm.DB) error {
|
err := Database.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
// Lock the row for update
|
// Lock the row for update
|
||||||
if err := tx.
|
if err := tx.
|
||||||
Where("attendance_id = ?", attendanceId).
|
Where("attendance_id = ?", attendanceId).
|
||||||
@@ -148,32 +148,32 @@ func (self *Attendance) Update(attendanceId uuid.UUID, checkinTime *time.Time) (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sync to MeiliSearch (eventual consistency)
|
// Sync to MeiliSearch (eventual consistency)
|
||||||
if err := attendance.UpdateSearchIndex(); err != nil {
|
if err := attendance.UpdateSearchIndex(ctx); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &attendance, nil
|
return &attendance, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) SearchUsersByEvent(eventID string) (*meilisearch.SearchResponse, error) {
|
func (self *Attendance) SearchUsersByEvent(ctx context.Context, eventID string) (*meilisearch.SearchResponse, error) {
|
||||||
index := MeiliSearch.Index("attendance")
|
index := MeiliSearch.Index("attendance")
|
||||||
|
|
||||||
return index.Search("", &meilisearch.SearchRequest{
|
return index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{
|
||||||
Filter: "event_id = \"" + eventID + "\"",
|
Filter: "event_id = \"" + eventID + "\"",
|
||||||
Sort: []string{"checkin_at:asc"},
|
Sort: []string{"checkin_at:asc"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) SearchEventsByUser(userID string) (*meilisearch.SearchResponse, error) {
|
func (self *Attendance) SearchEventsByUser(ctx context.Context, userID string) (*meilisearch.SearchResponse, error) {
|
||||||
index := MeiliSearch.Index("attendance")
|
index := MeiliSearch.Index("attendance")
|
||||||
|
|
||||||
return index.Search("", &meilisearch.SearchRequest{
|
return index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{
|
||||||
Filter: "user_id = \"" + userID + "\"",
|
Filter: "user_id = \"" + userID + "\"",
|
||||||
Sort: []string{"checkin_at:asc"},
|
Sort: []string{"checkin_at:asc"},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) UpdateSearchIndex() error {
|
func (self *Attendance) UpdateSearchIndex(ctx context.Context) error {
|
||||||
doc := AttendanceSearchDoc{
|
doc := AttendanceSearchDoc{
|
||||||
AttendanceId: self.AttendanceId.String(),
|
AttendanceId: self.AttendanceId.String(),
|
||||||
EventId: self.EventId.String(),
|
EventId: self.EventId.String(),
|
||||||
@@ -188,21 +188,20 @@ func (self *Attendance) UpdateSearchIndex() error {
|
|||||||
PrimaryKey: &primaryKey,
|
PrimaryKey: &primaryKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := index.UpdateDocuments([]AttendanceSearchDoc{doc}, opts); err != nil {
|
if _, err := index.UpdateDocumentsWithContext(ctx, []AttendanceSearchDoc{doc}, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) DeleteSearchIndex() error {
|
func (self *Attendance) DeleteSearchIndex(ctx context.Context) error {
|
||||||
index := MeiliSearch.Index("attendance")
|
index := MeiliSearch.Index("attendance")
|
||||||
_, err := index.DeleteDocument(self.AttendanceId.String(), nil)
|
_, err := index.DeleteDocumentWithContext(ctx, self.AttendanceId.String(), nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) GenCheckinCode(eventId uuid.UUID) (*string, error) {
|
func (self *Attendance) GenCheckinCode(ctx context.Context, eventId uuid.UUID) (*string, error) {
|
||||||
ctx := context.Background()
|
|
||||||
ttl := viper.GetDuration("ttl.checkin_code_ttl")
|
ttl := viper.GetDuration("ttl.checkin_code_ttl")
|
||||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
|
|
||||||
@@ -223,18 +222,16 @@ func (self *Attendance) GenCheckinCode(eventId uuid.UUID) (*string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Attendance) VerifyCheckinCode(checkinCode string) error {
|
func (self *Attendance) VerifyCheckinCode(ctx context.Context, checkinCode string) error {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
val, err := Redis.Get(ctx, "checkin_code:"+checkinCode).Result()
|
val, err := Redis.Get(ctx, "checkin_code:"+checkinCode).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("invalid or expired checkin code")
|
return errors.New("[Attendance Data] invalid or expired checkin code")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expected format: user_id:<uuid>:event_id:<uuid>
|
// Expected format: user_id:<uuid>:event_id:<uuid>
|
||||||
parts := strings.Split(val, ":")
|
parts := strings.Split(val, ":")
|
||||||
if len(parts) != 4 {
|
if len(parts) != 4 {
|
||||||
return errors.New("invalid checkin code format")
|
return errors.New("[Attendance Data] invalid checkin code format")
|
||||||
}
|
}
|
||||||
|
|
||||||
userIdStr := parts[1]
|
userIdStr := parts[1]
|
||||||
@@ -250,13 +247,13 @@ func (self *Attendance) VerifyCheckinCode(checkinCode string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
attendanceData, err := self.GetAttendance(userId, eventId)
|
attendanceData, err := self.GetAttendance(ctx, userId, eventId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
time := time.Now()
|
time := time.Now()
|
||||||
_, err = self.Update(attendanceData.AttendanceId, &time)
|
_, err = self.Update(ctx, attendanceData.AttendanceId, &time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -22,9 +23,9 @@ type Client struct {
|
|||||||
RedirectUri datatypes.JSON `json:"redirect_uri" gorm:"type:json;not null"`
|
RedirectUri datatypes.JSON `json:"redirect_uri" gorm:"type:json;not null"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Client) GetClientByClientId(clientId string) (*Client, error) {
|
func (self *Client) GetClientByClientId(ctx context.Context, clientId string) (*Client, error) {
|
||||||
var client Client
|
var client Client
|
||||||
if err := Database.
|
if err := Database.WithContext(ctx).
|
||||||
Where("client_id = ?", clientId).
|
Where("client_id = ?", clientId).
|
||||||
First(&client).Error; err != nil {
|
First(&client).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -32,10 +33,10 @@ func (self *Client) GetClientByClientId(clientId string) (*Client, error) {
|
|||||||
return &client, nil
|
return &client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Client) GetDecryptedSecret() (string, error) {
|
func (self *Client) GetDecryptedSecret() ([]byte, error) {
|
||||||
secretKey := viper.GetString("secrets.client_secret_key")
|
secretKey := viper.GetString("secrets.client_secret_key")
|
||||||
secret, err := cryptography.AESCBCDecrypt(self.ClientSecret, []byte(secretKey))
|
secret, err := cryptography.AESCBCDecrypt(self.ClientSecret, []byte(secretKey))
|
||||||
return string(secret), err
|
return secret, err
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClientParams struct {
|
type ClientParams struct {
|
||||||
@@ -44,7 +45,7 @@ type ClientParams struct {
|
|||||||
RedirectUri []string
|
RedirectUri []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Client) Create(params *ClientParams) (*Client, error) {
|
func (self *Client) Create(ctx context.Context, params *ClientParams) (*Client, error) {
|
||||||
jsonRedirectUri, err := json.Marshal(params.RedirectUri)
|
jsonRedirectUri, err := json.Marshal(params.RedirectUri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -69,7 +70,7 @@ func (self *Client) Create(params *ClientParams) (*Client, error) {
|
|||||||
RedirectUri: jsonRedirectUri,
|
RedirectUri: jsonRedirectUri,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := Database.Create(&client).Error; err != nil {
|
if err := Database.WithContext(ctx).Create(&client).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,5 +88,5 @@ func (self *Client) ValidateRedirectURI(redirectURI string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New("redirect uri not match")
|
return errors.New("[Client Data] redirect uri not match")
|
||||||
}
|
}
|
||||||
|
|||||||
19
data/data.go
19
data/data.go
@@ -1,11 +1,14 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"nixcn-cms/data/drivers"
|
"nixcn-cms/data/drivers"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
"github.com/meilisearch/meilisearch-go"
|
"github.com/meilisearch/meilisearch-go"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -14,7 +17,7 @@ var Database *gorm.DB
|
|||||||
var Redis redis.UniversalClient
|
var Redis redis.UniversalClient
|
||||||
var MeiliSearch meilisearch.ServiceManager
|
var MeiliSearch meilisearch.ServiceManager
|
||||||
|
|
||||||
func Init() {
|
func Init(ctx context.Context) {
|
||||||
// Init database
|
// Init database
|
||||||
dbType := viper.GetString("database.type")
|
dbType := viper.GetString("database.type")
|
||||||
exDSN := drivers.ExternalDSN{
|
exDSN := drivers.ExternalDSN{
|
||||||
@@ -25,19 +28,22 @@ func Init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if dbType != "postgres" {
|
if dbType != "postgres" {
|
||||||
log.Fatal("[Database] Only support postgras db!")
|
slog.ErrorContext(ctx, "[Database] Only support postgras db!")
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conect to db
|
// Conect to db
|
||||||
db, err := drivers.Postgres(exDSN)
|
db, err := drivers.Postgres(exDSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[Database] Error connecting to db!")
|
slog.ErrorContext(ctx, "[Database] Error connecting to db!", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auto migrate
|
// Auto migrate
|
||||||
err = db.AutoMigrate(&User{}, &Event{}, &Attendance{}, &Client{})
|
err = db.AutoMigrate(&User{}, &Event{}, &Attendance{}, &Client{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("[Database] Error migrating database: ", err)
|
slog.ErrorContext(ctx, "[Database] Error migrating database!", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
Database = db
|
Database = db
|
||||||
|
|
||||||
@@ -52,7 +58,8 @@ func Init() {
|
|||||||
}
|
}
|
||||||
rdb, err := drivers.Redis(rDSN)
|
rdb, err := drivers.Redis(rDSN)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("[Redis] Error connecting to Redis: ", err)
|
slog.ErrorContext(ctx, "[Redis] Error connecting to Redis!", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
Redis = rdb
|
Redis = rdb
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,31 @@
|
|||||||
package drivers
|
package drivers
|
||||||
|
|
||||||
import "github.com/meilisearch/meilisearch-go"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/meilisearch/meilisearch-go"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
|
||||||
|
)
|
||||||
|
|
||||||
func MeiliSearch(dsn MeiliDSN) meilisearch.ServiceManager {
|
func MeiliSearch(dsn MeiliDSN) meilisearch.ServiceManager {
|
||||||
|
serviceName := viper.GetString("search.service_name")
|
||||||
|
|
||||||
|
otelTransport := otelhttp.NewTransport(
|
||||||
|
http.DefaultTransport,
|
||||||
|
otelhttp.WithSpanNameFormatter(func(operation string, r *http.Request) string {
|
||||||
|
return fmt.Sprintf("%s %s", serviceName, r.Method)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: otelTransport,
|
||||||
|
}
|
||||||
|
|
||||||
return meilisearch.New(dsn.Host,
|
return meilisearch.New(dsn.Host,
|
||||||
meilisearch.WithAPIKey(dsn.ApiKey),
|
meilisearch.WithAPIKey(dsn.ApiKey),
|
||||||
|
meilisearch.WithCustomClient(httpClient),
|
||||||
meilisearch.WithContentEncoding(
|
meilisearch.WithContentEncoding(
|
||||||
meilisearch.GzipEncoding,
|
meilisearch.GzipEncoding,
|
||||||
meilisearch.BestCompression,
|
meilisearch.BestCompression,
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
package drivers
|
package drivers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log/slog"
|
||||||
"nixcn-cms/config"
|
"nixcn-cms/config"
|
||||||
|
"nixcn-cms/logger"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/plugin/opentelemetry/tracing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SplitHostPort(url string) (host, port string) {
|
func SplitHostPort(url string) (host, port string) {
|
||||||
@@ -17,8 +22,27 @@ func SplitHostPort(url string) (host, port string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Postgres(dsn ExternalDSN) (*gorm.DB, error) {
|
func Postgres(dsn ExternalDSN) (*gorm.DB, error) {
|
||||||
|
serviceName := viper.GetString("database.service_name")
|
||||||
|
|
||||||
host, port := SplitHostPort(dsn.Host)
|
host, port := SplitHostPort(dsn.Host)
|
||||||
conn := "host=" + host + " user=" + dsn.Username + " password=" + dsn.Password + " dbname=" + dsn.Name + " port=" + port + " sslmode=disable TimeZone=" + config.TZ()
|
conn := "host=" + host + " user=" + dsn.Username + " password=" + dsn.Password + " dbname=" + dsn.Name + " port=" + port + " sslmode=disable TimeZone=" + config.TZ()
|
||||||
db, err := gorm.Open(postgres.Open(conn), &gorm.Config{})
|
|
||||||
|
db, err := gorm.Open(postgres.Open(conn), &gorm.Config{
|
||||||
|
Logger: logger.GormLogger(),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = db.Use(tracing.NewPlugin(
|
||||||
|
tracing.WithAttributes(
|
||||||
|
attribute.String("db.instance", serviceName),
|
||||||
|
),
|
||||||
|
))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("[Database] Error starting otel plugin!", "name", serviceName, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
return db, err
|
return db, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,18 @@ package drivers
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/extra/redisotel/v9"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.opentelemetry.io/otel/attribute"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Redis(dsn RedisDSN) (redis.UniversalClient, error) {
|
func Redis(dsn RedisDSN) (redis.UniversalClient, error) {
|
||||||
|
serviceName := viper.GetString("cache.service_name")
|
||||||
|
|
||||||
// Connect to Redis
|
// Connect to Redis
|
||||||
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
|
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
|
||||||
Addrs: dsn.Hosts,
|
Addrs: dsn.Hosts,
|
||||||
@@ -15,8 +22,23 @@ func Redis(dsn RedisDSN) (redis.UniversalClient, error) {
|
|||||||
Password: dsn.Password,
|
Password: dsn.Password,
|
||||||
DB: dsn.DB,
|
DB: dsn.DB,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
attrs := []attribute.KeyValue{
|
||||||
|
semconv.DBSystemRedis,
|
||||||
|
attribute.String("db.instance", serviceName),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := redisotel.InstrumentMetrics(rdb, redisotel.WithAttributes(attrs...)); err != nil {
|
||||||
|
slog.Error("[Redis] Error starting otel metrics plugin!", "name", serviceName, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := redisotel.InstrumentTracing(rdb, redisotel.WithAttributes(attrs...)); err != nil {
|
||||||
|
slog.Error("[Redis] Error starting otel tracing plugin!", "name", serviceName, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ping redis
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
// Ping Redis
|
|
||||||
_, err := rdb.Ping(ctx).Result()
|
_, err := rdb.Ping(ctx).Result()
|
||||||
|
|
||||||
return rdb, err
|
return rdb, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
@@ -31,10 +32,10 @@ type EventSearchDoc struct {
|
|||||||
EndTime time.Time `json:"end_time"`
|
EndTime time.Time `json:"end_time"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Event) GetEventById(eventId uuid.UUID) (*Event, error) {
|
func (self *Event) GetEventById(ctx context.Context, eventId uuid.UUID) (*Event, error) {
|
||||||
var event Event
|
var event Event
|
||||||
|
|
||||||
err := Database.
|
err := Database.WithContext(ctx).
|
||||||
Where("event_id = ?", eventId).
|
Where("event_id = ?", eventId).
|
||||||
First(&event).Error
|
First(&event).Error
|
||||||
|
|
||||||
@@ -48,9 +49,9 @@ func (self *Event) GetEventById(eventId uuid.UUID) (*Event, error) {
|
|||||||
return &event, nil
|
return &event, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Event) UpdateEventById(eventId uuid.UUID) error {
|
func (self *Event) UpdateEventById(ctx context.Context, eventId uuid.UUID) error {
|
||||||
// DB transaction
|
// DB transaction
|
||||||
if err := Database.Transaction(func(tx *gorm.DB) error {
|
if err := Database.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
// Update by business key
|
// Update by business key
|
||||||
if err := tx.
|
if err := tx.
|
||||||
Model(&Event{}).
|
Model(&Event{}).
|
||||||
@@ -68,19 +69,19 @@ func (self *Event) UpdateEventById(eventId uuid.UUID) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Sync search index
|
// Sync search index
|
||||||
if err := self.UpdateSearchIndex(); err != nil {
|
if err := self.UpdateSearchIndex(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Event) Create() error {
|
func (self *Event) Create(ctx context.Context) error {
|
||||||
self.UUID = uuid.New()
|
self.UUID = uuid.New()
|
||||||
self.EventId = uuid.New()
|
self.EventId = uuid.New()
|
||||||
|
|
||||||
// DB transaction only
|
// DB transaction only
|
||||||
if err := Database.Transaction(func(tx *gorm.DB) error {
|
if err := Database.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if err := tx.Create(self).Error; err != nil {
|
if err := tx.Create(self).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -90,7 +91,7 @@ func (self *Event) Create() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search index (eventual consistency)
|
// Search index (eventual consistency)
|
||||||
if err := self.UpdateSearchIndex(); err != nil {
|
if err := self.UpdateSearchIndex(ctx); err != nil {
|
||||||
// TODO: async retry / log
|
// TODO: async retry / log
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -98,20 +99,20 @@ func (self *Event) Create() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Event) GetFullTable() (*[]Event, error) {
|
func (self *Event) GetFullTable(ctx context.Context) (*[]Event, error) {
|
||||||
var events []Event
|
var events []Event
|
||||||
err := Database.Find(&events).Error
|
err := Database.WithContext(ctx).Find(&events).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &events, err
|
return &events, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Event) FastListEvents(limit, offset int64) (*[]EventSearchDoc, error) {
|
func (self *Event) FastListEvents(ctx context.Context, limit, offset int64) (*[]EventSearchDoc, error) {
|
||||||
index := MeiliSearch.Index("event")
|
index := MeiliSearch.Index("event")
|
||||||
|
|
||||||
// Fast read from MeiliSearch (no DB involved)
|
// Fast read from MeiliSearch (no DB involved)
|
||||||
result, err := index.Search("", &meilisearch.SearchRequest{
|
result, err := index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{
|
||||||
Limit: limit,
|
Limit: limit,
|
||||||
Offset: offset,
|
Offset: offset,
|
||||||
})
|
})
|
||||||
@@ -127,7 +128,7 @@ func (self *Event) FastListEvents(limit, offset int64) (*[]EventSearchDoc, error
|
|||||||
return &list, nil
|
return &list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Event) UpdateSearchIndex() error {
|
func (self *Event) UpdateSearchIndex(ctx context.Context) error {
|
||||||
doc := EventSearchDoc{
|
doc := EventSearchDoc{
|
||||||
EventId: self.EventId.String(),
|
EventId: self.EventId.String(),
|
||||||
Name: self.Name,
|
Name: self.Name,
|
||||||
@@ -143,15 +144,15 @@ func (self *Event) UpdateSearchIndex() error {
|
|||||||
PrimaryKey: &primaryKey,
|
PrimaryKey: &primaryKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := index.UpdateDocuments([]EventSearchDoc{doc}, opts); err != nil {
|
if _, err := index.UpdateDocumentsWithContext(ctx, []EventSearchDoc{doc}, opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Event) DeleteSearchIndex() error {
|
func (self *Event) DeleteSearchIndex(ctx context.Context) error {
|
||||||
index := MeiliSearch.Index("event")
|
index := MeiliSearch.Index("event")
|
||||||
_, err := index.DeleteDocument(self.EventId.String(), nil)
|
_, err := index.DeleteDocumentWithContext(ctx, self.EventId.String(), nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
81
data/user.go
81
data/user.go
@@ -1,6 +1,8 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/meilisearch/meilisearch-go"
|
"github.com/meilisearch/meilisearch-go"
|
||||||
@@ -31,10 +33,50 @@ type UserSearchDoc struct {
|
|||||||
Avatar string `json:"avatar"`
|
Avatar string `json:"avatar"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *User) GetByEmail(email string) (*User, error) {
|
func (self *User) SetEmail(s string) *User {
|
||||||
|
self.Email = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) SetUsername(s string) *User {
|
||||||
|
self.Username = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) SetNickname(s string) *User {
|
||||||
|
self.Nickname = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) SetSubtitle(s string) *User {
|
||||||
|
self.Subtitle = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) SetAvatar(s string) *User {
|
||||||
|
self.Avatar = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) SetBio(s string) *User {
|
||||||
|
self.Bio = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) SetPermissionLevel(s uint) *User {
|
||||||
|
self.PermissionLevel = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) SetAllowPublic(s bool) *User {
|
||||||
|
self.AllowPublic = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *User) GetByEmail(ctx context.Context, email *string) (*User, error) {
|
||||||
var user User
|
var user User
|
||||||
|
|
||||||
err := Database.
|
err := Database.WithContext(ctx).
|
||||||
Where("email = ?", email).
|
Where("email = ?", email).
|
||||||
First(&user).Error
|
First(&user).Error
|
||||||
|
|
||||||
@@ -48,10 +90,10 @@ func (self *User) GetByEmail(email string) (*User, error) {
|
|||||||
return &user, nil
|
return &user, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *User) GetByUserId(userId uuid.UUID) (*User, error) {
|
func (self *User) GetByUserId(ctx context.Context, userId *uuid.UUID) (*User, error) {
|
||||||
var user User
|
var user User
|
||||||
|
|
||||||
err := Database.
|
err := Database.WithContext(ctx).
|
||||||
Where("user_id = ?", userId).
|
Where("user_id = ?", userId).
|
||||||
First(&user).Error
|
First(&user).Error
|
||||||
|
|
||||||
@@ -65,12 +107,12 @@ func (self *User) GetByUserId(userId uuid.UUID) (*User, error) {
|
|||||||
return &user, err
|
return &user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *User) Create() error {
|
func (self *User) Create(ctx context.Context) error {
|
||||||
self.UUID = uuid.New()
|
self.UUID = uuid.New()
|
||||||
self.UserId = uuid.New()
|
self.UserId = uuid.New()
|
||||||
|
|
||||||
// DB transaction only
|
// DB transaction only
|
||||||
if err := Database.Transaction(func(tx *gorm.DB) error {
|
if err := Database.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if err := tx.Create(self).Error; err != nil {
|
if err := tx.Create(self).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -80,7 +122,7 @@ func (self *User) Create() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search index (eventual consistency)
|
// Search index (eventual consistency)
|
||||||
if err := self.UpdateSearchIndex(); err != nil {
|
if err := self.UpdateSearchIndex(&ctx); err != nil {
|
||||||
// TODO: async retry / log
|
// TODO: async retry / log
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -88,8 +130,8 @@ func (self *User) Create() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *User) UpdateByUserID(userId uuid.UUID) error {
|
func (self *User) UpdateByUserID(ctx context.Context, userId *uuid.UUID) error {
|
||||||
return Database.Transaction(func(tx *gorm.DB) error {
|
return Database.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if err := tx.Model(&User{}).Where("user_id = ?", userId).Updates(&self).Error; err != nil {
|
if err := tx.Model(&User{}).Where("user_id = ?", userId).Updates(&self).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -97,22 +139,22 @@ func (self *User) UpdateByUserID(userId uuid.UUID) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *User) GetFullTable() (*[]User, error) {
|
func (self *User) GetFullTable(ctx context.Context) (*[]User, error) {
|
||||||
var users []User
|
var users []User
|
||||||
err := Database.Find(&users).Error
|
err := Database.WithContext(ctx).Find(&users).Error
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &users, nil
|
return &users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *User) FastListUsers(limit, offset int64) (*[]UserSearchDoc, error) {
|
func (self *User) FastListUsers(ctx context.Context, limit, offset *int64) (*[]UserSearchDoc, error) {
|
||||||
index := MeiliSearch.Index("user")
|
index := MeiliSearch.Index("user")
|
||||||
|
|
||||||
// Fast read from MeiliSearch, no DB involved
|
// Fast read from MeiliSearch, no DB involved
|
||||||
result, err := index.Search("", &meilisearch.SearchRequest{
|
result, err := index.SearchWithContext(ctx, "", &meilisearch.SearchRequest{
|
||||||
Limit: limit,
|
Limit: *limit,
|
||||||
Offset: offset,
|
Offset: *offset,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -126,7 +168,7 @@ func (self *User) FastListUsers(limit, offset int64) (*[]UserSearchDoc, error) {
|
|||||||
return &list, nil
|
return &list, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *User) UpdateSearchIndex() error {
|
func (self *User) UpdateSearchIndex(ctx *context.Context) error {
|
||||||
doc := UserSearchDoc{
|
doc := UserSearchDoc{
|
||||||
UserId: self.UserId.String(),
|
UserId: self.UserId.String(),
|
||||||
Email: self.Email,
|
Email: self.Email,
|
||||||
@@ -142,7 +184,8 @@ func (self *User) UpdateSearchIndex() error {
|
|||||||
PrimaryKey: &primaryKey,
|
PrimaryKey: &primaryKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := index.UpdateDocuments(
|
if _, err := index.UpdateDocumentsWithContext(
|
||||||
|
*ctx,
|
||||||
[]UserSearchDoc{doc},
|
[]UserSearchDoc{doc},
|
||||||
opts,
|
opts,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@@ -152,8 +195,8 @@ func (self *User) UpdateSearchIndex() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *User) DeleteSearchIndex() error {
|
func (self *User) DeleteSearchIndex(ctx *context.Context) error {
|
||||||
index := MeiliSearch.Index("user")
|
index := MeiliSearch.Index("user")
|
||||||
_, err := index.DeleteDocument(self.UserId.String(), nil)
|
_, err := index.DeleteDocumentWithContext(*ctx, self.UserId.String(), nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
12
devenv.nix
12
devenv.nix
@@ -10,6 +10,7 @@
|
|||||||
just
|
just
|
||||||
watchexec
|
watchexec
|
||||||
fvm
|
fvm
|
||||||
|
podman
|
||||||
];
|
];
|
||||||
|
|
||||||
dotenv = {
|
dotenv = {
|
||||||
@@ -29,12 +30,21 @@
|
|||||||
javascript.corepack.enable = true;
|
javascript.corepack.enable = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
env.PODMAN_COMPOSE_PROVIDER = "none";
|
||||||
|
|
||||||
processes = {
|
processes = {
|
||||||
client-cms = {
|
client-cms = {
|
||||||
exec = "pnpm run dev";
|
exec = "pnpm run dev";
|
||||||
cwd = "./client/cms";
|
cwd = "./client/cms";
|
||||||
};
|
};
|
||||||
backend.exec = "just watch-back";
|
backend.exec = "sleep 30 && just watch-back";
|
||||||
|
lgtm.exec = ''
|
||||||
|
podman rm -f lgtm || true
|
||||||
|
podman run --name lgtm \
|
||||||
|
-p 3000:3000 -p 4317:4317 -p 4318:4318 \
|
||||||
|
-e OTEL_METRIC_EXPORT_INTERVAL=5000 \
|
||||||
|
docker.io/grafana/otel-lgtm:latest
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
services = {
|
services = {
|
||||||
|
|||||||
3
generate.go
Normal file
3
generate.go
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
//go:generate go run ./cmd/gen_exception/main.go
|
||||||
49
go.mod
49
go.mod
@@ -10,22 +10,39 @@ require (
|
|||||||
github.com/aliyun/credentials-go v1.4.5
|
github.com/aliyun/credentials-go v1.4.5
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0
|
github.com/go-viper/mapstructure/v2 v2.4.0
|
||||||
|
github.com/goccy/go-json v0.10.5
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/meilisearch/meilisearch-go v0.35.0
|
github.com/meilisearch/meilisearch-go v0.35.0
|
||||||
|
github.com/redis/go-redis/extra/redisotel/v9 v9.17.2
|
||||||
github.com/redis/go-redis/v9 v9.17.2
|
github.com/redis/go-redis/v9 v9.17.2
|
||||||
github.com/sirupsen/logrus v1.9.3
|
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
|
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.64.0
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0
|
||||||
|
go.opentelemetry.io/otel v1.39.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0
|
||||||
|
go.opentelemetry.io/otel/log v0.15.0
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0
|
||||||
|
go.opentelemetry.io/otel/sdk/log v0.15.0
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0
|
||||||
golang.org/x/crypto v0.46.0
|
golang.org/x/crypto v0.46.0
|
||||||
golang.org/x/oauth2 v0.34.0
|
golang.org/x/text v0.33.0
|
||||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/datatypes v1.2.7
|
gorm.io/datatypes v1.2.7
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
gorm.io/gorm v1.31.1
|
gorm.io/gorm v1.31.1
|
||||||
|
gorm.io/plugin/opentelemetry v0.1.16
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
filippo.io/edwards25519 v1.1.0 // indirect
|
filippo.io/edwards25519 v1.1.0 // indirect
|
||||||
|
github.com/ClickHouse/ch-go v0.61.5 // indirect
|
||||||
|
github.com/ClickHouse/clickhouse-go/v2 v2.30.0 // indirect
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect
|
||||||
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
github.com/alibabacloud-go/debug v1.0.1 // indirect
|
||||||
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
|
github.com/alibabacloud-go/openapi-util v0.1.1 // indirect
|
||||||
@@ -33,20 +50,27 @@ require (
|
|||||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||||
github.com/bytedance/sonic v1.14.2 // indirect
|
github.com/bytedance/sonic v1.14.2 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
github.com/clbanning/mxj/v2 v2.7.0 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||||
|
github.com/go-faster/city v1.0.1 // indirect
|
||||||
|
github.com/go-faster/errors v0.7.1 // indirect
|
||||||
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
github.com/go-playground/validator/v10 v10.29.0 // indirect
|
github.com/go-playground/validator/v10 v10.29.0 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
|
||||||
github.com/goccy/go-yaml v1.19.1 // indirect
|
github.com/goccy/go-yaml v1.19.1 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||||
|
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||||
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
github.com/jackc/pgx/v5 v5.6.0 // indirect
|
||||||
@@ -54,15 +78,22 @@ require (
|
|||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.17.8 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/paulmach/orb v0.11.1 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.57.1 // indirect
|
github.com/quic-go/quic-go v0.57.1 // indirect
|
||||||
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.17.2 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
github.com/sagikazarmark/locafero v0.11.0 // indirect
|
||||||
|
github.com/segmentio/asm v1.2.0 // indirect
|
||||||
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
@@ -71,15 +102,21 @@ require (
|
|||||||
github.com/tjfoc/gmsm v1.4.1 // indirect
|
github.com/tjfoc/gmsm v1.4.1 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||||
go.uber.org/mock v0.6.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||||
golang.org/x/arch v0.23.0 // indirect
|
golang.org/x/arch v0.23.0 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/net v0.48.0 // indirect
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.39.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||||
|
google.golang.org/grpc v1.77.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.11 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
gorm.io/driver/mysql v1.5.6 // indirect
|
gorm.io/driver/clickhouse v0.7.0 // indirect
|
||||||
|
gorm.io/driver/mysql v1.5.7 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
141
go.sum
141
go.sum
@@ -2,6 +2,10 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
|
|||||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/ClickHouse/ch-go v0.61.5 h1:zwR8QbYI0tsMiEcze/uIMK+Tz1D3XZXLdNrlaOpeEI4=
|
||||||
|
github.com/ClickHouse/ch-go v0.61.5/go.mod h1:s1LJW/F/LcFs5HJnuogFMta50kKDO0lf9zzfrbl0RQg=
|
||||||
|
github.com/ClickHouse/clickhouse-go/v2 v2.30.0 h1:AG4D/hW39qa58+JHQIFOSnxyL46H6h2lrmGGk17dhFo=
|
||||||
|
github.com/ClickHouse/clickhouse-go/v2 v2.30.0/go.mod h1:i9ZQAojcayW3RsdCb3YR+n+wC2h65eJsZCscZ1Z1wyo=
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA=
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo=
|
||||||
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc=
|
||||||
@@ -61,6 +65,8 @@ github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPII
|
|||||||
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
||||||
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
||||||
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||||
|
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
@@ -78,6 +84,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cu
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||||
|
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
@@ -88,6 +96,15 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
|
|||||||
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
|
||||||
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk=
|
||||||
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls=
|
||||||
|
github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw=
|
||||||
|
github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw=
|
||||||
|
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||||
|
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||||
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
@@ -105,6 +122,7 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
|||||||
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
|
github.com/goccy/go-yaml v1.19.1 h1:3rG3+v8pkhRqoQ/88NYNMHYVGYztCOCIZ7UQhu7H+NE=
|
||||||
github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.19.1/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||||
@@ -124,10 +142,16 @@ github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrU
|
|||||||
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
|
||||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
@@ -136,6 +160,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||||
|
github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek=
|
||||||
|
github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||||
@@ -152,8 +180,14 @@ github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/
|
|||||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
|
github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
|
||||||
|
github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
@@ -177,9 +211,17 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
|||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
|
||||||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
|
||||||
|
github.com/paulmach/orb v0.11.1 h1:3koVegMC4X/WeiXYz9iswopaTwMem53NzTJuTF20JzU=
|
||||||
|
github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU=
|
||||||
|
github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||||
|
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@@ -187,14 +229,20 @@ github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
|||||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||||
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
|
github.com/quic-go/quic-go v0.57.1 h1:25KAAR9QR8KZrCZRThWMKVAwGoiHIrNbT72ULHTuI10=
|
||||||
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
github.com/quic-go/quic-go v0.57.1/go.mod h1:ly4QBAjHA2VhdnxhojRsCUOeJwKYg+taDlos92xb1+s=
|
||||||
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.17.2 h1:KYWnHK9pwzOUo3sNJlNmzRwZ5mw7opugn8njtGThKNg=
|
||||||
|
github.com/redis/go-redis/extra/rediscmd/v9 v9.17.2/go.mod h1:wsfMQVl/GFYD9Gx/tlxurlTtvHkZRAt8j1qi27eIlTk=
|
||||||
|
github.com/redis/go-redis/extra/redisotel/v9 v9.17.2 h1:wthFPRW3Y50CknMrjjJoYwXUFR4U7hMVJCMeLzDI8s4=
|
||||||
|
github.com/redis/go-redis/extra/redisotel/v9 v9.17.2/go.mod h1:iqfQX7U2o8MWSl8W+Ah8KqbQyi/UoR/MQNgvaUyA1wc=
|
||||||
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
github.com/redis/go-redis/v9 v9.17.2 h1:P2EGsA4qVIM3Pp+aPocCJ7DguDHhqrXNhVcEp4ViluI=
|
||||||
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
github.com/redis/go-redis/v9 v9.17.2/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
|
||||||
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
@@ -215,6 +263,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
|
|||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
@@ -224,6 +273,7 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
|
|||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||||
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w=
|
||||||
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=
|
||||||
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=
|
||||||
@@ -231,11 +281,57 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
|
||||||
|
github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g=
|
||||||
|
github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8=
|
||||||
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
||||||
|
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
|
go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0 h1:eypSOd+0txRKCXPNyqLPsbSfA0jULgJcGmSAdFAnrCM=
|
||||||
|
go.opentelemetry.io/contrib/bridges/otelslog v0.14.0/go.mod h1:CRGvIBL/aAxpQU34ZxyQVFlovVcp67s4cAmQu8Jh9mc=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.64.0 h1:7IKZbAYwlwLXAdu7SVPhzTjDjogWZxP4MIa7rovY+PU=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.64.0/go.mod h1:+TF5nf3NIv2X8PGxqfYOaRnAoMM43rUA2C3XsN2DoWA=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||||
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||||
|
go.opentelemetry.io/contrib/propagators/b3 v1.39.0 h1:PI7pt9pkSnimWcp5sQhUA9OzLbc3Ba4sL+VEUTNsxrk=
|
||||||
|
go.opentelemetry.io/contrib/propagators/b3 v1.39.0/go.mod h1:5gV/EzPnfYIwjzj+6y8tbGW2PKWhcsz5e/7twptRVQY=
|
||||||
|
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||||
|
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0 h1:W+m0g+/6v3pa5PgVf2xoFMi5YtNR06WtS7ve5pcvLtM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.15.0/go.mod h1:JM31r0GGZ/GU94mX8hN4D8v6e40aFlUECSQ48HaLgHM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0 h1:cEf8jF6WbuGQWUVcqgyWtTR0kOOAWY1DYZ+UhvdmQPw=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.39.0/go.mod h1:k1lzV5n5U3HkGvTCJHraTAGJ7MqsgL1wrGwTj1Isfiw=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0 h1:f0cb2XPmrqn4XMy9PNliTgRKJgS5WcL/u0/WRYGz4t0=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.39.0/go.mod h1:vnakAaFckOMiMtOIhFI2MNH4FYrZzXCYxmb1LlhoGz8=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0 h1:in9O8ESIOlwJAEGTkkf34DesGRAc/Pn8qJ7k3r/42LM=
|
||||||
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.39.0/go.mod h1:Rp0EXBm5tfnv0WL+ARyO/PHBEaEAT8UUHQ6AGJcSq6c=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0 h1:8UPA4IbVZxpsD76ihGOQiFml99GPAEZLohDXvqHdi6U=
|
||||||
|
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.39.0/go.mod h1:MZ1T/+51uIVKlRzGw1Fo46KEWThjlCBZKl2LzY5nv4g=
|
||||||
|
go.opentelemetry.io/otel/log v0.15.0 h1:0VqVnc3MgyYd7QqNVIldC3dsLFKgazR6P3P3+ypkyDY=
|
||||||
|
go.opentelemetry.io/otel/log v0.15.0/go.mod h1:9c/G1zbyZfgu1HmQD7Qj84QMmwTp2QCQsZH1aeoWDE4=
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||||
|
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||||
|
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
|
||||||
|
go.opentelemetry.io/otel/sdk/log v0.15.0 h1:WgMEHOUt5gjJE93yqfqJOkRflApNif84kxoHWS9VVHE=
|
||||||
|
go.opentelemetry.io/otel/sdk/log v0.15.0/go.mod h1:qDC/FlKQCXfH5hokGsNg9aUBGMJQsrUyeOiW5u+dKBQ=
|
||||||
|
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0 h1:Ijbtz+JKXl8T2MngiwqBlPaHqc4YCaP/i13Qrow6gAM=
|
||||||
|
go.opentelemetry.io/otel/sdk/log/logtest v0.14.0/go.mod h1:dCU8aEL6q+L9cYTqcVOk8rM9Tp8WdnHOPLiBgp0SGOA=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2WKg+sEJTtB8=
|
||||||
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
|
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||||
|
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
|
||||||
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
|
||||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
@@ -249,6 +345,7 @@ golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPh
|
|||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
|
||||||
@@ -263,6 +360,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk
|
|||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
@@ -277,7 +375,9 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||||
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
|
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
@@ -292,13 +392,13 @@ golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
|
|||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
|
|
||||||
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
|
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||||
@@ -313,9 +413,9 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -344,6 +444,7 @@ golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
|||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
@@ -351,8 +452,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
|||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -363,6 +464,8 @@ golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3
|
|||||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||||
|
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||||
@@ -370,25 +473,37 @@ golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxb
|
|||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516 h1:vmC/ws+pLzWjj/gzApyoZuSVrDtF1aod4u/+bbj8hgM=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:p3MLuOwURrGBRoEyFHBT3GjUwaCQVKeNqqWxlcISGdw=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
|
||||||
|
google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM=
|
||||||
|
google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
@@ -404,8 +519,10 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk=
|
gorm.io/datatypes v1.2.7 h1:ww9GAhF1aGXZY3EB3cJPJ7//JiuQo7DlQA7NNlVaTdk=
|
||||||
gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
|
gorm.io/datatypes v1.2.7/go.mod h1:M2iO+6S3hhi4nAyYe444Pcb0dcIiOMJ7QHaUXxyiNZY=
|
||||||
gorm.io/driver/mysql v1.5.6 h1:Ld4mkIickM+EliaQZQx3uOJDJHtrd70MxAUqWqlx3Y8=
|
gorm.io/driver/clickhouse v0.7.0 h1:BCrqvgONayvZRgtuA6hdya+eAW5P2QVagV3OlEp1vtA=
|
||||||
gorm.io/driver/mysql v1.5.6/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
gorm.io/driver/clickhouse v0.7.0/go.mod h1:TmNo0wcVTsD4BBObiRnCahUgHJHjBIwuRejHwYt3JRs=
|
||||||
|
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||||
|
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||||
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4=
|
||||||
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo=
|
||||||
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ=
|
||||||
@@ -415,5 +532,7 @@ gorm.io/driver/sqlserver v1.6.0/go.mod h1:WQzt4IJo/WHKnckU9jXBLMJIVNMVeTu25dnOze
|
|||||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
|
gorm.io/plugin/opentelemetry v0.1.16 h1:Kypj2YYAliJqkIczDZDde6P6sFMhKSlG5IpngMFQGpc=
|
||||||
|
gorm.io/plugin/opentelemetry v0.1.16/go.mod h1:P3RmTeZXT+9n0F1ccUqR5uuTvEXDxF8k2UpO7mTIB2Y=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import (
|
|||||||
func DecodeB64Json(b64Json string) (*KycInfo, error) {
|
func DecodeB64Json(b64Json string) (*KycInfo, error) {
|
||||||
rawJson, err := base64.StdEncoding.DecodeString(b64Json)
|
rawJson, err := base64.StdEncoding.DecodeString(b64Json)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("invalid base64 json")
|
return nil, errors.New("[KYC] invalid base64 json")
|
||||||
}
|
}
|
||||||
|
|
||||||
var kyc KycInfo
|
var kyc KycInfo
|
||||||
if err := json.Unmarshal(rawJson, &kyc); err != nil {
|
if err := json.Unmarshal(rawJson, &kyc); err != nil {
|
||||||
return nil, errors.New("invalid json structure")
|
return nil, errors.New("[KYC] invalid json structure")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &kyc, nil
|
return &kyc, nil
|
||||||
@@ -56,7 +56,7 @@ func DecodeAES(cipherStr string) (*KycInfo, error) {
|
|||||||
|
|
||||||
var kyc KycInfo
|
var kyc KycInfo
|
||||||
if err := json.Unmarshal(plainBytes, &kyc); err != nil {
|
if err := json.Unmarshal(plainBytes, &kyc); err != nil {
|
||||||
return nil, errors.New("invalid decrypted json")
|
return nil, errors.New("[KYC] invalid decrypted json")
|
||||||
}
|
}
|
||||||
|
|
||||||
return &kyc, nil
|
return &kyc, nil
|
||||||
7
internal/ali_cnrid/types.go
Normal file
7
internal/ali_cnrid/types.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package kyc
|
||||||
|
|
||||||
|
type KycAli struct {
|
||||||
|
ParamType string `json:"param_type"`
|
||||||
|
IdentifyNum string `json:"identify_num"`
|
||||||
|
UserName string `json:"user_name"`
|
||||||
|
}
|
||||||
@@ -14,9 +14,7 @@ type Token struct {
|
|||||||
Email string
|
Email string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthCode(clientId string, email string) (string, error) {
|
func NewAuthCode(ctx context.Context, clientId string, email string) (string, error) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// generate random code
|
// generate random code
|
||||||
b := make([]byte, 32)
|
b := make([]byte, 32)
|
||||||
if _, err := rand.Read(b); err != nil {
|
if _, err := rand.Read(b); err != nil {
|
||||||
@@ -48,8 +46,7 @@ func NewAuthCode(clientId string, email string) (string, error) {
|
|||||||
return code, nil
|
return code, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifyAuthCode(code string) (*Token, bool) {
|
func VerifyAuthCode(ctx context.Context, code string) (*Token, bool) {
|
||||||
ctx := context.Background()
|
|
||||||
key := "auth_code:" + code
|
key := "auth_code:" + code
|
||||||
|
|
||||||
// Read auth code payload
|
// Read auth code payload
|
||||||
@@ -39,11 +39,11 @@ func (self *Token) NewClaims(clientId string, userId uuid.UUID) JwtClaims {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate access token
|
// Generate access token
|
||||||
func (self *Token) GenerateAccessToken(clientId string, userId uuid.UUID) (string, error) {
|
func (self *Token) GenerateAccessToken(ctx context.Context, clientId string, userId uuid.UUID) (string, error) {
|
||||||
claims := self.NewClaims(clientId, userId)
|
claims := self.NewClaims(clientId, userId)
|
||||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||||
|
|
||||||
clientData, err := new(data.Client).GetClientByClientId(clientId)
|
clientData, err := new(data.Client).GetClientByClientId(ctx, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("error getting client data: %v", err)
|
return "", fmt.Errorf("error getting client data: %v", err)
|
||||||
}
|
}
|
||||||
@@ -70,9 +70,9 @@ func (self *Token) GenerateRefreshToken() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Issue both access and refresh token
|
// Issue both access and refresh token
|
||||||
func (self *Token) IssueTokens(clientId string, userId uuid.UUID) (string, string, error) {
|
func (self *Token) IssueTokens(ctx context.Context, clientId string, userId uuid.UUID) (string, string, error) {
|
||||||
// access token
|
// access token
|
||||||
access, err := self.GenerateAccessToken(clientId, userId)
|
access, err := self.GenerateAccessToken(ctx, clientId, userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,6 @@ func (self *Token) IssueTokens(clientId string, userId uuid.UUID) (string, strin
|
|||||||
return "", "", err
|
return "", "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
ttl := viper.GetDuration("ttl.refresh_ttl")
|
ttl := viper.GetDuration("ttl.refresh_ttl")
|
||||||
|
|
||||||
refreshKey := "refresh:" + refresh
|
refreshKey := "refresh:" + refresh
|
||||||
@@ -122,21 +121,20 @@ func (self *Token) IssueTokens(clientId string, userId uuid.UUID) (string, strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Refresh access token
|
// Refresh access token
|
||||||
func (self *Token) RefreshAccessToken(refreshToken string) (string, error) {
|
func (self *Token) RefreshAccessToken(ctx context.Context, refreshToken string) (string, error) {
|
||||||
ctx := context.Background()
|
|
||||||
key := "refresh:" + refreshToken
|
key := "refresh:" + refreshToken
|
||||||
|
|
||||||
// read refresh token bind data
|
// read refresh token bind data
|
||||||
dataMap, err := data.Redis.HGetAll(ctx, key).Result()
|
dataMap, err := data.Redis.HGetAll(ctx, key).Result()
|
||||||
if err != nil || len(dataMap) == 0 {
|
if err != nil || len(dataMap) == 0 {
|
||||||
return "", errors.New("invalid refresh token")
|
return "", errors.New("[Auth Token] invalid refresh token")
|
||||||
}
|
}
|
||||||
|
|
||||||
userIdStr := dataMap["user_id"]
|
userIdStr := dataMap["user_id"]
|
||||||
clientId := dataMap["client_id"]
|
clientId := dataMap["client_id"]
|
||||||
|
|
||||||
if userIdStr == "" || clientId == "" {
|
if userIdStr == "" || clientId == "" {
|
||||||
return "", errors.New("refresh token corrupted")
|
return "", errors.New("[Auth Token] refresh token corrupted")
|
||||||
}
|
}
|
||||||
|
|
||||||
userId, err := uuid.Parse(userIdStr)
|
userId, err := uuid.Parse(userIdStr)
|
||||||
@@ -145,11 +143,10 @@ func (self *Token) RefreshAccessToken(refreshToken string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate new access token
|
// Generate new access token
|
||||||
return self.GenerateAccessToken(clientId, userId)
|
return self.GenerateAccessToken(ctx, clientId, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Token) RenewRefreshToken(refreshToken string) (string, error) {
|
func (self *Token) RenewRefreshToken(ctx context.Context, refreshToken string) (string, error) {
|
||||||
ctx := context.Background()
|
|
||||||
ttl := viper.GetDuration("ttl.refresh_ttl")
|
ttl := viper.GetDuration("ttl.refresh_ttl")
|
||||||
|
|
||||||
oldKey := "refresh:" + refreshToken
|
oldKey := "refresh:" + refreshToken
|
||||||
@@ -157,14 +154,14 @@ func (self *Token) RenewRefreshToken(refreshToken string) (string, error) {
|
|||||||
// read old refresh token bind data
|
// read old refresh token bind data
|
||||||
dataMap, err := data.Redis.HGetAll(ctx, oldKey).Result()
|
dataMap, err := data.Redis.HGetAll(ctx, oldKey).Result()
|
||||||
if err != nil || len(dataMap) == 0 {
|
if err != nil || len(dataMap) == 0 {
|
||||||
return "", errors.New("invalid refresh token")
|
return "", errors.New("[Auth Token] invalid refresh token")
|
||||||
}
|
}
|
||||||
|
|
||||||
userIdStr := dataMap["user_id"]
|
userIdStr := dataMap["user_id"]
|
||||||
clientId := dataMap["client_id"]
|
clientId := dataMap["client_id"]
|
||||||
|
|
||||||
if userIdStr == "" || clientId == "" {
|
if userIdStr == "" || clientId == "" {
|
||||||
return "", errors.New("refresh token corrupted")
|
return "", errors.New("[Auth Token] refresh token corrupted")
|
||||||
}
|
}
|
||||||
|
|
||||||
// generate new refresh token
|
// generate new refresh token
|
||||||
@@ -174,7 +171,7 @@ func (self *Token) RenewRefreshToken(refreshToken string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// revoke old refresh token
|
// revoke old refresh token
|
||||||
if err := self.RevokeRefreshToken(refreshToken); err != nil {
|
if err := self.RevokeRefreshToken(ctx, refreshToken); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,9 +208,7 @@ func (self *Token) RenewRefreshToken(refreshToken string) (string, error) {
|
|||||||
return newRefresh, nil
|
return newRefresh, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Token) RevokeRefreshToken(refreshToken string) error {
|
func (self *Token) RevokeRefreshToken(ctx context.Context, refreshToken string) error {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
refreshKey := "refresh:" + refreshToken
|
refreshKey := "refresh:" + refreshToken
|
||||||
|
|
||||||
// read refresh token metadata (user_id, client_id)
|
// read refresh token metadata (user_id, client_id)
|
||||||
@@ -246,7 +241,7 @@ func (self *Token) RevokeRefreshToken(refreshToken string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *Token) HeaderVerify(header string) (string, error) {
|
func (self *Token) HeaderVerify(ctx context.Context, header string) (string, error) {
|
||||||
if header == "" {
|
if header == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@@ -254,7 +249,7 @@ func (self *Token) HeaderVerify(header string) (string, error) {
|
|||||||
// Split header to 2
|
// Split header to 2
|
||||||
parts := strings.SplitN(header, " ", 2)
|
parts := strings.SplitN(header, " ", 2)
|
||||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||||
return "", errors.New("invalid Authorization header format")
|
return "", errors.New("[Auth Token] invalid Authorization header format")
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenStr := parts[1]
|
tokenStr := parts[1]
|
||||||
@@ -266,14 +261,14 @@ func (self *Token) HeaderVerify(header string) (string, error) {
|
|||||||
claims,
|
claims,
|
||||||
func(token *jwt.Token) (any, error) {
|
func(token *jwt.Token) (any, error) {
|
||||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
return nil, errors.New("unexpected signing method")
|
return nil, errors.New("[Auth Token] unexpected signing method")
|
||||||
}
|
}
|
||||||
|
|
||||||
if claims.ClientId == "" {
|
if claims.ClientId == "" {
|
||||||
return nil, errors.New("client_id missing in token")
|
return nil, errors.New("[Auth Token] client_id missing in token")
|
||||||
}
|
}
|
||||||
|
|
||||||
clientData, err := new(data.Client).GetClientByClientId(claims.ClientId)
|
clientData, err := new(data.Client).GetClientByClientId(ctx, claims.ClientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error getting client data: %v", err)
|
return nil, fmt.Errorf("error getting client data: %v", err)
|
||||||
}
|
}
|
||||||
@@ -288,7 +283,8 @@ func (self *Token) HeaderVerify(header string) (string, error) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if err != nil || !token.Valid {
|
if err != nil || !token.Valid {
|
||||||
return "", errors.New("invalid or expired token")
|
fmt.Println(err)
|
||||||
|
return "", errors.New("[Auth Token] invalid or expired token")
|
||||||
}
|
}
|
||||||
|
|
||||||
return claims.UserID.String(), nil
|
return claims.UserID.String(), nil
|
||||||
@@ -21,7 +21,7 @@ func normalizeKey(key []byte) ([]byte, error) {
|
|||||||
case 16, 24, 32:
|
case 16, 24, 32:
|
||||||
return key, nil
|
return key, nil
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("AES key length must be 16, 24, or 32 bytes")
|
return nil, errors.New("[Cryptography AES] AES key length must be 16, 24, or 32 bytes")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ func AESGCMDecrypt(encoded string, key []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(data) < gcm.NonceSize() {
|
if len(data) < gcm.NonceSize() {
|
||||||
return nil, errors.New("ciphertext too short")
|
return nil, errors.New("[Cryptography AES] ciphertext too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
nonce := data[:gcm.NonceSize()]
|
nonce := data[:gcm.NonceSize()]
|
||||||
@@ -92,11 +92,11 @@ func pkcs7Pad(data []byte, blockSize int) []byte {
|
|||||||
func pkcs7Unpad(data []byte) ([]byte, error) {
|
func pkcs7Unpad(data []byte) ([]byte, error) {
|
||||||
length := len(data)
|
length := len(data)
|
||||||
if length == 0 {
|
if length == 0 {
|
||||||
return nil, errors.New("invalid padding")
|
return nil, errors.New("[Cryptography AES] invalid padding")
|
||||||
}
|
}
|
||||||
padding := int(data[length-1])
|
padding := int(data[length-1])
|
||||||
if padding == 0 || padding > length {
|
if padding == 0 || padding > length {
|
||||||
return nil, errors.New("invalid padding")
|
return nil, errors.New("[Cryptography AES] invalid padding")
|
||||||
}
|
}
|
||||||
return data[:length-padding], nil
|
return data[:length-padding], nil
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ func AESCBCDecrypt(encoded string, key []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(data) < block.BlockSize() {
|
if len(data) < block.BlockSize() {
|
||||||
return nil, errors.New("ciphertext too short")
|
return nil, errors.New("[Cryptography AES] ciphertext too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := data[:block.BlockSize()]
|
iv := data[:block.BlockSize()]
|
||||||
@@ -195,7 +195,7 @@ func AESCFBDecrypt(encoded string, key []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(data) < block.BlockSize() {
|
if len(data) < block.BlockSize() {
|
||||||
return nil, errors.New("ciphertext too short")
|
return nil, errors.New("[Cryptography AES] ciphertext too short")
|
||||||
}
|
}
|
||||||
|
|
||||||
iv := data[:block.BlockSize()]
|
iv := data[:block.BlockSize()]
|
||||||
|
|||||||
84
internal/email/email.go
Normal file
84
internal/email/email.go
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
package email
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
gomail "gopkg.in/gomail.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
dialer *gomail.Dialer
|
||||||
|
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
username string
|
||||||
|
security string
|
||||||
|
insecure bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Client) NewSMTPClient() (*Client, error) {
|
||||||
|
host := viper.GetString("email.host")
|
||||||
|
port := viper.GetInt("email.port")
|
||||||
|
user := viper.GetString("email.username")
|
||||||
|
pass := viper.GetString("email.password")
|
||||||
|
|
||||||
|
security := strings.ToLower(viper.GetString("email.security"))
|
||||||
|
insecure := viper.GetBool("email.insecure_skip_verify")
|
||||||
|
|
||||||
|
if host == "" || port == 0 || user == "" {
|
||||||
|
return nil, errors.New("[Email] SMTP config not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pass == "" {
|
||||||
|
return nil, errors.New("[Email] SMTP basic auth requires email.password")
|
||||||
|
}
|
||||||
|
|
||||||
|
dialer := gomail.NewDialer(host, port, user, pass)
|
||||||
|
dialer.TLSConfig = &tls.Config{
|
||||||
|
ServerName: host,
|
||||||
|
InsecureSkipVerify: insecure,
|
||||||
|
}
|
||||||
|
|
||||||
|
switch security {
|
||||||
|
case "ssl":
|
||||||
|
dialer.SSL = true
|
||||||
|
case "starttls":
|
||||||
|
dialer.SSL = false
|
||||||
|
case "plain", "":
|
||||||
|
dialer.SSL = false
|
||||||
|
dialer.TLSConfig = nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("[Email] unknown smtp security mode: " + security)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
dialer: dialer,
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
username: user,
|
||||||
|
security: security,
|
||||||
|
insecure: insecure,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Send(from, to, subject, html string) (string, error) {
|
||||||
|
if c.dialer == nil {
|
||||||
|
return "", errors.New("[Email] SMTP dialer not initialized")
|
||||||
|
}
|
||||||
|
|
||||||
|
m := gomail.NewMessage()
|
||||||
|
m.SetHeader("From", from)
|
||||||
|
m.SetHeader("To", to)
|
||||||
|
m.SetHeader("Subject", subject)
|
||||||
|
m.SetBody("text/html", html)
|
||||||
|
|
||||||
|
if err := c.dialer.DialAndSend(m); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Now().Format(time.RFC3339Nano), nil
|
||||||
|
}
|
||||||
76
internal/exception/builder.go
Normal file
76
internal/exception/builder.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package exception
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 12 chars len
|
||||||
|
// :1=status
|
||||||
|
// :3=service
|
||||||
|
// :2=endpoint
|
||||||
|
// :1=common/specific
|
||||||
|
// :5=original
|
||||||
|
|
||||||
|
type Builder struct {
|
||||||
|
Status string
|
||||||
|
Service string
|
||||||
|
Endpoint string
|
||||||
|
Type string
|
||||||
|
Original string
|
||||||
|
Error error
|
||||||
|
ErrorCode string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) SetStatus(s string) *Builder {
|
||||||
|
self.Status = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) SetService(s string) *Builder {
|
||||||
|
self.Service = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) SetEndpoint(s string) *Builder {
|
||||||
|
self.Endpoint = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) SetType(s string) *Builder {
|
||||||
|
self.Type = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) SetOriginal(s string) *Builder {
|
||||||
|
self.Original = s
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) SetError(e error) *Builder {
|
||||||
|
self.Error = e
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) build() {
|
||||||
|
self.ErrorCode = fmt.Sprintf("%s%s%s%s%s",
|
||||||
|
self.Status,
|
||||||
|
self.Service,
|
||||||
|
self.Endpoint,
|
||||||
|
self.Type,
|
||||||
|
self.Original,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) Throw(ctx context.Context) *Builder {
|
||||||
|
self.build()
|
||||||
|
if self.Error != nil {
|
||||||
|
ErrorHandler(ctx, self.Status, self.ErrorCode, self.Error)
|
||||||
|
}
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Builder) String() string {
|
||||||
|
self.build()
|
||||||
|
return self.ErrorCode
|
||||||
|
}
|
||||||
19
internal/exception/error.go
Normal file
19
internal/exception/error.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package exception
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ErrorHandler(ctx context.Context, status string, errorCode string, err error) {
|
||||||
|
switch status {
|
||||||
|
case StatusSuccess:
|
||||||
|
slog.InfoContext(ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||||
|
case StatusUser:
|
||||||
|
slog.WarnContext(ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||||
|
case StatusServer:
|
||||||
|
slog.ErrorContext(ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||||
|
case StatusClient:
|
||||||
|
slog.ErrorContext(ctx, "Service exception! ErrId: "+errorCode, "id", errorCode, "err", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
17
internal/kyc/types.go
Normal file
17
internal/kyc/types.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package kyc
|
||||||
|
|
||||||
|
type KycInfo struct {
|
||||||
|
Type string `json:"type"` // cnrid/passport
|
||||||
|
LegalName string `json:"legal_name"`
|
||||||
|
ResidentId string `json:"rsident_id"`
|
||||||
|
PassportInfo PassportInfo `json:"passport_info"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PassportInfo struct {
|
||||||
|
FirstName string `json:"first_name"`
|
||||||
|
LastName string `json:"last_name"`
|
||||||
|
DateOfExpire string `json:"date_of_expire"`
|
||||||
|
Nationality string `json:"nationality"`
|
||||||
|
DocumentType string `json:"document_type"`
|
||||||
|
DocumentNumber string `json:"document_number"`
|
||||||
|
}
|
||||||
13
internal/sc_realid/types.go
Normal file
13
internal/sc_realid/types.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package screalid
|
||||||
|
|
||||||
|
type KycPassportResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
FinalResult struct {
|
||||||
|
FirstName string `json:"firstName"`
|
||||||
|
LastName string `json:"lastName"`
|
||||||
|
DateOfExpire string `json:"dateOfExpire"`
|
||||||
|
Nationality string `json:"nationality"`
|
||||||
|
DocumentType string `json:"documentType"`
|
||||||
|
DocumentNumber string `json:"documentNumber"`
|
||||||
|
} `json:"finalResult"`
|
||||||
|
}
|
||||||
16
justfile
16
justfile
@@ -9,7 +9,9 @@ client_cms_dir := join(client_dir, "cms")
|
|||||||
server_exec_path := join(output_dir, project_name)
|
server_exec_path := join(output_dir, project_name)
|
||||||
server_entry := "main.go"
|
server_entry := "main.go"
|
||||||
|
|
||||||
install: install-cms
|
install: install-cms install-back
|
||||||
|
|
||||||
|
generate: gen-back
|
||||||
|
|
||||||
install-cms:
|
install-cms:
|
||||||
cd {{ client_cms_dir }} && {{ pnpm_cmd }} install
|
cd {{ client_cms_dir }} && {{ pnpm_cmd }} install
|
||||||
@@ -24,20 +26,26 @@ build-client-cms:
|
|||||||
build-back:
|
build-back:
|
||||||
{{ go_cmd }} build -o {{ server_exec_path }}{{ if os() == "windows" { ".exe" } else { "" } }} {{ server_entry }}
|
{{ go_cmd }} build -o {{ server_exec_path }}{{ if os() == "windows" { ".exe" } else { "" } }} {{ server_entry }}
|
||||||
|
|
||||||
|
install-back:
|
||||||
|
cd {{ project_dir }} && go mod tidy
|
||||||
|
|
||||||
run-back:
|
run-back:
|
||||||
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} {{ server_exec_path }}{{ if os() == "windows" { ".exe" } else { "" } }}
|
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} {{ server_exec_path }}{{ if os() == "windows" { ".exe" } else { "" } }}
|
||||||
|
|
||||||
test-back:
|
test-back:
|
||||||
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} GO_ENV=test go test -C .. ./...
|
cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} GO_ENV=test go test -C .. ./...
|
||||||
|
|
||||||
|
gen-back:
|
||||||
|
cd {{ project_dir }} && go generate .
|
||||||
|
|
||||||
watch-back:
|
watch-back:
|
||||||
watchexec -r -e go,yaml,tpl -i '.devenv/**' -i '.direnv/**' -i 'client/**' -i 'vendor/**' 'go build -o {{ server_exec_path }} . && cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} {{ server_exec_path }}'
|
watchexec -r -e go,yaml,tpl -i '.devenv/**' -i '.direnv/**' -i 'client/**' -i 'vendor/**' 'go build -o {{ server_exec_path }} . && cd {{ output_dir }} && CONFIG_PATH={{ output_dir }} {{ server_exec_path }}'
|
||||||
|
|
||||||
dev: clean install
|
dev: clean install generate
|
||||||
devenv up --verbose
|
devenv up --verbose
|
||||||
|
|
||||||
dev-client-cms: install-cms
|
dev-client-cms: install-cms
|
||||||
devenv up client-cms --verbose
|
devenv up client-cms --verbose
|
||||||
|
|
||||||
dev-back: clean
|
dev-back: clean install-back gen-back
|
||||||
devenv up backend postgres redis meilisearch --verbose
|
devenv up backend postgres redis meilisearch lgtm --verbose
|
||||||
|
|||||||
28
logger/gorm.go
Normal file
28
logger/gorm.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SlogWriter struct{}
|
||||||
|
|
||||||
|
func (w *SlogWriter) Printf(format string, args ...any) {
|
||||||
|
msg := fmt.Sprintf(format, args...)
|
||||||
|
slog.Info(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GormLogger() logger.Interface {
|
||||||
|
return logger.New(
|
||||||
|
&SlogWriter{},
|
||||||
|
logger.Config{
|
||||||
|
SlowThreshold: 200 * time.Millisecond,
|
||||||
|
LogLevel: logger.Warn,
|
||||||
|
IgnoreRecordNotFoundError: true,
|
||||||
|
Colorful: false,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
package logger
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Init() {
|
|
||||||
FileLogger := viper.GetBool("server.file_logger")
|
|
||||||
DebugMode := viper.GetBool("server.debug_mode")
|
|
||||||
|
|
||||||
if FileLogger == true {
|
|
||||||
gin.DisableConsoleColor()
|
|
||||||
file, err := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
|
|
||||||
if err != nil {
|
|
||||||
log.Panicln("Error opening log file: ", err)
|
|
||||||
}
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
log.SetOutput(file)
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
} else {
|
|
||||||
log.SetFormatter(&log.JSONFormatter{})
|
|
||||||
log.SetOutput(os.Stdout)
|
|
||||||
log.SetLevel(log.WarnLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
if DebugMode == true {
|
|
||||||
gin.SetMode(gin.DebugMode)
|
|
||||||
log.SetLevel(log.DebugLevel)
|
|
||||||
} else {
|
|
||||||
gin.SetMode(gin.ReleaseMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func Gin() gin.HandlerFunc {
|
|
||||||
return func(ctx *gin.Context) {
|
|
||||||
startTime := time.Now()
|
|
||||||
ctx.Next()
|
|
||||||
endTime := time.Now()
|
|
||||||
latencyTime := endTime.Sub(startTime)
|
|
||||||
reqMethod := ctx.Request.Method
|
|
||||||
reqUri := ctx.Request.RequestURI
|
|
||||||
statusCode := ctx.Writer.Status()
|
|
||||||
clientIP := ctx.ClientIP()
|
|
||||||
|
|
||||||
log.WithFields(log.Fields{
|
|
||||||
"METHOD": reqMethod,
|
|
||||||
"URI": reqUri,
|
|
||||||
"STATUS": statusCode,
|
|
||||||
"LATENCY": latencyTime,
|
|
||||||
"CLIENT_IP": clientIP,
|
|
||||||
}).Info("HTTP REQUEST")
|
|
||||||
|
|
||||||
ctx.Next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
97
logger/slog.go
Normal file
97
logger/slog.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.opentelemetry.io/contrib/bridges/otelslog"
|
||||||
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type multiHandler struct {
|
||||||
|
handlers []slog.Handler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *multiHandler) Enabled(ctx context.Context, l slog.Level) bool {
|
||||||
|
for _, h := range m.handlers {
|
||||||
|
if h.Enabled(ctx, l) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *multiHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||||
|
span := trace.SpanFromContext(ctx)
|
||||||
|
if span.SpanContext().HasTraceID() {
|
||||||
|
r.AddAttrs(
|
||||||
|
slog.String("trace_id", span.SpanContext().TraceID().String()),
|
||||||
|
slog.String("span_id", span.SpanContext().SpanID().String()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, h := range m.handlers {
|
||||||
|
_ = h.Handle(ctx, r)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||||
|
newHandlers := make([]slog.Handler, len(m.handlers))
|
||||||
|
for i, h := range m.handlers {
|
||||||
|
newHandlers[i] = h.WithAttrs(attrs)
|
||||||
|
}
|
||||||
|
return &multiHandler{handlers: newHandlers}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *multiHandler) WithGroup(name string) slog.Handler {
|
||||||
|
newHandlers := make([]slog.Handler, len(m.handlers))
|
||||||
|
for i, h := range m.handlers {
|
||||||
|
newHandlers[i] = h.WithGroup(name)
|
||||||
|
}
|
||||||
|
return &multiHandler{handlers: newHandlers}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
levelStr := strings.ToLower(viper.GetString("server.log_level"))
|
||||||
|
var level slog.Level
|
||||||
|
switch levelStr {
|
||||||
|
case "debug":
|
||||||
|
level = slog.LevelDebug
|
||||||
|
case "warn":
|
||||||
|
level = slog.LevelWarn
|
||||||
|
case "error":
|
||||||
|
level = slog.LevelError
|
||||||
|
default:
|
||||||
|
level = slog.LevelInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
var writer io.Writer = os.Stdout
|
||||||
|
if level == slog.LevelDebug {
|
||||||
|
file, _ := os.OpenFile("app.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
|
||||||
|
writer = io.MultiWriter(os.Stdout, file)
|
||||||
|
}
|
||||||
|
|
||||||
|
localHandler := slog.NewJSONHandler(writer, &slog.HandlerOptions{
|
||||||
|
Level: level,
|
||||||
|
AddSource: true,
|
||||||
|
ReplaceAttr: func(groups []string, a slog.Attr) slog.Attr {
|
||||||
|
if a.Key == slog.TimeKey {
|
||||||
|
return slog.String(a.Key, a.Value.Time().Format("2006-01-02 15:04:05"))
|
||||||
|
}
|
||||||
|
return a
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
otelHandler := otelslog.NewHandler(viper.GetString("server.service_name"))
|
||||||
|
|
||||||
|
combinedHandler := &multiHandler{
|
||||||
|
handlers: []slog.Handler{localHandler, otelHandler},
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.SetDefault(slog.New(combinedHandler))
|
||||||
|
}
|
||||||
20
main.go
20
main.go
@@ -1,15 +1,31 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
"nixcn-cms/config"
|
"nixcn-cms/config"
|
||||||
"nixcn-cms/data"
|
"nixcn-cms/data"
|
||||||
"nixcn-cms/logger"
|
"nixcn-cms/logger"
|
||||||
"nixcn-cms/server"
|
"nixcn-cms/server"
|
||||||
|
"nixcn-cms/tracer"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
config.Init()
|
config.Init()
|
||||||
|
|
||||||
|
// OTEL
|
||||||
|
ctx := context.Background()
|
||||||
|
shutdown := tracer.Init(ctx)
|
||||||
|
defer func() {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
if err := shutdown(ctx); err != nil {
|
||||||
|
slog.Error("[Main] Tracer shutdown failed!", "err", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
logger.Init()
|
logger.Init()
|
||||||
data.Init()
|
data.Init(ctx)
|
||||||
server.Start()
|
server.Start(ctx)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
func ApiVersionCheck() gin.HandlerFunc {
|
func ApiVersionCheck() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
apiVersion := c.GetHeader("X-Api-Version")
|
apiVersion := c.GetHeader("X-Api-Version")
|
||||||
if apiVersion == "" {
|
if apiVersion == "" {
|
||||||
c.Abort()
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.MiddlewareServiceApiVersion).
|
||||||
|
SetEndpoint(exception.EndpointMiddlewareService).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.ApiVersionNotFound).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
utils.HttpAbort(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|||||||
56
middleware/gin_logger.go
Normal file
56
middleware/gin_logger.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GinLogger() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
var body []byte
|
||||||
|
if c.Request.Body != nil {
|
||||||
|
body, _ = io.ReadAll(c.Request.Body)
|
||||||
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
headerJSON, _ := json.Marshal(c.Request.Header)
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
ctx := c.Request.Context()
|
||||||
|
|
||||||
|
var errorMessage string
|
||||||
|
if len(c.Errors) > 0 {
|
||||||
|
errorMessage = c.Errors.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := []any{
|
||||||
|
"status", c.Writer.Status(),
|
||||||
|
"method", c.Request.Method,
|
||||||
|
"uri", c.Request.RequestURI,
|
||||||
|
"ip", c.ClientIP(),
|
||||||
|
"latency", time.Since(startTime).String(),
|
||||||
|
"user_agent", c.Request.UserAgent(),
|
||||||
|
"headers", string(headerJSON),
|
||||||
|
"request_body", string(body),
|
||||||
|
"errors", errorMessage,
|
||||||
|
}
|
||||||
|
|
||||||
|
status := c.Writer.Status()
|
||||||
|
if len(c.Errors) > 0 || status >= 500 {
|
||||||
|
slog.ErrorContext(ctx, fmt.Sprintf("%d %s %s", c.Writer.Status(), c.Request.Method, c.Request.RequestURI), fields...)
|
||||||
|
} else if status >= 400 {
|
||||||
|
slog.WarnContext(ctx, fmt.Sprintf("%d %s %s", c.Writer.Status(), c.Request.Method, c.Request.RequestURI), fields...)
|
||||||
|
} else {
|
||||||
|
slog.InfoContext(ctx, fmt.Sprintf("%d %s %s", c.Writer.Status(), c.Request.Method, c.Request.RequestURI), fields...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,32 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"nixcn-cms/pkgs/authtoken"
|
"nixcn-cms/internal/authtoken"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
"nixcn-cms/utils"
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func JWTAuth(required bool) gin.HandlerFunc {
|
func JWTAuth() gin.HandlerFunc {
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
auth := c.GetHeader("Authorization")
|
auth := c.GetHeader("Authorization")
|
||||||
|
|
||||||
authtoken := new(authtoken.Token)
|
authtoken := new(authtoken.Token)
|
||||||
uid, err := authtoken.HeaderVerify(auth)
|
uid, err := authtoken.HeaderVerify(c, auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpAbort(c, 401, "", "unauthorized")
|
errorCode := new(exception.Builder).
|
||||||
return
|
SetStatus(exception.StatusUser).
|
||||||
}
|
SetService(exception.MiddlewareServiceJwt).
|
||||||
|
SetEndpoint(exception.EndpointMiddlewareService).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUnauthorized).
|
||||||
|
SetError(err).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
|
||||||
if required == true && uid == "" {
|
utils.HttpAbort(c, 401, errorCode)
|
||||||
utils.HttpAbort(c, 401, "", "unauthorized")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package middleware
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"nixcn-cms/data"
|
"nixcn-cms/data"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
"nixcn-cms/utils"
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -15,19 +16,47 @@ func Permission(requiredLevel uint) gin.HandlerFunc {
|
|||||||
if !ok {
|
if !ok {
|
||||||
userIdOrig, ok := c.Get("user_id")
|
userIdOrig, ok := c.Get("user_id")
|
||||||
if !ok || userIdOrig.(string) == "" {
|
if !ok || userIdOrig.(string) == "" {
|
||||||
utils.HttpAbort(c, 401, "", "missing user id")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.MiddlewareServicePermission).
|
||||||
|
SetEndpoint(exception.EndpointMiddlewareService).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorMissingUserId).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
|
||||||
|
utils.HttpAbort(c, 401, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userId, err := uuid.Parse(userIdOrig.(string))
|
userId, err := uuid.Parse(userIdOrig.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpAbort(c, 500, "", "error parsing user id")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.MiddlewareServicePermission).
|
||||||
|
SetEndpoint(exception.EndpointMiddlewareService).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUuidParseFailed).
|
||||||
|
SetError(err).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
utils.HttpAbort(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userData, err := new(data.User).GetByUserId(userId)
|
userData, err := new(data.User).GetByUserId(c, &userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpAbort(c, 404, "", "user not found")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.MiddlewareServicePermission).
|
||||||
|
SetEndpoint(exception.EndpointMiddlewareService).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUserNotFound).
|
||||||
|
SetError(err).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
|
||||||
|
utils.HttpAbort(c, 404, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,7 +67,16 @@ func Permission(requiredLevel uint) gin.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if permissionLevel < requiredLevel {
|
if permissionLevel < requiredLevel {
|
||||||
utils.HttpAbort(c, 403, "", "permission denied")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.MiddlewareServicePermission).
|
||||||
|
SetEndpoint(exception.EndpointMiddlewareService).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorPermissionDenied).
|
||||||
|
Throw(c).
|
||||||
|
String()
|
||||||
|
|
||||||
|
utils.HttpAbort(c, 403, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,313 +0,0 @@
|
|||||||
package email
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/smtp"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
|
||||||
"golang.org/x/oauth2"
|
|
||||||
"golang.org/x/oauth2/clientcredentials"
|
|
||||||
gomail "gopkg.in/gomail.v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
// basic smtp
|
|
||||||
dialer *gomail.Dialer
|
|
||||||
|
|
||||||
// shared
|
|
||||||
from string
|
|
||||||
host string
|
|
||||||
port int
|
|
||||||
username string
|
|
||||||
|
|
||||||
security string
|
|
||||||
insecure bool
|
|
||||||
|
|
||||||
// auth mode
|
|
||||||
authMode string
|
|
||||||
|
|
||||||
// oauth2
|
|
||||||
oauth *oauthTokenProvider
|
|
||||||
}
|
|
||||||
|
|
||||||
type oauthTokenProvider struct {
|
|
||||||
cfg clientcredentials.Config
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
token *oauth2.Token
|
|
||||||
fetchErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *oauthTokenProvider) getToken(ctx context.Context) (string, error) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
if p.token != nil && p.token.Valid() && time.Until(p.token.Expiry) > 60*time.Second {
|
|
||||||
return p.token.AccessToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
tok, err := p.cfg.Token(ctx)
|
|
||||||
if err != nil {
|
|
||||||
p.fetchErr = err
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
p.token = tok
|
|
||||||
p.fetchErr = nil
|
|
||||||
return tok.AccessToken, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSMTPClient() (*Client, error) {
|
|
||||||
host := viper.GetString("email.host")
|
|
||||||
port := viper.GetInt("email.port")
|
|
||||||
user := viper.GetString("email.username")
|
|
||||||
pass := viper.GetString("email.password")
|
|
||||||
from := viper.GetString("email.from")
|
|
||||||
|
|
||||||
security := strings.ToLower(viper.GetString("email.security"))
|
|
||||||
insecure := viper.GetBool("email.insecure_skip_verify")
|
|
||||||
|
|
||||||
authMode := strings.ToLower(viper.GetString("email.auth"))
|
|
||||||
if authMode == "" {
|
|
||||||
authMode = "basic"
|
|
||||||
}
|
|
||||||
|
|
||||||
if host == "" || port == 0 || user == "" {
|
|
||||||
return nil, errors.New("SMTP config not set")
|
|
||||||
}
|
|
||||||
|
|
||||||
c := &Client{
|
|
||||||
from: from,
|
|
||||||
host: host,
|
|
||||||
port: port,
|
|
||||||
username: user,
|
|
||||||
security: security,
|
|
||||||
insecure: insecure,
|
|
||||||
authMode: authMode,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch authMode {
|
|
||||||
case "basic":
|
|
||||||
if pass == "" {
|
|
||||||
return nil, errors.New("SMTP basic auth requires email.password")
|
|
||||||
}
|
|
||||||
|
|
||||||
dialer := gomail.NewDialer(host, port, user, pass)
|
|
||||||
dialer.TLSConfig = &tls.Config{
|
|
||||||
ServerName: host,
|
|
||||||
InsecureSkipVerify: insecure,
|
|
||||||
}
|
|
||||||
|
|
||||||
switch security {
|
|
||||||
case "ssl":
|
|
||||||
dialer.SSL = true
|
|
||||||
case "starttls":
|
|
||||||
dialer.SSL = false
|
|
||||||
case "plain", "":
|
|
||||||
dialer.SSL = false
|
|
||||||
dialer.TLSConfig = nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown smtp security mode: " + security)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.dialer = dialer
|
|
||||||
return c, nil
|
|
||||||
|
|
||||||
case "oauth2":
|
|
||||||
if security == "" {
|
|
||||||
security = "starttls"
|
|
||||||
c.security = "starttls"
|
|
||||||
}
|
|
||||||
if security == "plain" {
|
|
||||||
return nil, errors.New("oauth2 requires TLS (starttls or ssl); plain is not allowed")
|
|
||||||
}
|
|
||||||
|
|
||||||
tenantID := viper.GetString("email.oauth2.tenant_id")
|
|
||||||
clientID := viper.GetString("email.oauth2.client_id")
|
|
||||||
clientSecret := viper.GetString("email.oauth2.client_secret")
|
|
||||||
scope := viper.GetString("email.oauth2.scope")
|
|
||||||
if scope == "" {
|
|
||||||
// Microsoft Learn: client credentials for SMTP uses https://outlook.office365.com/.default :contentReference[oaicite:3]{index=3}
|
|
||||||
scope = "https://outlook.office365.com/.default"
|
|
||||||
}
|
|
||||||
|
|
||||||
if tenantID == "" || clientID == "" || clientSecret == "" {
|
|
||||||
return nil, errors.New("oauth2 requires email.oauth2.tenant_id/client_id/client_secret")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.oauth = &oauthTokenProvider{
|
|
||||||
cfg: clientcredentials.Config{
|
|
||||||
ClientID: clientID,
|
|
||||||
ClientSecret: clientSecret,
|
|
||||||
TokenURL: fmt.Sprintf("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantID),
|
|
||||||
Scopes: []string{scope},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return c, nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return nil, errors.New("unknown email.auth: " + authMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Send(to, subject, html string) (string, error) {
|
|
||||||
m := gomail.NewMessage()
|
|
||||||
m.SetHeader("From", c.from)
|
|
||||||
m.SetHeader("To", to)
|
|
||||||
m.SetHeader("Subject", subject)
|
|
||||||
m.SetBody("text/html", html)
|
|
||||||
|
|
||||||
switch c.authMode {
|
|
||||||
case "basic":
|
|
||||||
if c.dialer == nil {
|
|
||||||
return "", errors.New("basic dialer not initialized")
|
|
||||||
}
|
|
||||||
if err := c.dialer.DialAndSend(m); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return time.Now().Format(time.RFC3339Nano), nil
|
|
||||||
|
|
||||||
case "oauth2":
|
|
||||||
if err := c.sendWithXOAUTH2(m, to); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return time.Now().Format(time.RFC3339Nano), nil
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "", errors.New("unsupported auth mode: " + c.authMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// XOAUTH2 auth for net/smtp
|
|
||||||
type xoauth2Auth struct {
|
|
||||||
username string
|
|
||||||
token string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *xoauth2Auth) Start(server *smtp.ServerInfo) (string, []byte, error) {
|
|
||||||
if !server.TLS {
|
|
||||||
return "", nil, errors.New("refusing to authenticate over insecure connection")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Microsoft Learn XOAUTH2 Format: user=<user>\x01auth=Bearer <token>\x01\x01 :contentReference[oaicite:4]{index=4}
|
|
||||||
resp := fmt.Sprintf("user=%s\x01auth=Bearer %s\x01\x01", a.username, a.token)
|
|
||||||
return "XOAUTH2", []byte(resp), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *xoauth2Auth) Next(fromServer []byte, more bool) ([]byte, error) {
|
|
||||||
if more {
|
|
||||||
return nil, errors.New("unexpected server challenge during XOAUTH2 auth")
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) sendWithXOAUTH2(m *gomail.Message, rcpt string) error {
|
|
||||||
if c.oauth == nil {
|
|
||||||
return errors.New("oauth2 provider not initialized")
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
token, err := c.oauth.getToken(ctx)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("oauth2 token error: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// write gomail.Message to RFC822
|
|
||||||
var buf bytes.Buffer
|
|
||||||
if _, err := m.WriteTo(&buf); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
msg := buf.Bytes()
|
|
||||||
|
|
||||||
addr := fmt.Sprintf("%s:%d", c.host, c.port)
|
|
||||||
tlsCfg := &tls.Config{
|
|
||||||
ServerName: c.host,
|
|
||||||
InsecureSkipVerify: c.insecure,
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
conn net.Conn
|
|
||||||
cl *smtp.Client
|
|
||||||
)
|
|
||||||
|
|
||||||
switch c.security {
|
|
||||||
case "ssl":
|
|
||||||
conn, err = tls.Dial("tcp", addr, tlsCfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cl, err = smtp.NewClient(conn, c.host)
|
|
||||||
if err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
case "starttls", "":
|
|
||||||
conn, err = net.Dial("tcp", addr)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cl, err = smtp.NewClient(conn, c.host)
|
|
||||||
if err != nil {
|
|
||||||
_ = conn.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Upgrade with STARTTLS
|
|
||||||
if ok, _ := cl.Extension("STARTTLS"); ok {
|
|
||||||
if err := cl.StartTLS(tlsCfg); err != nil {
|
|
||||||
_ = cl.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_ = cl.Close()
|
|
||||||
return errors.New("server does not support STARTTLS")
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return errors.New("unknown smtp security mode: " + c.security)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() { _ = cl.Quit() }()
|
|
||||||
|
|
||||||
// AUTH XOAUTH2
|
|
||||||
if err := cl.Auth(&xoauth2Auth{username: c.username, token: token}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// MAIL FROM / RCPT TO / DATA
|
|
||||||
if err := cl.Mail(extractAddress(c.from)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := cl.Rcpt(rcpt); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
w, err := cl.Data()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(msg); err != nil {
|
|
||||||
_ = w.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractAddress(from string) string {
|
|
||||||
if i := strings.LastIndex(from, "<"); i >= 0 {
|
|
||||||
if j := strings.LastIndex(from, ">"); j > i {
|
|
||||||
return strings.TrimSpace(from[i+1 : j])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(from)
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package kyc
|
|
||||||
|
|
||||||
type KycInfo struct {
|
|
||||||
Type string `json:"type"` // Chinese / Foreigner
|
|
||||||
LegalName string `json:"legal_name"`
|
|
||||||
ResidentId string `json:"rsident_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type KycAli struct {
|
|
||||||
ParamType string `json:"param_type"`
|
|
||||||
IdentifyNum string `json:"identify_num"`
|
|
||||||
UserName string `json:"user_name"`
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nixcn-cms/middleware"
|
|
||||||
"nixcn-cms/service/auth"
|
|
||||||
"nixcn-cms/service/event"
|
|
||||||
"nixcn-cms/service/user"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Router(e *gin.Engine) {
|
|
||||||
// API Services
|
|
||||||
api := e.Group("/api/v1", middleware.ApiVersionCheck())
|
|
||||||
auth.Handler(api.Group("/auth"))
|
|
||||||
user.Handler(api.Group("/user"))
|
|
||||||
event.Handler(api.Group("/event"))
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,44 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"nixcn-cms/logger"
|
"nixcn-cms/api"
|
||||||
|
"nixcn-cms/middleware"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
log "github.com/sirupsen/logrus"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
"go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Start() {
|
func Start(ctx context.Context) {
|
||||||
r := gin.Default()
|
if !viper.GetBool("server.debug_mode") {
|
||||||
r.Use(logger.Gin(), gin.Recovery())
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
gin.DisableConsoleColor()
|
||||||
|
}
|
||||||
|
|
||||||
Router(r)
|
r := gin.New()
|
||||||
|
r.Use(otelgin.Middleware(viper.GetString("server.service_name")))
|
||||||
|
r.Use(middleware.GinLogger())
|
||||||
|
r.Use(gin.Recovery())
|
||||||
|
|
||||||
|
api.Handler(r.Group("/api/v1"))
|
||||||
|
|
||||||
// Start http server
|
// Start http server
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: viper.GetString("server.address"),
|
Addr: viper.GetString("server.address"),
|
||||||
Handler: r,
|
Handler: r,
|
||||||
|
BaseContext: func(net.Listener) context.Context { return ctx },
|
||||||
ReadTimeout: 10 * time.Second,
|
ReadTimeout: 10 * time.Second,
|
||||||
WriteTimeout: 10 * time.Second,
|
WriteTimeout: 10 * time.Second,
|
||||||
MaxHeaderBytes: 1 << 20,
|
MaxHeaderBytes: 1 << 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Starting server on " + viper.GetString("server.address"))
|
slog.InfoContext(ctx, "[Server] Starting server on "+viper.GetString("server.address"))
|
||||||
if err := server.ListenAndServe(); err != nil {
|
if err := server.ListenAndServe(); err != nil {
|
||||||
log.Errorf("Error starting server: %v\n", err)
|
slog.ErrorContext(ctx, "[Server] Error starting server!", "err", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
124
service/auth/exchange.go
Normal file
124
service/auth/exchange.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"nixcn-cms/data"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
|
"nixcn-cms/pkgs/authcode"
|
||||||
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ()
|
||||||
|
|
||||||
|
func Exchange(c *gin.Context) {
|
||||||
|
var exchangeReq struct {
|
||||||
|
ClientId string `json:"client_id"`
|
||||||
|
RedirectUri string `json:"redirect_uri"`
|
||||||
|
State string `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
err := c.ShouldBindJSON(&exchangeReq)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceExchange).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userIdOrig, ok := c.Get("user_id")
|
||||||
|
if !ok {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceExchange).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUnauthorized).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 401, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userId, err := uuid.Parse(userIdOrig.(string))
|
||||||
|
if err != nil {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceExchange).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUuidParseFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userData := new(data.User)
|
||||||
|
user, err := userData.GetByUserId(c, userId)
|
||||||
|
if err != nil {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceExchange).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthExchangeGetUserIdFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
code, err := authcode.NewAuthCode(c, exchangeReq.ClientId, user.Email)
|
||||||
|
if err != nil {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceExchange).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthExchangeCodeGenFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
url, err := url.Parse(exchangeReq.RedirectUri)
|
||||||
|
if err != nil {
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceExchange).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthExchangeInvalidRedirectUri).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
query := url.Query()
|
||||||
|
query.Set("code", code)
|
||||||
|
url.RawQuery = query.Encode()
|
||||||
|
|
||||||
|
exchangeResp := struct {
|
||||||
|
RedirectUri string `json:"redirect_uri"`
|
||||||
|
}{url.String()}
|
||||||
|
|
||||||
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusSuccess).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceExchange).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 200, errorCode, exchangeResp)
|
||||||
|
}
|
||||||
@@ -7,8 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Handler(r *gin.RouterGroup) {
|
func Handler(r *gin.RouterGroup) {
|
||||||
r.GET("/redirect", Redirect, middleware.JWTAuth(false))
|
r.GET("/redirect", Redirect)
|
||||||
r.POST("/magic", Magic)
|
r.POST("/magic", middleware.ApiVersionCheck(), Magic)
|
||||||
r.POST("/token", Token)
|
r.POST("/token", middleware.ApiVersionCheck(), Token)
|
||||||
r.POST("/refresh", Refresh)
|
r.POST("/refresh", middleware.ApiVersionCheck(), Refresh)
|
||||||
|
r.POST("/exchange", middleware.ApiVersionCheck(), middleware.JWTAuth(), Exchange)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
"nixcn-cms/pkgs/authcode"
|
"nixcn-cms/pkgs/authcode"
|
||||||
"nixcn-cms/pkgs/email"
|
"nixcn-cms/pkgs/email"
|
||||||
"nixcn-cms/pkgs/turnstile"
|
"nixcn-cms/pkgs/turnstile"
|
||||||
@@ -23,27 +24,59 @@ func Magic(c *gin.Context) {
|
|||||||
// Parse request
|
// Parse request
|
||||||
var req MagicRequest
|
var req MagicRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
utils.HttpResponse(c, 400, "", "invalid request")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceMagic).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cloudflare turnstile
|
// Cloudflare turnstile
|
||||||
ok, err := turnstile.VerifyTurnstile(req.TurnstileToken, c.ClientIP())
|
ok, err := turnstile.VerifyTurnstile(req.TurnstileToken, c.ClientIP())
|
||||||
if err != nil || !ok {
|
if err != nil || !ok {
|
||||||
utils.HttpResponse(c, 403, "", "turnstile failed")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceMagic).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthMagicTurnstileFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 403, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
code, err := authcode.NewAuthCode(req.ClientId, req.Email)
|
code, err := authcode.NewAuthCode(c, req.ClientId, req.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "code gen failed")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceMagic).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthMagicCodeGenFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
externalUrl := viper.GetString("server.external_url")
|
externalUrl := viper.GetString("server.external_url")
|
||||||
url, err := url.Parse(externalUrl)
|
url, err := url.Parse(externalUrl)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "invalid external url")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceMagic).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthMagicInvalidExternalUrl).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,22 +93,37 @@ func Magic(c *gin.Context) {
|
|||||||
uriData := struct {
|
uriData := struct {
|
||||||
Uri string `json:"uri"`
|
Uri string `json:"uri"`
|
||||||
}{url.String()}
|
}{url.String()}
|
||||||
|
|
||||||
utils.HttpResponse(c, 200, "", "magiclink sent", uriData)
|
utils.HttpResponse(c, 200, "", "magiclink sent", uriData)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
// Send email using resend
|
// Send email using resend
|
||||||
emailClient, err := email.NewSMTPClient()
|
emailClient, err := new(email.Client).NewSMTPClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "invalid email config")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceMagic).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthMagicInvalidEmailConfig).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
emailClient.Send(
|
emailClient.Send(
|
||||||
|
"NixCN CMS <cms@yuri.nix.org.cn>",
|
||||||
req.Email,
|
req.Email,
|
||||||
"NixCN CMS Email Verify",
|
"NixCN CMS Email Verify",
|
||||||
"<p>Click the link below to verify your email. This link will expire in 10 minutes.</p><a href="+url.String()+">"+url.String()+"</a>",
|
"<p>Click the link below to verify your email. This link will expire in 10 minutes.</p><a href="+url.String()+">"+url.String()+"</a>",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.HttpResponse(c, 200, "", "magic link sent")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceMagic).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 200, errorCode)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package auth
|
|||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"nixcn-cms/data"
|
"nixcn-cms/data"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
"nixcn-cms/pkgs/authcode"
|
"nixcn-cms/pkgs/authcode"
|
||||||
"nixcn-cms/utils"
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
@@ -14,71 +15,62 @@ import (
|
|||||||
func Redirect(c *gin.Context) {
|
func Redirect(c *gin.Context) {
|
||||||
clientId := c.Query("client_id")
|
clientId := c.Query("client_id")
|
||||||
if clientId == "" {
|
if clientId == "" {
|
||||||
utils.HttpResponse(c, 400, "", "invalid request")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
redirectUri := c.Query("redirect_uri")
|
redirectUri := c.Query("redirect_uri")
|
||||||
if redirectUri == "" {
|
if redirectUri == "" {
|
||||||
utils.HttpResponse(c, 400, "", "invalid request")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state := c.Query("state")
|
state := c.Query("state")
|
||||||
if state == "" {
|
if state == "" {
|
||||||
utils.HttpResponse(c, 400, "", "invalid request")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
code := c.Query("code")
|
code := c.Query("code")
|
||||||
if code == "" {
|
|
||||||
userIdOrig, ok := c.Get("user_id")
|
|
||||||
if !ok || userIdOrig == "" {
|
|
||||||
utils.HttpResponse(c, 401, "", "unauthorized")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userId, err := uuid.Parse(userIdOrig.(string))
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "failed to parse uuid")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userData := new(data.User)
|
|
||||||
user, err := userData.GetByUserId(userId)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "failed to get user id")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
code, err := authcode.NewAuthCode(clientId, user.Email)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "code gen failed")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
url, err := url.Parse(redirectUri)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 400, "", "invalid redirect uri")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
query := url.Query()
|
|
||||||
query.Set("code", code)
|
|
||||||
url.RawQuery = query.Encode()
|
|
||||||
|
|
||||||
c.Redirect(302, url.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify email token
|
// Verify email token
|
||||||
authCode, ok := authcode.VerifyAuthCode(code)
|
authCode, ok := authcode.VerifyAuthCode(c, code)
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.HttpResponse(c, 403, "", "invalid or expired token")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthRedirectTokenInvalid).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 403, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify if user exists
|
// Verify if user exists
|
||||||
userData := new(data.User)
|
userData := new(data.User)
|
||||||
user, err := userData.GetByEmail(authCode.Email)
|
user, err := userData.GetByEmail(c, authCode.Email)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == gorm.ErrRecordNotFound {
|
if err == gorm.ErrRecordNotFound {
|
||||||
@@ -88,38 +80,86 @@ func Redirect(c *gin.Context) {
|
|||||||
user.Email = authCode.Email
|
user.Email = authCode.Email
|
||||||
user.Username = user.UserId.String()
|
user.Username = user.UserId.String()
|
||||||
user.PermissionLevel = 10
|
user.PermissionLevel = 10
|
||||||
if err := user.Create(); err != nil {
|
if err := user.Create(c); err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "internal server error")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInternal).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
utils.HttpResponse(c, 500, "", "internal server error")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInternal).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
clientData := new(data.Client)
|
clientData := new(data.Client)
|
||||||
client, err := clientData.GetClientByClientId(clientId)
|
client, err := clientData.GetClientByClientId(c, clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 400, "", "client not found")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthRedirectClientNotFound).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.ValidateRedirectURI(redirectUri)
|
err = client.ValidateRedirectURI(redirectUri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 400, "", "redirect uri not match")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthRedirectUriMismatch).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newCode, err := authcode.NewAuthCode(clientId, authCode.Email)
|
newCode, err := authcode.NewAuthCode(c, clientId, authCode.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "internal server error")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInternal).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
url, err := url.Parse(redirectUri)
|
url, err := url.Parse(redirectUri)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 400, "", "invalid redirect uri")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRedirect).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthRedirectInvalidUri).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
query := url.Query()
|
query := url.Query()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
"nixcn-cms/pkgs/authtoken"
|
"nixcn-cms/pkgs/authtoken"
|
||||||
"nixcn-cms/utils"
|
"nixcn-cms/utils"
|
||||||
|
|
||||||
@@ -14,7 +15,15 @@ func Refresh(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
utils.HttpResponse(c, 400, "", "invalid request")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRefresh).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,15 +31,31 @@ func Refresh(c *gin.Context) {
|
|||||||
Application: viper.GetString("server.application"),
|
Application: viper.GetString("server.application"),
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, err := JwtTool.RefreshAccessToken(req.RefreshToken)
|
accessToken, err := JwtTool.RefreshAccessToken(c, req.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 401, "", "invalid refresh token")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRefresh).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthRefreshInvalidToken).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 401, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToken, err := JwtTool.RenewRefreshToken(req.RefreshToken)
|
refreshToken, err := JwtTool.RenewRefreshToken(c, req.RefreshToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "error renew refresh token")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRefresh).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthRefreshRenewFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,5 +64,12 @@ func Refresh(c *gin.Context) {
|
|||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token"`
|
||||||
}{accessToken, refreshToken}
|
}{accessToken, refreshToken}
|
||||||
|
|
||||||
utils.HttpResponse(c, 200, "", "success", tokenResp)
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceRefresh).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 200, errorCode, tokenResp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"nixcn-cms/data"
|
"nixcn-cms/data"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
"nixcn-cms/pkgs/authcode"
|
"nixcn-cms/pkgs/authcode"
|
||||||
"nixcn-cms/pkgs/authtoken"
|
"nixcn-cms/pkgs/authtoken"
|
||||||
"nixcn-cms/utils"
|
"nixcn-cms/utils"
|
||||||
@@ -19,20 +20,43 @@ func Token(c *gin.Context) {
|
|||||||
|
|
||||||
err := c.ShouldBindJSON(&req)
|
err := c.ShouldBindJSON(&req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 400, "", "invalid request")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceToken).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authCode, ok := authcode.VerifyAuthCode(req.Code)
|
authCode, ok := authcode.VerifyAuthCode(c, req.Code)
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.HttpResponse(c, 403, "", "invalid or expired token")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceToken).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthTokenInvalidToken).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 403, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
userData := new(data.User)
|
userData := new(data.User)
|
||||||
user, err := userData.GetByEmail(authCode.Email)
|
user, err := userData.GetByEmail(c, authCode.Email)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "internal server error")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceToken).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInternal).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,9 +64,17 @@ func Token(c *gin.Context) {
|
|||||||
JwtTool := authtoken.Token{
|
JwtTool := authtoken.Token{
|
||||||
Application: viper.GetString("server.application"),
|
Application: viper.GetString("server.application"),
|
||||||
}
|
}
|
||||||
accessToken, refreshToken, err := JwtTool.IssueTokens(authCode.ClientId, user.UserId)
|
accessToken, refreshToken, err := JwtTool.IssueTokens(c, authCode.ClientId, user.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "error generating tokens")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceToken).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.AuthTokenGenFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,5 +83,12 @@ func Token(c *gin.Context) {
|
|||||||
RefreshToken string `json:"refresh_token"`
|
RefreshToken string `json:"refresh_token"`
|
||||||
}{accessToken, refreshToken}
|
}{accessToken, refreshToken}
|
||||||
|
|
||||||
utils.HttpResponse(c, 200, "", "success", tokenResp)
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceAuth).
|
||||||
|
SetEndpoint(exception.EndpointAuthServiceToken).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 200, errorCode, tokenResp)
|
||||||
}
|
}
|
||||||
|
|||||||
8
service/common.go
Normal file
8
service/common.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import "nixcn-cms/internal/exception"
|
||||||
|
|
||||||
|
type CommonResult struct {
|
||||||
|
HttpCode int
|
||||||
|
Exception *exception.Builder
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@ package event
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"nixcn-cms/data"
|
"nixcn-cms/data"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
"nixcn-cms/utils"
|
"nixcn-cms/utils"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,31 +14,69 @@ func Checkin(c *gin.Context) {
|
|||||||
data := new(data.Attendance)
|
data := new(data.Attendance)
|
||||||
userIdOrig, ok := c.Get("user_id")
|
userIdOrig, ok := c.Get("user_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.HttpResponse(c, 403, "", "userid error")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckin).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorMissingUserId).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 403, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userId, err := uuid.Parse(userIdOrig.(string))
|
userId, err := uuid.Parse(userIdOrig.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "failed to parse uuid")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckin).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUuidParseFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get event id from query
|
// Get event id from query
|
||||||
eventIdOrig, ok := c.GetQuery("event_id")
|
eventIdOrig, ok := c.GetQuery("event_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.HttpResponse(c, 400, "", "undefinded event id")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckin).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse event id to uuid
|
// Parse event id to uuid
|
||||||
eventId, err := uuid.Parse(eventIdOrig)
|
eventId, err := uuid.Parse(eventIdOrig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "error parsing string to uuid")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckin).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUuidParseFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
data.UserId = userId
|
data.UserId = userId
|
||||||
code, err := data.GenCheckinCode(eventId)
|
code, err := data.GenCheckinCode(c, eventId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "error generating code")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckin).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.EventCheckinGenCodeFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,9 +93,17 @@ func CheckinSubmit(c *gin.Context) {
|
|||||||
c.ShouldBindJSON(&req)
|
c.ShouldBindJSON(&req)
|
||||||
|
|
||||||
attendanceData := new(data.Attendance)
|
attendanceData := new(data.Attendance)
|
||||||
err := attendanceData.VerifyCheckinCode(req.ChekinCode)
|
err := attendanceData.VerifyCheckinCode(c, req.ChekinCode)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 400, "", "error verify checkin code")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckinSubmit).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,34 +113,79 @@ func CheckinSubmit(c *gin.Context) {
|
|||||||
func CheckinQuery(c *gin.Context) {
|
func CheckinQuery(c *gin.Context) {
|
||||||
userIdOrig, ok := c.Get("user_id")
|
userIdOrig, ok := c.Get("user_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.HttpResponse(c, 400, "", "userid error")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckinQuery).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorMissingUserId).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userId, err := uuid.Parse(userIdOrig.(string))
|
userId, err := uuid.Parse(userIdOrig.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "failed to parse uuid")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckinQuery).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUuidParseFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
eventIdOrig, ok := c.GetQuery("event_id")
|
eventIdOrig, ok := c.GetQuery("event_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.HttpResponse(c, 400, "", "could not found event_id")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckinQuery).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
eventId, err := uuid.Parse(eventIdOrig)
|
eventId, err := uuid.Parse(eventIdOrig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 400, "", "event_id is not valid")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckinQuery).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
attendanceData := new(data.Attendance)
|
attendanceData := new(data.Attendance)
|
||||||
attendance, err := attendanceData.GetAttendance(userId, eventId)
|
attendance, err := attendanceData.GetAttendance(c, userId, eventId)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "database error")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckinQuery).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorDatabase).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
} else if attendance == nil {
|
} else if attendance == nil {
|
||||||
utils.HttpResponse(c, 404, "", "event checkin record not found")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckinQuery).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.EventCheckinQueryRecordNotFound).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 404, errorCode)
|
||||||
return
|
return
|
||||||
} else if attendance.CheckinAt.IsZero() {
|
} else if attendance.CheckinAt.IsZero() {
|
||||||
utils.HttpResponse(c, 200, "", "success", gin.H{"checkin_at": nil})
|
utils.HttpResponse(c, 200, "", "success", gin.H{"checkin_at": nil})
|
||||||
@@ -104,5 +196,12 @@ func CheckinQuery(c *gin.Context) {
|
|||||||
CheckinAt time.Time `json:"checkin_at"`
|
CheckinAt time.Time `json:"checkin_at"`
|
||||||
}{attendance.CheckinAt}
|
}{attendance.CheckinAt}
|
||||||
|
|
||||||
utils.HttpResponse(c, 200, "", "success", checkInAtResp)
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceCheckinQuery).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 200, errorCode, checkInAtResp)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Handler(r *gin.RouterGroup) {
|
func Handler(r *gin.RouterGroup) {
|
||||||
r.Use(middleware.JWTAuth(true), middleware.Permission(10))
|
r.Use(middleware.JWTAuth(), middleware.Permission(10))
|
||||||
r.GET("/info", Info)
|
r.GET("/info", Info)
|
||||||
r.GET("/checkin", Checkin)
|
r.GET("/checkin", Checkin)
|
||||||
r.GET("/checkin/query", CheckinQuery)
|
r.GET("/checkin/query", CheckinQuery)
|
||||||
r.POST("/checkin/submit", CheckinSubmit, middleware.Permission(20))
|
r.POST("/checkin/submit", middleware.Permission(20), CheckinSubmit)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package event
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"nixcn-cms/data"
|
"nixcn-cms/data"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
"nixcn-cms/utils"
|
"nixcn-cms/utils"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -13,20 +14,43 @@ func Info(c *gin.Context) {
|
|||||||
eventData := new(data.Event)
|
eventData := new(data.Event)
|
||||||
eventIdOrig, ok := c.GetQuery("event_id")
|
eventIdOrig, ok := c.GetQuery("event_id")
|
||||||
if !ok {
|
if !ok {
|
||||||
utils.HttpResponse(c, 400, "", "undefinded event id")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceInfo).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 400, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse event id
|
// Parse event id
|
||||||
eventId, err := uuid.Parse(eventIdOrig)
|
eventId, err := uuid.Parse(eventIdOrig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 500, "", "error parsing string to uuid")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceInfo).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUuidParseFailed).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 500, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
event, err := eventData.GetEventById(eventId)
|
event, err := eventData.GetEventById(c, eventId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.HttpResponse(c, 404, "", "event id not found")
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceInfo).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.EventInfoNotFound).
|
||||||
|
SetError(err).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 404, errorCode)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,5 +60,12 @@ func Info(c *gin.Context) {
|
|||||||
EndTime time.Time `json:"end_time"`
|
EndTime time.Time `json:"end_time"`
|
||||||
}{event.Name, event.StartTime, event.EndTime}
|
}{event.Name, event.StartTime, event.EndTime}
|
||||||
|
|
||||||
utils.HttpResponse(c, 200, "", "success", eventInfoResp)
|
errorCode := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceEvent).
|
||||||
|
SetEndpoint(exception.EndpointEventServiceInfo).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
Build(c)
|
||||||
|
utils.HttpResponse(c, 200, errorCode, eventInfoResp)
|
||||||
}
|
}
|
||||||
|
|||||||
469
service/user.go
Normal file
469
service/user.go
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
"nixcn-cms/data"
|
||||||
|
"nixcn-cms/internal/cryptography"
|
||||||
|
"nixcn-cms/internal/exception"
|
||||||
|
"strconv"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserService interface {
|
||||||
|
GetUserInfo(*UserInfoPayload) *UserInfoResult
|
||||||
|
UpdateUserInfo(*UserInfoPayload) *UserInfoResult
|
||||||
|
ListUsers(*UserListPayload) *UserListResult
|
||||||
|
GetUserFullTable(*UserTablePayload) *UserTableResult
|
||||||
|
CreateUser()
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserServiceImpl struct{}
|
||||||
|
|
||||||
|
func NewUserService() UserService {
|
||||||
|
return &UserServiceImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoData struct {
|
||||||
|
UserId uuid.UUID `json:"user_id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
Subtitle string `json:"subtitle"`
|
||||||
|
Avatar string `json:"avatar"`
|
||||||
|
Bio string `json:"bio"`
|
||||||
|
PermissionLevel uint `json:"permission_level"`
|
||||||
|
AllowPublic bool `json:"allow_public"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoPayload struct {
|
||||||
|
Context context.Context
|
||||||
|
UserId uuid.UUID
|
||||||
|
Data *UserInfoData
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserInfoResult struct {
|
||||||
|
Common CommonResult
|
||||||
|
Data *UserInfoData
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserInfo
|
||||||
|
func (self *UserServiceImpl) GetUserInfo(payload *UserInfoPayload) (result *UserInfoResult) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
userData, err := new(data.User).
|
||||||
|
GetByUserId(
|
||||||
|
payload.Context,
|
||||||
|
&payload.UserId,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceInfo).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorUserNotFound).
|
||||||
|
SetError(err).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 404,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceInfo).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
SetError(nil).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 200,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: &UserInfoData{
|
||||||
|
UserId: userData.UserId,
|
||||||
|
Email: userData.Email,
|
||||||
|
Username: userData.Username,
|
||||||
|
Nickname: userData.Nickname,
|
||||||
|
Subtitle: userData.Subtitle,
|
||||||
|
Avatar: userData.Avatar,
|
||||||
|
Bio: userData.Bio,
|
||||||
|
PermissionLevel: userData.PermissionLevel,
|
||||||
|
AllowPublic: userData.AllowPublic,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserInfo
|
||||||
|
func (self *UserServiceImpl) UpdateUserInfo(payload *UserInfoPayload) (result *UserInfoResult) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
userData := new(data.User).
|
||||||
|
SetNickname(payload.Data.Nickname).
|
||||||
|
SetSubtitle(payload.Data.Subtitle).
|
||||||
|
SetAvatar(payload.Data.Avatar).
|
||||||
|
SetBio(payload.Data.Bio).
|
||||||
|
SetAllowPublic(payload.Data.AllowPublic)
|
||||||
|
|
||||||
|
if payload.Data.Username != "" {
|
||||||
|
if len(payload.Data.Username) < 5 || len(payload.Data.Username) >= 255 {
|
||||||
|
execption := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 400,
|
||||||
|
Exception: execption,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userData.SetUsername(payload.Data.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Data.Nickname != "" {
|
||||||
|
if utf8.RuneCountInString(payload.Data.Nickname) > 24 {
|
||||||
|
execption := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 400,
|
||||||
|
Exception: execption,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userData.SetNickname(payload.Data.Nickname)
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Data.Subtitle != "" {
|
||||||
|
if utf8.RuneCountInString(payload.Data.Subtitle) > 32 {
|
||||||
|
execption := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceUpdate).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(nil).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 400,
|
||||||
|
Exception: execption,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userData.SetSubtitle(payload.Data.Subtitle)
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Data.Avatar != "" {
|
||||||
|
_, err := url.ParseRequestURI(payload.Data.Avatar)
|
||||||
|
if err != nil {
|
||||||
|
execption := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceUpdate).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 400,
|
||||||
|
Exception: execption,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userData.SetAvatar(payload.Data.Avatar)
|
||||||
|
}
|
||||||
|
|
||||||
|
if payload.Data.Bio != "" {
|
||||||
|
if !cryptography.IsBase64Std(payload.Data.Bio) {
|
||||||
|
execption := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceUpdate).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(nil).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 400,
|
||||||
|
Exception: execption,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
userData.Bio = payload.Data.Bio
|
||||||
|
}
|
||||||
|
|
||||||
|
err = userData.UpdateByUserID(payload.Context, &payload.UserId)
|
||||||
|
if err != nil {
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceUpdate).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorDatabase).
|
||||||
|
SetError(err).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 500,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceUpdate).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
SetError(nil).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserInfoResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 200,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserListPayload struct {
|
||||||
|
Context context.Context
|
||||||
|
Limit *string
|
||||||
|
Offset *string
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserListResult struct {
|
||||||
|
Common CommonResult
|
||||||
|
Data *[]data.UserSearchDoc `json:"user_list"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUsers
|
||||||
|
func (self *UserServiceImpl) ListUsers(payload *UserListPayload) (result *UserListResult) {
|
||||||
|
var limit string
|
||||||
|
if payload.Limit == nil || *payload.Limit == "" {
|
||||||
|
limit = "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset string
|
||||||
|
if payload.Offset == nil || *payload.Offset == "" {
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceList).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(nil).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserListResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 500,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
offset = *payload.Offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse string to int64
|
||||||
|
limitNum, err := strconv.ParseInt(limit, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceList).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserListResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 400,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetNum, err := strconv.ParseInt(offset, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusUser).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceList).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorInvalidInput).
|
||||||
|
SetError(err).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserListResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 400,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user list from search engine
|
||||||
|
userList, err := new(data.User).
|
||||||
|
FastListUsers(payload.Context, &limitNum, &offsetNum)
|
||||||
|
if err != nil {
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceList).
|
||||||
|
SetType(exception.TypeSpecific).
|
||||||
|
SetOriginal(exception.UserListMeilisearchFailed).
|
||||||
|
SetError(err).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserListResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 500,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceList).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
SetError(nil).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserListResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 200,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: userList,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserTablePayload struct {
|
||||||
|
Context context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserTableResult struct {
|
||||||
|
Common CommonResult
|
||||||
|
Data *[]data.User `json:"user_table"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListUserFullTable
|
||||||
|
func (self *UserServiceImpl) GetUserFullTable(payload *UserTablePayload) (result *UserTableResult) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
userFullTable, err := new(data.User).
|
||||||
|
GetFullTable(payload.Context)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceFull).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonErrorDatabase).
|
||||||
|
SetError(err).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserTableResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 500,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
exception := new(exception.Builder).
|
||||||
|
SetStatus(exception.StatusServer).
|
||||||
|
SetService(exception.ServiceUser).
|
||||||
|
SetEndpoint(exception.EndpointUserServiceFull).
|
||||||
|
SetType(exception.TypeCommon).
|
||||||
|
SetOriginal(exception.CommonSuccess).
|
||||||
|
SetError(nil).
|
||||||
|
Throw(payload.Context)
|
||||||
|
|
||||||
|
result = &UserTableResult{
|
||||||
|
Common: CommonResult{
|
||||||
|
HttpCode: 200,
|
||||||
|
Exception: exception,
|
||||||
|
},
|
||||||
|
Data: userFullTable,
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateUser
|
||||||
|
func (self *UserServiceImpl) CreateUser() {}
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nixcn-cms/data"
|
|
||||||
"nixcn-cms/utils"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Full(c *gin.Context) {
|
|
||||||
userIdOrig, ok := c.Get("user_id")
|
|
||||||
if !ok {
|
|
||||||
utils.HttpResponse(c, 403, "", "userid error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userId, err := uuid.Parse(userIdOrig.(string))
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "failed to parse uuid")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userData, err := new(data.User).GetByUserId(userId)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 404, "", "user not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
users, err := userData.GetFullTable()
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "database error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userFullResp := struct {
|
|
||||||
UserTable *[]data.User `json:"user_table"`
|
|
||||||
}{users}
|
|
||||||
utils.HttpResponse(c, 200, "", "success", userFullResp)
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nixcn-cms/middleware"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Handler(r *gin.RouterGroup) {
|
|
||||||
r.Use(middleware.JWTAuth(true), middleware.Permission(5))
|
|
||||||
r.GET("/info", Info)
|
|
||||||
r.PATCH("/update", Update)
|
|
||||||
r.GET("/list", List, middleware.Permission(20))
|
|
||||||
r.POST("/full", Full, middleware.Permission(40))
|
|
||||||
r.POST("/create", Create, middleware.Permission(50))
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nixcn-cms/data"
|
|
||||||
"nixcn-cms/utils"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Info(c *gin.Context) {
|
|
||||||
userData := new(data.User)
|
|
||||||
userIdOrig, ok := c.Get("user_id")
|
|
||||||
if !ok {
|
|
||||||
utils.HttpResponse(c, 403, "", "userid error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userId, err := uuid.Parse(userIdOrig.(string))
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "failed to parse uuid")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user from database
|
|
||||||
user, err := userData.GetByUserId(userId)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 404, "", "user not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
userInfoResp := struct {
|
|
||||||
UserId uuid.UUID `json:"user_id"`
|
|
||||||
Email string `json:"email"`
|
|
||||||
Username string `json:"username"`
|
|
||||||
Nickname string `json:"nickname"`
|
|
||||||
Subtitle string `json:"subtitle"`
|
|
||||||
Avatar string `json:"avatar"`
|
|
||||||
Bio string `json:"bio"`
|
|
||||||
PermissionLevel uint `json:"permission_level"`
|
|
||||||
}{user.UserId, user.Email, user.Username, user.Nickname, user.Subtitle, user.Avatar, user.Bio, user.PermissionLevel}
|
|
||||||
|
|
||||||
utils.HttpResponse(c, 200, "", "success", userInfoResp)
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nixcn-cms/data"
|
|
||||||
"nixcn-cms/utils"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func List(c *gin.Context) {
|
|
||||||
// Get limit and offset from query
|
|
||||||
limit, ok := c.GetQuery("limit")
|
|
||||||
if !ok {
|
|
||||||
limit = "0"
|
|
||||||
}
|
|
||||||
offset, ok := c.GetQuery("offset")
|
|
||||||
if !ok {
|
|
||||||
utils.HttpResponse(c, 400, "", "offset not found")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse string to int64
|
|
||||||
limitNum, err := strconv.ParseInt(limit, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 400, "", "parse string to int error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
offsetNum, err := strconv.ParseInt(offset, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 400, "", "parse string to int error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get user list from search engine
|
|
||||||
list, err := new(data.User).FastListUsers(limitNum, offsetNum)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "failed list users from meilisearch")
|
|
||||||
}
|
|
||||||
|
|
||||||
userListResp := struct {
|
|
||||||
List *[]data.UserSearchDoc `json:"list"`
|
|
||||||
}{list}
|
|
||||||
utils.HttpResponse(c, 200, "", "success", userListResp)
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
package user
|
|
||||||
|
|
||||||
import (
|
|
||||||
"nixcn-cms/data"
|
|
||||||
"nixcn-cms/internal/cryptography"
|
|
||||||
"nixcn-cms/utils"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/google/uuid"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Update(c *gin.Context) {
|
|
||||||
// New user model
|
|
||||||
userIdOrig, ok := c.Get("user_id")
|
|
||||||
if !ok {
|
|
||||||
utils.HttpResponse(c, 403, "", "userid error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userId, err := uuid.Parse(userIdOrig.(string))
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "failed to parse uuid")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var ReqInfo data.User
|
|
||||||
c.BindJSON(&ReqInfo)
|
|
||||||
|
|
||||||
// Get user info
|
|
||||||
userData, err := new(data.User).GetByUserId(userId)
|
|
||||||
if err != nil {
|
|
||||||
utils.HttpResponse(c, 500, "", "failed to find user")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(ReqInfo.Email) < 5 || len(ReqInfo.Email) >= 255 {
|
|
||||||
utils.HttpResponse(c, 400, "", "invilad email")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userData.Email = ReqInfo.Email
|
|
||||||
|
|
||||||
if len(ReqInfo.Username) < 5 || len(ReqInfo.Username) >= 255 {
|
|
||||||
utils.HttpResponse(c, 400, "", "invilad user name")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
userData.Username = ReqInfo.Username
|
|
||||||
|
|
||||||
userData.Nickname = ReqInfo.Nickname
|
|
||||||
userData.Subtitle = ReqInfo.Subtitle
|
|
||||||
userData.Avatar = ReqInfo.Avatar
|
|
||||||
|
|
||||||
if ReqInfo.Bio != "" {
|
|
||||||
if !cryptography.IsBase64Std(ReqInfo.Bio) {
|
|
||||||
utils.HttpResponse(c, 400, "", "invalid base64")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userData.Bio = ReqInfo.Bio
|
|
||||||
|
|
||||||
// Update user info
|
|
||||||
userData.UpdateByUserID(userId)
|
|
||||||
|
|
||||||
utils.HttpResponse(c, 200, "", "success")
|
|
||||||
}
|
|
||||||
91
tracer/otel_tracer.go
Normal file
91
tracer/otel_tracer.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package tracer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"go.opentelemetry.io/otel"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc"
|
||||||
|
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
|
||||||
|
"go.opentelemetry.io/otel/log/global"
|
||||||
|
"go.opentelemetry.io/otel/propagation"
|
||||||
|
sdklog "go.opentelemetry.io/otel/sdk/log"
|
||||||
|
sdkmetric "go.opentelemetry.io/otel/sdk/metric"
|
||||||
|
"go.opentelemetry.io/otel/sdk/resource"
|
||||||
|
sdktrace "go.opentelemetry.io/otel/sdk/trace"
|
||||||
|
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init(ctx context.Context) func(context.Context) error {
|
||||||
|
endpoint := viper.GetString("tracer.otel_controller_endpoint")
|
||||||
|
if endpoint == "" {
|
||||||
|
endpoint = "localhost:4317"
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := resource.New(ctx,
|
||||||
|
resource.WithAttributes(
|
||||||
|
semconv.ServiceNameKey.String(viper.GetString("server.service_name")),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("[OTEL] Failed to create resource", "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
traceExporter, _ := otlptracegrpc.New(ctx,
|
||||||
|
otlptracegrpc.WithEndpoint(endpoint),
|
||||||
|
otlptracegrpc.WithInsecure(),
|
||||||
|
)
|
||||||
|
tp := sdktrace.NewTracerProvider(
|
||||||
|
sdktrace.WithBatcher(traceExporter),
|
||||||
|
sdktrace.WithResource(res),
|
||||||
|
sdktrace.WithSampler(sdktrace.AlwaysSample()),
|
||||||
|
)
|
||||||
|
otel.SetTracerProvider(tp)
|
||||||
|
|
||||||
|
metricExporter, _ := otlpmetricgrpc.New(ctx,
|
||||||
|
otlpmetricgrpc.WithEndpoint(endpoint),
|
||||||
|
otlpmetricgrpc.WithInsecure(),
|
||||||
|
)
|
||||||
|
mp := sdkmetric.NewMeterProvider(
|
||||||
|
sdkmetric.WithResource(res),
|
||||||
|
sdkmetric.WithReader(sdkmetric.NewPeriodicReader(metricExporter,
|
||||||
|
sdkmetric.WithInterval(5*time.Second))),
|
||||||
|
)
|
||||||
|
otel.SetMeterProvider(mp)
|
||||||
|
|
||||||
|
logExporter, _ := otlploggrpc.New(ctx,
|
||||||
|
otlploggrpc.WithEndpoint(endpoint),
|
||||||
|
otlploggrpc.WithInsecure(),
|
||||||
|
)
|
||||||
|
lp := sdklog.NewLoggerProvider(
|
||||||
|
sdklog.WithResource(res),
|
||||||
|
sdklog.WithProcessor(sdklog.NewBatchProcessor(logExporter)),
|
||||||
|
)
|
||||||
|
global.SetLoggerProvider(lp)
|
||||||
|
|
||||||
|
otel.SetTextMapPropagator(propagation.NewCompositeTextMapPropagator(
|
||||||
|
propagation.TraceContext{},
|
||||||
|
propagation.Baggage{},
|
||||||
|
))
|
||||||
|
|
||||||
|
return func(shutCtx context.Context) error {
|
||||||
|
var errs []error
|
||||||
|
if err := tp.Shutdown(shutCtx); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
if err := mp.Shutdown(shutCtx); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
if err := lp.Shutdown(shutCtx); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errs[0]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +1,54 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/goccy/go-json"
|
||||||
|
)
|
||||||
|
|
||||||
type RespStatus struct {
|
type RespStatus struct {
|
||||||
Code int `json:"code"`
|
Code int `json:"code"`
|
||||||
ErrorId string `json:"error_id"`
|
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
|
ErrorId string `json:"error_id"`
|
||||||
Data any `json:"data"`
|
Data any `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpResponse(c *gin.Context, code int, errorId string, status string, data ...any) {
|
func render(c *gin.Context, code int, errId string, data []any, abort bool) {
|
||||||
var resp = RespStatus{
|
resp := RespStatus{
|
||||||
Code: code,
|
Code: code,
|
||||||
ErrorId: errorId,
|
Status: http.StatusText(code),
|
||||||
Status: status,
|
ErrorId: errId,
|
||||||
Data: data,
|
|
||||||
}
|
}
|
||||||
c.JSON(code, resp)
|
|
||||||
|
switch len(data) {
|
||||||
|
case 0:
|
||||||
|
resp.Data = nil
|
||||||
|
case 1:
|
||||||
|
resp.Data = data[0]
|
||||||
|
default:
|
||||||
|
resp.Data = data
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(resp)
|
||||||
|
if err != nil {
|
||||||
|
c.Status(http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Header("Content-Type", "application/json; charset=utf-8")
|
||||||
|
if abort {
|
||||||
|
c.AbortWithStatus(code)
|
||||||
|
} else {
|
||||||
|
c.Status(code)
|
||||||
|
}
|
||||||
|
_, _ = c.Writer.Write(jsonBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func HttpAbort(c *gin.Context, code int, errorId string, status string, data ...any) {
|
func HttpResponse(c *gin.Context, code int, errId string, data ...any) {
|
||||||
var resp = RespStatus{
|
render(c, code, errId, data, false)
|
||||||
Code: code,
|
}
|
||||||
ErrorId: errorId,
|
|
||||||
Status: status,
|
func HttpAbort(c *gin.Context, code int, errId string, data ...any) {
|
||||||
Data: data,
|
render(c, code, errId, data, true)
|
||||||
}
|
|
||||||
c.AbortWithStatusJSON(code, resp)
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user