init repo
This commit is contained in:
@@ -0,0 +1,39 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
config.yaml
|
||||||
|
.drone.yml
|
||||||
+146
@@ -0,0 +1,146 @@
|
|||||||
|
kind: pipeline
|
||||||
|
name: build-and-publish
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: download modules
|
||||||
|
image: git.vaala.cloud/vaalacat/golang:1.21-alpine
|
||||||
|
commands:
|
||||||
|
- sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||||
|
- apk update --no-cache && apk add --no-cache tzdata git
|
||||||
|
- CGO_ENABLED=0 GOPRIVATE=git.vaala.cloud GOPROXY=https://goproxy.cn,https://proxy.golang.org,direct go mod download
|
||||||
|
- mkdir -p etc
|
||||||
|
- cp /etc/ssl/certs/ca-certificates.crt ./etc/ca-certificates.crt
|
||||||
|
- cp /usr/share/zoneinfo/Asia/Shanghai ./etc/Shanghai
|
||||||
|
volumes:
|
||||||
|
- name: gocache
|
||||||
|
path: /go/pkg/mod
|
||||||
|
- name: build
|
||||||
|
path: /tmp/app
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
- promote
|
||||||
|
- rollback
|
||||||
|
- name: build frontend
|
||||||
|
image: git.vaala.cloud/vaalacat/node:20-alpine
|
||||||
|
commands:
|
||||||
|
- sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories
|
||||||
|
- apk update --no-cache && apk add --no-cache tzdata git openssh curl
|
||||||
|
- mkdir -p ~/.ssh
|
||||||
|
- npm install -g pnpm
|
||||||
|
- ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- corepack enable
|
||||||
|
- corepack prepare pnpm@latest-7 --activate
|
||||||
|
- pnpm config set store-dir /root/.pnpm-store
|
||||||
|
|
||||||
|
- pnpm install --no-frozen-lockfile
|
||||||
|
- pnpm build
|
||||||
|
# - curl -sSf https://sshx.io/get | sh
|
||||||
|
# - sshx
|
||||||
|
# - yarn install
|
||||||
|
# - yarn run build
|
||||||
|
volumes:
|
||||||
|
- name: nodecache
|
||||||
|
path: /root/.pnpm-store
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
- promote
|
||||||
|
- rollback
|
||||||
|
- name: build - amd64
|
||||||
|
image: git.vaala.cloud/vaalacat/golang:1.21-alpine
|
||||||
|
commands:
|
||||||
|
- CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOPRIVATE=git.vaala.cloud GOPROXY=https://goproxy.cn,https://proxy.golang.org,direct go build -ldflags="-s -w" -o toyboom-server-amd64 cmd/*.go
|
||||||
|
volumes:
|
||||||
|
- name: gocache
|
||||||
|
path: /go/pkg/mod
|
||||||
|
- name: build
|
||||||
|
path: /tmp/app
|
||||||
|
depends_on:
|
||||||
|
- build frontend
|
||||||
|
- download modules
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
- promote
|
||||||
|
- rollback
|
||||||
|
- name: build - arm64
|
||||||
|
image: git.vaala.cloud/vaalacat/golang:1.21-alpine
|
||||||
|
commands:
|
||||||
|
- CGO_ENABLED=0 GOOS=linux GOARCH=arm64 GOPRIVATE=git.vaala.cloud GOPROXY=https://goproxy.cn,https://proxy.golang.org,direct go build -ldflags="-s -w" -o toyboom-server-arm64 cmd/*.go
|
||||||
|
volumes:
|
||||||
|
- name: gocache
|
||||||
|
path: /go/pkg/mod
|
||||||
|
- name: build
|
||||||
|
path: /tmp/app
|
||||||
|
depends_on:
|
||||||
|
- build frontend
|
||||||
|
- download modules
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- pull_request
|
||||||
|
- promote
|
||||||
|
- rollback
|
||||||
|
|
||||||
|
- name: publish - amd64
|
||||||
|
image: git.vaala.cloud/vaalacat/drone-docker-buildx:24
|
||||||
|
privileged: true
|
||||||
|
settings:
|
||||||
|
debug: false
|
||||||
|
platforms:
|
||||||
|
- linux/amd64
|
||||||
|
build_args:
|
||||||
|
- ARCH=amd64
|
||||||
|
repo: git.vaala.cloud/sharkai/toyboom-server
|
||||||
|
tags:
|
||||||
|
- amd64-c3e691e3
|
||||||
|
registry:
|
||||||
|
from_secret: DOCKER_REGISTRY
|
||||||
|
username:
|
||||||
|
from_secret: DOCKER_USERNAME
|
||||||
|
password:
|
||||||
|
from_secret: DOCKER_PASSWORD
|
||||||
|
depends_on:
|
||||||
|
- build - amd64
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- promote
|
||||||
|
- rollback
|
||||||
|
target:
|
||||||
|
- production
|
||||||
|
- name: publish - arm64
|
||||||
|
image: git.vaala.cloud/vaalacat/drone-docker-buildx:24
|
||||||
|
privileged: true
|
||||||
|
settings:
|
||||||
|
debug: false
|
||||||
|
platforms:
|
||||||
|
- linux/arm64
|
||||||
|
build_args:
|
||||||
|
- ARCH=arm64
|
||||||
|
repo: git.vaala.cloud/sharkai/toyboom-server
|
||||||
|
tags:
|
||||||
|
- arm64-c3e691e3
|
||||||
|
registry:
|
||||||
|
from_secret: DOCKER_REGISTRY
|
||||||
|
username:
|
||||||
|
from_secret: DOCKER_USERNAME
|
||||||
|
password:
|
||||||
|
from_secret: DOCKER_PASSWORD
|
||||||
|
depends_on:
|
||||||
|
- build - arm64
|
||||||
|
when:
|
||||||
|
event:
|
||||||
|
- promote
|
||||||
|
- rollback
|
||||||
|
target:
|
||||||
|
- production
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
temp: {}
|
||||||
|
- name: gocache
|
||||||
|
host:
|
||||||
|
path: /tmp/drone/github.com/nose7en/ToyBoomServer/gocache
|
||||||
|
- name: nodecache
|
||||||
|
host:
|
||||||
|
path: /tmp/drone/github.com/nose7en/ToyBoomServer/nodecache
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"extends": "next/core-web-vitals"
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
.yarn/install-state.gz
|
||||||
|
/.vscode
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
/test
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
config.yaml
|
||||||
|
|
||||||
|
cmd/out
|
||||||
|
tmp
|
||||||
|
*.http
|
||||||
|
sadsad
|
||||||
|
cmd/*.exe
|
||||||
|
test
|
||||||
|
|
||||||
|
*.db
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
FROM alpine
|
||||||
|
|
||||||
|
ARG ARCH
|
||||||
|
|
||||||
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
|
||||||
|
apk update --no-cache && apk --no-cache add curl bash fuse3 sqlite
|
||||||
|
|
||||||
|
ENV TZ Asia/Shanghai
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ./toyboom-server-${ARCH} /app/toyboom-server
|
||||||
|
COPY ./etc /app/etc
|
||||||
|
|
||||||
|
RUN ln -sf /app/etc/Shanghai /etc/localtime && mv /app/etc/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
|
||||||
|
|
||||||
|
CMD [ "/app/toyboom-server" ]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
FROM golang:1.22 AS builder
|
||||||
|
|
||||||
|
WORKDIR $GOPATH/src/toyboom-server
|
||||||
|
COPY . .
|
||||||
|
RUN mkdir /app && \
|
||||||
|
CGO_ENABLED=1 GOPROXY=https://goproxy.cn,direct go build -o toyboom-server main.go && \
|
||||||
|
cp toyboom-server /app/
|
||||||
|
|
||||||
|
FROM golang:1.22
|
||||||
|
COPY --from=builder /app/toyboom-server /app/toyboom-server
|
||||||
|
EXPOSE 8080
|
||||||
|
ENTRYPOINT [ "/app/toyboom-server" ]
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Toyboom Server
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── Dockerfile
|
||||||
|
├── Dockerfile.standalone
|
||||||
|
├── README.md
|
||||||
|
├── biz
|
||||||
|
│ ├── handler.go
|
||||||
|
│ ├── static.go
|
||||||
|
│ └── user
|
||||||
|
│ ├── create.go
|
||||||
|
│ └── get.go
|
||||||
|
├── cmd
|
||||||
|
│ ├── db.go
|
||||||
|
│ └── main.go
|
||||||
|
├── common
|
||||||
|
│ ├── const.go
|
||||||
|
│ ├── context.go
|
||||||
|
│ ├── helper.go
|
||||||
|
│ ├── logger.go
|
||||||
|
│ ├── logger_remote.go
|
||||||
|
│ ├── request.go
|
||||||
|
│ ├── response.go
|
||||||
|
│ └── result.go
|
||||||
|
├── config
|
||||||
|
│ ├── helper.go
|
||||||
|
│ └── settings.go
|
||||||
|
├── dao
|
||||||
|
│ ├── interface.go
|
||||||
|
│ └── user.go
|
||||||
|
├── defs
|
||||||
|
│ ├── entity.go
|
||||||
|
│ ├── request.go
|
||||||
|
│ ├── response.go
|
||||||
|
│ └── user_info.go
|
||||||
|
├── go.mod
|
||||||
|
├── go.sum
|
||||||
|
├── middleware
|
||||||
|
│ ├── auth.go
|
||||||
|
│ └── init.go
|
||||||
|
├── models
|
||||||
|
│ ├── models.go
|
||||||
|
│ └── user.go
|
||||||
|
├── next.config.mjs
|
||||||
|
├── rpc
|
||||||
|
│ ├── apple.go
|
||||||
|
│ └── manager.go
|
||||||
|
├── services
|
||||||
|
│ └── api
|
||||||
|
│ └── handler.go
|
||||||
|
├── storage
|
||||||
|
│ └── db.go
|
||||||
|
├── toyboom.db
|
||||||
|
├── utils
|
||||||
|
│ ├── base64.go
|
||||||
|
│ ├── crypto.go
|
||||||
|
│ ├── idgen.go
|
||||||
|
│ └── jwt.go
|
||||||
|
└── watcher
|
||||||
|
└── corn_service.go
|
||||||
|
```
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package biz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nose7en/ToyBoomServer/biz/user"
|
||||||
|
"github.com/nose7en/ToyBoomServer/common"
|
||||||
|
"github.com/nose7en/ToyBoomServer/config"
|
||||||
|
"github.com/nose7en/ToyBoomServer/middleware"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Router() *gin.Engine {
|
||||||
|
r := gin.Default()
|
||||||
|
|
||||||
|
api := r.Group("/api")
|
||||||
|
v1 := api.Group("/v1")
|
||||||
|
|
||||||
|
userRouter := v1.Group("/user")
|
||||||
|
{
|
||||||
|
userRouter.POST("/create", middleware.ValidateAppleAppToken(), common.Wrapper(user.CreateUser))
|
||||||
|
userRouter.GET("/info", middleware.ValidateAppleAppToken(), common.Wrapper(user.GetUserInfo))
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.IsDebug() {
|
||||||
|
// for debug
|
||||||
|
v1.GET("/ping", middleware.ValidateAppleAppToken(), func(ctx *gin.Context) { ctx.JSON(200, gin.H{"message": "pong"}) })
|
||||||
|
}
|
||||||
|
return r
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package biz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"io/fs"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/static"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type embedFileSystem struct {
|
||||||
|
http.FileSystem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e embedFileSystem) Exists(prefix string, path string) bool {
|
||||||
|
_, err := e.Open(path)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
|
||||||
|
fsys, err := fs.Sub(fsEmbed, targetPath)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return embedFileSystem{
|
||||||
|
FileSystem: http.FS(fsys),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandleStaticFile(router *gin.Engine, f embed.FS) {
|
||||||
|
root := EmbedFolder(f, "out")
|
||||||
|
|
||||||
|
router.Use(static.Serve("/", root))
|
||||||
|
|
||||||
|
staticServer := static.Serve("/", root)
|
||||||
|
router.NoRoute(func(c *gin.Context) {
|
||||||
|
if c.Request.Method == http.MethodGet &&
|
||||||
|
!strings.ContainsRune(c.Request.URL.Path, '.') &&
|
||||||
|
!strings.HasPrefix(c.Request.URL.Path, "/v1/") {
|
||||||
|
if strings.HasSuffix(c.Request.URL.Path, "/") {
|
||||||
|
c.Request.URL.Path += "index.html"
|
||||||
|
staticServer(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(c.Request.URL.Path, ".html") {
|
||||||
|
c.Request.URL.Path += ".html"
|
||||||
|
staticServer(c)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/common"
|
||||||
|
"github.com/nose7en/ToyBoomServer/dao"
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
"github.com/nose7en/ToyBoomServer/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CreateUser(c context.Context, req *defs.CommonRequest) (*defs.CommonResponse, error) {
|
||||||
|
userInfo := common.GetUser(c)
|
||||||
|
newUser := &models.User{}
|
||||||
|
newUser.FillWithUserInfo(userInfo)
|
||||||
|
|
||||||
|
if err := dao.NewMutation().CreateUser(newUser); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &defs.CommonResponse{
|
||||||
|
Status: &defs.Status{Code: defs.RespCode_SUCCESS, Message: defs.RespMessage_SUCCESS},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package user
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/common"
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetUserInfo(c context.Context, req *defs.CommonRequest) (resp *defs.GetUserInfoResponse, err error) {
|
||||||
|
userInfo := common.GetUser(c)
|
||||||
|
return &defs.GetUserInfoResponse{
|
||||||
|
User: lo.ToPtr(userInfo.ToUser()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/common"
|
||||||
|
"github.com/nose7en/ToyBoomServer/config"
|
||||||
|
"github.com/nose7en/ToyBoomServer/models"
|
||||||
|
"github.com/nose7en/ToyBoomServer/storage"
|
||||||
|
|
||||||
|
"github.com/glebarez/sqlite"
|
||||||
|
"gorm.io/driver/mysql"
|
||||||
|
"gorm.io/driver/postgres"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func initDatabase() {
|
||||||
|
c := context.Background()
|
||||||
|
|
||||||
|
common.Logger(c).Infof("start to init database, type: %s", config.GetSettings().DB.Type)
|
||||||
|
storage.MustInitDBManager(nil, config.GetSettings().DB.Type)
|
||||||
|
|
||||||
|
switch config.GetSettings().DB.Type {
|
||||||
|
case "sqlite":
|
||||||
|
if sqlitedb, err := gorm.Open(sqlite.Open(config.GetSettings().DB.DSN), &gorm.Config{}); err != nil {
|
||||||
|
common.Logger(c).Panic(err)
|
||||||
|
} else {
|
||||||
|
storage.GetDBManager().SetDB("sqlite", storage.DefaultDBName, sqlitedb)
|
||||||
|
common.Logger(c).Infof("init database success, data location: [%s]", config.GetSettings().DB.DSN)
|
||||||
|
}
|
||||||
|
case "mysql":
|
||||||
|
if mysqlDB, err := gorm.Open(mysql.Open(config.GetSettings().DB.DSN), &gorm.Config{}); err != nil {
|
||||||
|
common.Logger(c).Panic(err)
|
||||||
|
} else {
|
||||||
|
storage.GetDBManager().SetDB("mysql", storage.DefaultDBName, mysqlDB)
|
||||||
|
common.Logger(c).Infof("init database success, data type: [%s]", "mysql")
|
||||||
|
}
|
||||||
|
case "postgres":
|
||||||
|
if postgresDB, err := gorm.Open(postgres.Open(config.GetSettings().DB.DSN), &gorm.Config{}); err != nil {
|
||||||
|
common.Logger(c).Panic(err)
|
||||||
|
} else {
|
||||||
|
storage.GetDBManager().SetDB("postgres", storage.DefaultDBName, postgresDB)
|
||||||
|
common.Logger(c).Infof("init database success, data type: [%s]", "postgres")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
common.Logger(c).Panicf("currently unsupported database type: %s", config.GetSettings().DB.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.GetDBManager().Init(models.Models()...)
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/biz"
|
||||||
|
"github.com/nose7en/ToyBoomServer/common"
|
||||||
|
"github.com/nose7en/ToyBoomServer/config"
|
||||||
|
"github.com/nose7en/ToyBoomServer/middleware"
|
||||||
|
"github.com/nose7en/ToyBoomServer/rpc"
|
||||||
|
"github.com/nose7en/ToyBoomServer/services/api"
|
||||||
|
|
||||||
|
"github.com/sourcegraph/conc"
|
||||||
|
)
|
||||||
|
|
||||||
|
// go:embed all:out/*
|
||||||
|
// var fs embed.FS
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
c := context.Background()
|
||||||
|
|
||||||
|
config.InitSettings()
|
||||||
|
rpc.InitManager()
|
||||||
|
middleware.Init()
|
||||||
|
initDatabase()
|
||||||
|
|
||||||
|
// ------------ biz init ----------------
|
||||||
|
// --------------------------------------
|
||||||
|
|
||||||
|
r := biz.Router()
|
||||||
|
// biz.HandleStaticFile(r, fs, ar)
|
||||||
|
|
||||||
|
apiService := api.NewAPIHandler(config.GetSettings().APIListenAddr)
|
||||||
|
apiService.Init(r)
|
||||||
|
// watherService := watcher.NewClient(func() error {
|
||||||
|
// return nil
|
||||||
|
// })
|
||||||
|
|
||||||
|
common.Logger(c).Infof("------------------------------------------------")
|
||||||
|
common.Logger(c).Infof("init services done, start to run all services...")
|
||||||
|
common.Logger(c).Infof("------------------------------------------------")
|
||||||
|
|
||||||
|
var wg conc.WaitGroup
|
||||||
|
wg.Go(apiService.Run)
|
||||||
|
// wg.Go(watherService.Run)
|
||||||
|
wg.Wait()
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
const (
|
||||||
|
AuthorizationKey = "authorization"
|
||||||
|
SetAuthorizationKey = "x-set-authorization"
|
||||||
|
MsgKey = "msg"
|
||||||
|
DataKey = "data"
|
||||||
|
UserIDKey = "x-toyboom-userid"
|
||||||
|
UserNameKey = "x-toyboom-username"
|
||||||
|
CommandKey = "x-toyboom-command"
|
||||||
|
EndpointKey = "endpoint"
|
||||||
|
IpAddrKey = "ipaddr"
|
||||||
|
HeaderKey = "header"
|
||||||
|
MethodKey = "method"
|
||||||
|
UAKey = "User-Agent"
|
||||||
|
ContentTypeKey = "Content-Type"
|
||||||
|
TraceIDKey = "TraceID"
|
||||||
|
TokenKey = "token"
|
||||||
|
ErrKey = "err"
|
||||||
|
UserInfoKey = "x-toyboom-userinfo"
|
||||||
|
HostKey = "Host"
|
||||||
|
LogtoWebHookSignKey = "Logto-Signature-Sha-256"
|
||||||
|
NormalUIDKey = "uid"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrInvalidRequest = "invalid request"
|
||||||
|
ErrUserInfoNotValid = "user info not valid"
|
||||||
|
ErrInternalError = "internal error"
|
||||||
|
ErrParamNotValid = "param not valid"
|
||||||
|
ErrDB = "database error"
|
||||||
|
ErrNotFound = "data not found"
|
||||||
|
ErrCodeNotFound = "code not found"
|
||||||
|
ErrCodeAlreadyUsed = "code already used"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ReqSuccess = "success"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
TimeLayout = time.RFC3339
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusSuccess = "success"
|
||||||
|
StatusFailed = "failed"
|
||||||
|
StatusDone = "done"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
CliTypeServer = "server"
|
||||||
|
CliTypeClient = "client"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultServerID = "default"
|
||||||
|
DefaultAdminUserID = 1
|
||||||
|
)
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetUser(c context.Context) defs.UserGettable {
|
||||||
|
val := c.Value(UserInfoKey)
|
||||||
|
if val == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, ok := val.(defs.UserGettable)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return u
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GlobalClientID(username, clientType, clientID string) string {
|
||||||
|
return fmt.Sprintf("%s.%s.%s", username, clientType, clientID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Wrapper[T ReqType, U RespType](handler func(context.Context, *T) (*U, error)) func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
Logger(c).Infof("request url: %s", c.Request.URL)
|
||||||
|
req, err := GetProtoRequest[T](c)
|
||||||
|
if err != nil {
|
||||||
|
Logger(c).WithError(err).Errorf("Failed to get request, url: %s", c.Request.URL)
|
||||||
|
ErrResp(c, &defs.CommonResponse{
|
||||||
|
Status: &defs.Status{Code: defs.RespCode_INVALID, Message: defs.RespMessage_INVALID},
|
||||||
|
}, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := handler(c, req)
|
||||||
|
if err != nil {
|
||||||
|
Logger(c).WithError(err).Errorf("Failed to handle request, url: %s", c.Request.URL)
|
||||||
|
ErrResp(c, resp, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
OKResp(c, resp)
|
||||||
|
Logger(c).Infof("handle request success, response url: %s", c.Request.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logrus.AddHook(&ObserveHook{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Logger(c context.Context) *logrus.Entry {
|
||||||
|
var uid, endpoint, header, method, traceid interface{}
|
||||||
|
if c != nil {
|
||||||
|
ginCtx, ok := c.(*gin.Context)
|
||||||
|
if ok {
|
||||||
|
uid = ginCtx.GetString(UserIDKey)
|
||||||
|
endpoint = ginCtx.Request.URL.Path
|
||||||
|
header = ginCtx.Request.Header
|
||||||
|
method = ginCtx.Request.Method
|
||||||
|
traceid = ginCtx.GetString(TraceIDKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.SetFormatter(&logrus.JSONFormatter{})
|
||||||
|
return logrus.WithContext(c).
|
||||||
|
WithField(UserIDKey, uid).
|
||||||
|
WithField(EndpointKey, endpoint).
|
||||||
|
WithField(HeaderKey, header).
|
||||||
|
WithField(MethodKey, method).
|
||||||
|
WithField(TraceIDKey, traceid)
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ObserveHook struct{}
|
||||||
|
|
||||||
|
func (h *ObserveHook) Levels() []logrus.Level {
|
||||||
|
return []logrus.Level{
|
||||||
|
logrus.ErrorLevel,
|
||||||
|
logrus.PanicLevel,
|
||||||
|
logrus.InfoLevel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ObserveHook) Fire(entry *logrus.Entry) error {
|
||||||
|
go func() {
|
||||||
|
data := []map[string]interface{}{{
|
||||||
|
"_msg": entry.Message,
|
||||||
|
"_level": entry.Level,
|
||||||
|
"_time": entry.Time,
|
||||||
|
"_app": "sharkapi",
|
||||||
|
"_userid": entry.Data[UserIDKey],
|
||||||
|
"_endpoint": entry.Data[EndpointKey],
|
||||||
|
"_header": entry.Data[HeaderKey],
|
||||||
|
"_x_trace_id": entry.Data[TraceIDKey],
|
||||||
|
}}
|
||||||
|
|
||||||
|
if e, ok := entry.Data[logrus.ErrorKey].(error); ok {
|
||||||
|
data[0]["_err"] = e.Error()
|
||||||
|
}
|
||||||
|
c, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = log2openOb(c)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("log2openOb error: %v", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func log2openOb(l []byte) error {
|
||||||
|
return nil
|
||||||
|
// cli := req.C() //.DevMode()
|
||||||
|
// resp, err := cli.R().SetBasicAuth(config.GetSettings().LogUser,
|
||||||
|
// config.GetSettings().LogKey).
|
||||||
|
// SetBody(l).
|
||||||
|
// SetHeader("Content-Type", "application/json").
|
||||||
|
// Post(config.GetSettings().LogEndpoint)
|
||||||
|
|
||||||
|
// if err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
// if resp.Response == nil {
|
||||||
|
// return errors.New("resp err")
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/gin-gonic/gin/binding"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReqType interface {
|
||||||
|
gin.H |
|
||||||
|
defs.CommonPaginationRequest | defs.CommonQueryPaginationRequest |
|
||||||
|
defs.CommonQueryRequest | defs.CommonRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetProtoRequest[T ReqType](c *gin.Context) (r *T, err error) {
|
||||||
|
r = new(T)
|
||||||
|
if c.Request.ContentLength == 0 {
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.ContentType() == "application/x-protobuf" {
|
||||||
|
err = c.Copy().ShouldBindWith(r, binding.ProtoBuf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = c.Copy().ShouldBindJSON(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RespType interface {
|
||||||
|
gin.H |
|
||||||
|
defs.CommonResponse | defs.GetUserInfoResponse |
|
||||||
|
defs.GetUserAuthTokenResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
func OKResp[T RespType](c *gin.Context, origin *T) {
|
||||||
|
c.Header(TraceIDKey, c.GetString(TraceIDKey))
|
||||||
|
if c.ContentType() == "application/x-protobuf" {
|
||||||
|
c.ProtoBuf(http.StatusOK, origin)
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, OK(ReqSuccess).WithBody(origin))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrResp[T RespType](c *gin.Context, origin *T, err string) {
|
||||||
|
c.Header(TraceIDKey, c.GetString(TraceIDKey))
|
||||||
|
if c.ContentType() == "application/x-protobuf" {
|
||||||
|
c.ProtoBuf(http.StatusInternalServerError, origin)
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, Err(err).WithBody(origin))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ErrUnAuthorized(c *gin.Context, err string) {
|
||||||
|
c.Header(TraceIDKey, c.GetString(TraceIDKey))
|
||||||
|
if c.ContentType() == "application/x-protobuf" {
|
||||||
|
c.ProtoBuf(http.StatusUnauthorized,
|
||||||
|
&defs.CommonResponse{Status: &defs.Status{Code: defs.RespCode_UNAUTHORIZED, Message: defs.RespMessage_UNAUTHORIZED}})
|
||||||
|
} else {
|
||||||
|
c.JSON(http.StatusOK, Err(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package common
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Result struct {
|
||||||
|
Code int `json:"code,omitempty"`
|
||||||
|
Msg string `json:"msg,omitempty"`
|
||||||
|
Data gin.H `json:"data,omitempty"`
|
||||||
|
Body interface{} `json:"body,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) WithMsg(message string) *Result {
|
||||||
|
r.Msg = message
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) WithData(data gin.H) *Result {
|
||||||
|
r.Data = data
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) WithKeyValue(key string, value interface{}) *Result {
|
||||||
|
if r.Data == nil {
|
||||||
|
r.Data = gin.H{}
|
||||||
|
}
|
||||||
|
r.Data[key] = value
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Result) WithBody(body interface{}) *Result {
|
||||||
|
r.Body = body
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func newResult(code int, msg string) *Result {
|
||||||
|
return &Result{
|
||||||
|
Code: code,
|
||||||
|
Msg: msg,
|
||||||
|
Data: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func OK(msg string) *Result {
|
||||||
|
return newResult(200, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Err(msg string) *Result {
|
||||||
|
return newResult(500, msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnAuth(msg string) *Result {
|
||||||
|
return newResult(401, msg)
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://ui.shadcn.com/schema.json",
|
||||||
|
"style": "new-york",
|
||||||
|
"rsc": true,
|
||||||
|
"tsx": true,
|
||||||
|
"tailwind": {
|
||||||
|
"config": "tailwind.config.ts",
|
||||||
|
"css": "src/app/globals.css",
|
||||||
|
"baseColor": "zinc",
|
||||||
|
"cssVariables": true,
|
||||||
|
"prefix": ""
|
||||||
|
},
|
||||||
|
"aliases": {
|
||||||
|
"components": "@/components",
|
||||||
|
"utils": "@/lib/utils"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
func IsDebug() bool {
|
||||||
|
return GetSettings().Debug
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/common"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Settings struct {
|
||||||
|
Debug bool `mapstructure:"debug"`
|
||||||
|
APIListenAddr string `mapstructure:"api_addr"`
|
||||||
|
RedisConf RedisConf `mapstructure:"redis_conf"`
|
||||||
|
CacheSize int `mapstructure:"cache_size"` // MB
|
||||||
|
DB DBConf `mapstructure:"db"`
|
||||||
|
JWTConfig JWTConfig `mapstructure:"jwt"`
|
||||||
|
AppleCfg AppleConf `mapstructure:"apple"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DBConf struct {
|
||||||
|
Type string `mapstructure:"type"`
|
||||||
|
DSN string `mapstructure:"dsn"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedisConf struct {
|
||||||
|
Addr string
|
||||||
|
Password string
|
||||||
|
DB int
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWTConfig struct {
|
||||||
|
Secret string `mapstructure:"secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppleConf struct {
|
||||||
|
Secret string `mapstructure:"secret"`
|
||||||
|
TeamID string `mapstructure:"team_id"`
|
||||||
|
ClientID string `mapstructure:"client_id"`
|
||||||
|
KeyID string `mapstructure:"key_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var settings *Settings
|
||||||
|
|
||||||
|
func GetSettings() *Settings {
|
||||||
|
return settings
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitSettings() {
|
||||||
|
c := context.Background()
|
||||||
|
|
||||||
|
setConfigParams()
|
||||||
|
fillDefaultSettings()
|
||||||
|
readAndParseConfig(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func fillDefaultSettings() {
|
||||||
|
viper.SetDefault("cache_size", 10)
|
||||||
|
viper.SetDefault("debug", false)
|
||||||
|
viper.SetDefault("db.type", "sqlite")
|
||||||
|
viper.SetDefault("db.dsn", "toyboom.db")
|
||||||
|
}
|
||||||
|
|
||||||
|
func setConfigParams() {
|
||||||
|
viper.SetConfigName("config")
|
||||||
|
viper.SetConfigType("yml")
|
||||||
|
viper.AddConfigPath("/etc/toyboom/")
|
||||||
|
viper.AddConfigPath("$HOME/.toyboom")
|
||||||
|
viper.AddConfigPath(".")
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func readAndParseConfig(c context.Context) {
|
||||||
|
err := viper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
common.Logger(c).WithError(err).Errorf("Error reading config file:[%s], will read from env", viper.ConfigFileUsed())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = viper.Unmarshal(&settings)
|
||||||
|
if err != nil {
|
||||||
|
common.Logger(c).Panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsDebug() {
|
||||||
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
gin.SetMode(gin.DebugMode)
|
||||||
|
} else {
|
||||||
|
logrus.SetLevel(logrus.InfoLevel)
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Query interface {
|
||||||
|
GetUserByAppleUserID(appleUserID string) (defs.UserGettable, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Mutation interface {
|
||||||
|
CreateUser(user defs.UserGettable) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ Query = (*queryImpl)(nil)
|
||||||
|
var _ Mutation = (*mutationImpl)(nil)
|
||||||
|
|
||||||
|
type queryImpl struct{}
|
||||||
|
type mutationImpl struct{}
|
||||||
|
|
||||||
|
func NewQuery() Query {
|
||||||
|
return &queryImpl{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMutation() Mutation {
|
||||||
|
return &mutationImpl{}
|
||||||
|
}
|
||||||
+13
@@ -0,0 +1,13 @@
|
|||||||
|
package dao
|
||||||
|
|
||||||
|
import "github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
|
||||||
|
func (q *queryImpl) GetUserByAppleUserID(appleUserID string) (defs.UserGettable, error) {
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *mutationImpl) CreateUser(user defs.UserGettable) error {
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package defs
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
IsPrivateEmail bool `json:"is_private_email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
// Tenants []Tenant
|
||||||
|
}
|
||||||
|
|
||||||
|
// type Tenant struct {
|
||||||
|
// ID string `json:"id"`
|
||||||
|
// Name string `json:"name"`
|
||||||
|
// Description string `json:"description"`
|
||||||
|
// }
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package defs
|
||||||
|
|
||||||
|
type CommonRequest struct {
|
||||||
|
Extra interface{} `json:"extra,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommonPaginationRequest struct {
|
||||||
|
Limit int `json:"limit,omitempty"`
|
||||||
|
Offset int `json:"offset,omitempty"`
|
||||||
|
Extra interface{} `json:"extra,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommonQueryRequest struct {
|
||||||
|
Query string `json:"query,omitempty"`
|
||||||
|
Extra interface{} `json:"extra,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommonQueryPaginationRequest struct {
|
||||||
|
Query string `json:"query,omitempty"`
|
||||||
|
Limit int `json:"limit,omitempty"`
|
||||||
|
Offset int `json:"offset,omitempty"`
|
||||||
|
Extra interface{} `json:"extra,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package defs
|
||||||
|
|
||||||
|
const (
|
||||||
|
RespCode_SUCCESS = RespCode(0)
|
||||||
|
RespCode_ERROR = RespCode(1)
|
||||||
|
RespCode_UNAUTHORIZED = RespCode(2)
|
||||||
|
RespCode_INVALID = RespCode(3)
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
RespMessage_SUCCESS = RespMsg("success")
|
||||||
|
RespMessage_ERROR = RespMsg("error")
|
||||||
|
RespMessage_UNAUTHORIZED = RespMsg("unauthorized")
|
||||||
|
RespMessage_INVALID = RespMsg("invalid")
|
||||||
|
)
|
||||||
|
|
||||||
|
type RespCode int
|
||||||
|
type RespMsg string
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Code RespCode `json:"code"`
|
||||||
|
Message RespMsg `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommonResponse struct {
|
||||||
|
Status *Status `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserInfoResponse struct {
|
||||||
|
Status *Status `json:"status"`
|
||||||
|
User *User `json:"user"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserAuthTokenResponse struct {
|
||||||
|
Status *Status `json:"status"`
|
||||||
|
Token string `json:"token"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
package defs
|
||||||
|
|
||||||
|
type UserGettable interface {
|
||||||
|
GetUserID() string
|
||||||
|
GetName() string
|
||||||
|
GetUsername() string
|
||||||
|
GetEmail() string
|
||||||
|
GetIsPrivateEmail() bool
|
||||||
|
GetEmailVerified() bool
|
||||||
|
ToUser() User
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
module github.com/nose7en/ToyBoomServer
|
||||||
|
|
||||||
|
go 1.21.5
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/coocood/freecache v1.2.4
|
||||||
|
github.com/gin-contrib/sessions v0.0.5
|
||||||
|
github.com/gin-contrib/static v0.0.1
|
||||||
|
github.com/gin-gonic/gin v1.9.1
|
||||||
|
github.com/glebarez/sqlite v1.10.0
|
||||||
|
github.com/go-co-op/gocron/v2 v2.2.4
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1
|
||||||
|
github.com/google/uuid v1.6.0
|
||||||
|
github.com/redis/go-redis/v9 v9.4.0
|
||||||
|
github.com/samber/lo v1.39.0
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
|
github.com/sourcegraph/conc v0.3.0
|
||||||
|
github.com/spf13/viper v1.18.2
|
||||||
|
gorm.io/driver/mysql v1.5.4
|
||||||
|
gorm.io/driver/postgres v1.5.6
|
||||||
|
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/Timothylock/go-signin-with-apple v0.2.3 // indirect
|
||||||
|
github.com/bytedance/sonic v1.9.1 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0 // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
|
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 // indirect
|
||||||
|
github.com/gorilla/context v1.1.1 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
|
github.com/gorilla/sessions v1.2.1 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 // indirect
|
||||||
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
|
github.com/jonboulle/clockwork v0.4.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
|
||||||
|
github.com/leodido/go-urn v1.2.4 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.19 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.11 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
golang.org/x/arch v0.3.0 // indirect
|
||||||
|
golang.org/x/crypto v0.17.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect
|
||||||
|
golang.org/x/net v0.19.0 // indirect
|
||||||
|
golang.org/x/sys v0.16.0 // indirect
|
||||||
|
golang.org/x/text v0.14.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
modernc.org/libc v1.22.5 // indirect
|
||||||
|
modernc.org/mathutil v1.5.0 // indirect
|
||||||
|
modernc.org/memory v1.5.0 // indirect
|
||||||
|
modernc.org/sqlite v1.23.1 // indirect
|
||||||
|
)
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
github.com/Timothylock/go-signin-with-apple v0.2.3 h1:t8y3cVW/L5+s4RSaWn4NC5VToLqZDpwkLsrcm9bGhhE=
|
||||||
|
github.com/Timothylock/go-signin-with-apple v0.2.3/go.mod h1:drDUeawmKp2eRdbXh99+5HKbGT1O1wLA+1LNN4+cs9w=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
|
||||||
|
github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s=
|
||||||
|
github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U=
|
||||||
|
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||||
|
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams=
|
||||||
|
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
|
||||||
|
github.com/coocood/freecache v1.2.4 h1:UdR6Yz/X1HW4fZOuH0Z94KwG851GWOSknua5VUbb/5M=
|
||||||
|
github.com/coocood/freecache v1.2.4/go.mod h1:RBUWa/Cy+OHdfTGFEhEuE1pMCMX51Ncizj7rthiQ3vk=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
|
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/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
|
github.com/gin-contrib/sessions v0.0.5 h1:CATtfHmLMQrMNpJRgzjWXD7worTh7g7ritsQfmF+0jE=
|
||||||
|
github.com/gin-contrib/sessions v0.0.5/go.mod h1:vYAuaUPqie3WUSsft6HUlCjlwwoJQs97miaG2+7neKY=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-contrib/static v0.0.1 h1:JVxuvHPuUfkoul12N7dtQw7KRn/pSMq7Ue1Va9Swm1U=
|
||||||
|
github.com/gin-contrib/static v0.0.1/go.mod h1:CSxeF+wep05e0kCOsqWdAWbSszmc31zTIbD8TvWl7Hs=
|
||||||
|
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
|
||||||
|
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
|
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||||
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
|
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
||||||
|
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
||||||
|
github.com/go-co-op/gocron/v2 v2.2.4 h1:fL6a8/U+BJQ9UbaeqKxua8wY02w4ftKZsxPzLSNOCKk=
|
||||||
|
github.com/go-co-op/gocron/v2 v2.2.4/go.mod h1:igssOwzZkfcnu3m2kwnCf/mYj4SmhP9ecSgmYjCOHkk=
|
||||||
|
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
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/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js=
|
||||||
|
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||||
|
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||||
|
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||||
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42 h1:dHLYa5D8/Ta0aLR2XcPsrkpAgGeFs6thhMcQK0oQ0n8=
|
||||||
|
github.com/google/pprof v0.0.0-20231229205709-960ae82b1e42/go.mod h1:czg5+yv1E0ZGTi6S6vVK1mke0fV+FaUhNGcd6VRS9Ik=
|
||||||
|
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/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
|
||||||
|
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||||
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
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/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||||
|
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
|
||||||
|
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||||
|
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||||
|
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
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/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
|
||||||
|
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
|
||||||
|
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
|
||||||
|
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
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/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
|
||||||
|
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||||
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA=
|
||||||
|
github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ=
|
||||||
|
github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
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.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
|
||||||
|
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||||
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
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/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k=
|
||||||
|
golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
|
||||||
|
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
|
||||||
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM=
|
||||||
|
golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
||||||
|
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
|
||||||
|
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
|
||||||
|
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
||||||
|
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso=
|
||||||
|
gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs=
|
||||||
|
gorm.io/driver/postgres v1.5.6 h1:ydr9xEd5YAM0vxVDY0X139dyzNz10spDiDlC7+ibLeU=
|
||||||
|
gorm.io/driver/postgres v1.5.6/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
|
||||||
|
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde h1:9DShaph9qhkIYw7QF91I/ynrr4cOO2PZra2PFD7Mfeg=
|
||||||
|
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||||
|
modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE=
|
||||||
|
modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
|
||||||
|
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
|
||||||
|
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
|
||||||
|
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
|
||||||
|
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
|
||||||
|
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
|
||||||
|
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/Timothylock/go-signin-with-apple/apple"
|
||||||
|
"github.com/nose7en/ToyBoomServer/common"
|
||||||
|
"github.com/nose7en/ToyBoomServer/config"
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
"github.com/nose7en/ToyBoomServer/rpc"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidateAppleAppToken() func(c *gin.Context) {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
code := c.GetHeader(common.TokenKey)
|
||||||
|
resp, err := rpc.GetManager().AppleCli().VerifyAppToken(c, code)
|
||||||
|
if err != nil || len(resp.Error) > 0 {
|
||||||
|
common.Logger(c).WithError(err).Errorf("failed to verify apple token, response error: %s", resp.Error)
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, common.UnAuth("failed to verify apple token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the unique user ID
|
||||||
|
unique, err := apple.GetUniqueID(resp.IDToken)
|
||||||
|
if err != nil {
|
||||||
|
common.Logger(c).WithError(err).Error("failed to get apple unique id")
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, common.UnAuth("failed to verify apple token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get detail user info
|
||||||
|
claim, err := apple.GetClaims(resp.IDToken)
|
||||||
|
if err != nil || claim == nil {
|
||||||
|
common.Logger(c).WithError(err).Error("failed to get apple user info or claim is nil")
|
||||||
|
c.AbortWithStatusJSON(http.StatusOK, common.UnAuth("failed to verify apple token"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.IsDebug() {
|
||||||
|
common.Logger(c).Debugf("apple auth success, user info: %+v", claim)
|
||||||
|
}
|
||||||
|
|
||||||
|
email := cast.ToString((*claim)["email"])
|
||||||
|
emailVerified := cast.ToBool((*claim)["email_verified"])
|
||||||
|
isPrivateEmail := cast.ToBool((*claim)["is_private_email"])
|
||||||
|
|
||||||
|
userInfo := &defs.User{
|
||||||
|
UserID: unique,
|
||||||
|
Email: email,
|
||||||
|
IsPrivateEmail: isPrivateEmail,
|
||||||
|
EmailVerified: emailVerified,
|
||||||
|
}
|
||||||
|
common.Logger(c).Infof("apple auth success, user info: %+v", userInfo)
|
||||||
|
c.Set(common.UserInfoKey, userInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
func Models() []interface{} {
|
||||||
|
return []interface{}{
|
||||||
|
&User{},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nose7en/ToyBoomServer/defs"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ defs.UserGettable = (*User)(nil)
|
||||||
|
|
||||||
|
func NewUserGettable(opt ...func(*User)) defs.UserGettable {
|
||||||
|
u := &User{}
|
||||||
|
for _, o := range opt {
|
||||||
|
o(u)
|
||||||
|
}
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
||||||
|
type User struct {
|
||||||
|
gorm.Model
|
||||||
|
UserID string `json:"user_id"` // user id from apple
|
||||||
|
Name string `json:"name"` // ToyBoom's user name
|
||||||
|
Username string `json:"username"` // user name from apple
|
||||||
|
Email string `json:"email"` // email from apple
|
||||||
|
IsPrivateEmail bool `json:"is_private_email"`
|
||||||
|
EmailVerified bool `json:"email_verified"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) FillWithUserInfo(userInfo defs.UserGettable) {
|
||||||
|
u.UserID = userInfo.GetUserID()
|
||||||
|
u.Name = userInfo.GetName()
|
||||||
|
u.Username = userInfo.GetUsername()
|
||||||
|
u.Email = userInfo.GetEmail()
|
||||||
|
u.IsPrivateEmail = userInfo.GetIsPrivateEmail()
|
||||||
|
u.EmailVerified = userInfo.GetEmailVerified()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetName() string {
|
||||||
|
return u.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetUsername() string {
|
||||||
|
return u.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetUserID() string {
|
||||||
|
return u.UserID
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetIsPrivateEmail() bool {
|
||||||
|
return u.IsPrivateEmail
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) GetEmailVerified() bool {
|
||||||
|
return u.EmailVerified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *User) ToUser() defs.User {
|
||||||
|
return defs.User{
|
||||||
|
UserID: u.UserID,
|
||||||
|
Name: u.Name,
|
||||||
|
Username: u.Username,
|
||||||
|
Email: u.Email,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
output: 'export',
|
||||||
|
async rewrites() {
|
||||||
|
return {
|
||||||
|
fallback: [
|
||||||
|
{
|
||||||
|
source: '/api/:path*',
|
||||||
|
destination: 'http://localhost:8080/api/:path*',
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
{
|
||||||
|
"name": "my-app",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"dev": "next dev",
|
||||||
|
"build": "next build && rm -rf cmd/out && cp -r out cmd/",
|
||||||
|
"start": "next start",
|
||||||
|
"lint": "next lint"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nanostores/react": "^0.7.1",
|
||||||
|
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-hover-card": "^1.0.7",
|
||||||
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
"@radix-ui/react-navigation-menu": "^1.1.4",
|
||||||
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
|
"@tanstack/react-query": "^5.18.1",
|
||||||
|
"ai": "^2.2.33",
|
||||||
|
"axios": "^1.6.7",
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"clsx": "^2.1.0",
|
||||||
|
"lucide-react": "^0.323.0",
|
||||||
|
"nanoid": "^5.0.5",
|
||||||
|
"nanostores": "^0.9.5",
|
||||||
|
"next": "14.1.0",
|
||||||
|
"next-themes": "^0.2.1",
|
||||||
|
"react": "^18",
|
||||||
|
"react-dom": "^18",
|
||||||
|
"react-intersection-observer": "^9.7.0",
|
||||||
|
"react-markdown": "^8.0.7",
|
||||||
|
"react-spinners": "^0.13.8",
|
||||||
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
|
"react-textarea-autosize": "^8.5.3",
|
||||||
|
"remark-gfm": "^3.0.1",
|
||||||
|
"remark-math": "^5.1.1",
|
||||||
|
"sonner": "^1.4.0",
|
||||||
|
"tailwind-merge": "^2.2.1",
|
||||||
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
"use-local-storage": "^3.0.0",
|
||||||
|
"uuid": "^9.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20",
|
||||||
|
"@types/react": "^18",
|
||||||
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react-syntax-highlighter": "^15.5.11",
|
||||||
|
"@types/uuid": "^9.0.8",
|
||||||
|
"autoprefixer": "^10.0.1",
|
||||||
|
"eslint": "^8",
|
||||||
|
"eslint-config-next": "14.1.0",
|
||||||
|
"postcss": "^8",
|
||||||
|
"tailwindcss": "^3.3.0",
|
||||||
|
"typescript": "^5"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+5671
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32' width='8' height='8' fill='none' stroke='rgb(0 0 0 / 0.1)'><path d='M0 .5H31.5V32'/></svg>
|
||||||
|
After Width: | Height: | Size: 150 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 283 64"><path fill="black" d="M141 16c-11 0-19 7-19 18s9 18 20 18c7 0 13-3 16-7l-7-5c-2 3-6 4-9 4-5 0-9-3-10-7h28v-3c0-11-8-18-19-18zm-9 15c1-4 4-7 9-7s8 3 9 7h-18zm117-15c-11 0-19 7-19 18s9 18 20 18c6 0 12-3 16-7l-8-5c-2 3-5 4-8 4-5 0-9-3-11-7h28l1-3c0-11-8-18-19-18zm-10 15c2-4 5-7 10-7s8 3 9 7h-19zm-39 3c0 6 4 10 10 10 4 0 7-2 9-5l8 5c-3 5-9 8-17 8-11 0-19-7-19-18s8-18 19-18c8 0 14 3 17 8l-8 5c-2-3-5-5-9-5-6 0-10 4-10 10zm83-29v46h-9V5h9zM37 0l37 64H0L37 0zm92 5-27 48L74 5h10l18 30 17-30h10zm59 12v10l-3-1c-6 0-10 4-10 10v15h-9V17h9v9c0-5 6-9 13-9z"/></svg>
|
||||||
|
After Width: | Height: | Size: 629 B |
@@ -0,0 +1,85 @@
|
|||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/ecdsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Timothylock/go-signin-with-apple/apple"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AppleClient struct {
|
||||||
|
*apple.Client
|
||||||
|
teamID string
|
||||||
|
clientID string
|
||||||
|
keyID string
|
||||||
|
secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
// GenerateClientSecret generates a new client secret, all fields are required, no http request, fully local logic
|
||||||
|
func (a *AppleClient) GenerateClientSecret() (string, error) {
|
||||||
|
token := &jwt.Token{
|
||||||
|
Header: map[string]interface{}{
|
||||||
|
"alg": "ES256",
|
||||||
|
"kid": a.keyID,
|
||||||
|
},
|
||||||
|
Claims: jwt.MapClaims{
|
||||||
|
"iss": a.teamID,
|
||||||
|
"iat": time.Now().Unix(),
|
||||||
|
// constraint: exp - iat <= 180 days
|
||||||
|
"exp": time.Now().Add(24 * time.Hour).Unix(),
|
||||||
|
"aud": "https://appleid.apple.com",
|
||||||
|
"sub": a.clientID,
|
||||||
|
},
|
||||||
|
Method: jwt.SigningMethodES256,
|
||||||
|
}
|
||||||
|
|
||||||
|
ecdsaKey, _ := authKeyFromBytes([]byte(a.secret))
|
||||||
|
return token.SignedString(ecdsaKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// authKeyFromBytes create private key for jwt sign
|
||||||
|
func authKeyFromBytes(key []byte) (*ecdsa.PrivateKey, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
var block *pem.Block
|
||||||
|
if block, _ = pem.Decode(key); block == nil {
|
||||||
|
return nil, errors.New("token: AuthKey must be a valid .p8 PEM file")
|
||||||
|
}
|
||||||
|
|
||||||
|
var parsedKey interface{}
|
||||||
|
if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var pkey *ecdsa.PrivateKey
|
||||||
|
var ok bool
|
||||||
|
if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok {
|
||||||
|
return nil, errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pkey, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AppleClient) VerifyAppToken(ctx context.Context, code string) (apple.ValidationResponse, error) {
|
||||||
|
var resp apple.ValidationResponse
|
||||||
|
clientSecret, err := a.GenerateClientSecret()
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reqBody := apple.AppValidationTokenRequest{
|
||||||
|
ClientID: a.clientID,
|
||||||
|
ClientSecret: clientSecret,
|
||||||
|
Code: code,
|
||||||
|
}
|
||||||
|
if err := a.Client.VerifyAppToken(ctx, reqBody, &resp); err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
const token = "c2fa05395f0974b798d7d9ae802a17bae.0.srvuz.8c3w4pp04rFe9CXbrtNxVg"
|
||||||
|
|
||||||
|
func TestAppleClient_VerifyAppToken(t *testing.T) {
|
||||||
|
|
||||||
|
config.InitSettings()
|
||||||
|
|
||||||
|
InitManager()
|
||||||
|
|
||||||
|
cli := GetManager().AppleCli()
|
||||||
|
resp, err := cli.VerifyAppToken(context.Background(), token)
|
||||||
|
|
||||||
|
t.Error(err)
|
||||||
|
t.Error(resp)
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package rpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Timothylock/go-signin-with-apple/apple"
|
||||||
|
"github.com/nose7en/ToyBoomServer/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
AppleCli() *AppleClient
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
mgr Manager = (*manager)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetManager() Manager {
|
||||||
|
return mgr
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitManager() {
|
||||||
|
mgr = &manager{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type manager struct {
|
||||||
|
appleCli *AppleClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *manager) AppleCli() *AppleClient {
|
||||||
|
if m.appleCli == nil {
|
||||||
|
m.appleCli = &AppleClient{
|
||||||
|
Client: apple.New(),
|
||||||
|
teamID: config.GetSettings().AppleCfg.TeamID,
|
||||||
|
clientID: config.GetSettings().AppleCfg.ClientID,
|
||||||
|
keyID: config.GetSettings().AppleCfg.KeyID,
|
||||||
|
secret: config.GetSettings().AppleCfg.Secret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.appleCli
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type APIHandler struct {
|
||||||
|
router *gin.Engine
|
||||||
|
addr []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAPIHandler(addr ...string) *APIHandler {
|
||||||
|
return &APIHandler{
|
||||||
|
addr: addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *APIHandler) Init(r *gin.Engine) {
|
||||||
|
h.router = r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *APIHandler) Run() {
|
||||||
|
h.router.Run(h.addr...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *APIHandler) GetRouter() *gin.Engine {
|
||||||
|
return h.router
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { type Message } from 'ai/react'
|
||||||
|
import api from './api'
|
||||||
|
import { ChatShareAPIPrefix } from '@/const/api';
|
||||||
|
|
||||||
|
export interface ChatShareRequest {
|
||||||
|
chatId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChatShareResp {
|
||||||
|
userID: number;
|
||||||
|
id: string;
|
||||||
|
messages: Message[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getSharedChat = async (id: string) => {
|
||||||
|
const res = await api.post(ChatShareAPIPrefix + '/v1/chat/messages/share',
|
||||||
|
{ chatId: id } as ChatShareRequest,
|
||||||
|
)
|
||||||
|
console.log("getSharedChat api response: ", res.data)
|
||||||
|
return res.data as ChatShareResp
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"use client"
|
||||||
|
import axios from 'axios'
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
const instance = axios.create({})
|
||||||
|
|
||||||
|
instance.interceptors.request.use((request) => {
|
||||||
|
request.headers["x-client-request-id"] = uuidv4();
|
||||||
|
return request
|
||||||
|
})
|
||||||
|
|
||||||
|
instance.interceptors.response.use((response) => {
|
||||||
|
return response
|
||||||
|
})
|
||||||
|
|
||||||
|
export default instance
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import api from "./api"
|
||||||
|
import { APIPrefix } from "@/const/api"
|
||||||
|
|
||||||
|
export const checkstatus = async () => {
|
||||||
|
console.log("checkstatus")
|
||||||
|
const res = await api.get(APIPrefix + '/ping')
|
||||||
|
return res
|
||||||
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,76 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
:root {
|
||||||
|
--background: 0 0% 100%;
|
||||||
|
--foreground: 240 10% 3.9%;
|
||||||
|
|
||||||
|
--card: 0 0% 100%;
|
||||||
|
--card-foreground: 240 10% 3.9%;
|
||||||
|
|
||||||
|
--popover: 0 0% 100%;
|
||||||
|
--popover-foreground: 240 10% 3.9%;
|
||||||
|
|
||||||
|
--primary: 240 5.9% 10%;
|
||||||
|
--primary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--secondary: 240 4.8% 95.9%;
|
||||||
|
--secondary-foreground: 240 5.9% 10%;
|
||||||
|
|
||||||
|
--muted: 240 4.8% 95.9%;
|
||||||
|
--muted-foreground: 240 3.8% 46.1%;
|
||||||
|
|
||||||
|
--accent: 240 4.8% 95.9%;
|
||||||
|
--accent-foreground: 240 5.9% 10%;
|
||||||
|
|
||||||
|
--destructive: 0 84.2% 60.2%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 240 5.9% 90%;
|
||||||
|
--input: 240 5.9% 90%;
|
||||||
|
--ring: 240 10% 3.9%;
|
||||||
|
|
||||||
|
--radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dark {
|
||||||
|
--background: 240 10% 3.9%;
|
||||||
|
--foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--card: 240 10% 3.9%;
|
||||||
|
--card-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--popover: 240 10% 3.9%;
|
||||||
|
--popover-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--primary: 0 0% 98%;
|
||||||
|
--primary-foreground: 240 5.9% 10%;
|
||||||
|
|
||||||
|
--secondary: 240 3.7% 15.9%;
|
||||||
|
--secondary-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--muted: 240 3.7% 15.9%;
|
||||||
|
--muted-foreground: 240 5% 64.9%;
|
||||||
|
|
||||||
|
--accent: 240 3.7% 15.9%;
|
||||||
|
--accent-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--destructive: 0 62.8% 30.6%;
|
||||||
|
--destructive-foreground: 0 0% 98%;
|
||||||
|
|
||||||
|
--border: 240 3.7% 15.9%;
|
||||||
|
--input: 240 3.7% 15.9%;
|
||||||
|
--ring: 240 4.9% 83.9%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply bg-background text-foreground;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { Metadata } from "next";
|
||||||
|
import { Inter } from "next/font/google";
|
||||||
|
import "./globals.css";
|
||||||
|
|
||||||
|
const inter = Inter({ subsets: ["latin"] });
|
||||||
|
|
||||||
|
export const metadata: Metadata = {
|
||||||
|
title: "VaalaAI",
|
||||||
|
description: "Vaala的AI",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function RootLayout({
|
||||||
|
children,
|
||||||
|
}: Readonly<{
|
||||||
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<body className={inter.className}>
|
||||||
|
{children}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<main>
|
||||||
|
hello
|
||||||
|
</main >
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
"use client"
|
||||||
|
import { TooltipProvider } from '@radix-ui/react-tooltip';
|
||||||
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
const queryClient = new QueryClient()
|
||||||
|
|
||||||
|
export default function Providers({ children }: { children: React.ReactNode }) {
|
||||||
|
return <TooltipProvider>
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
|
{children}
|
||||||
|
</QueryClientProvider>
|
||||||
|
</TooltipProvider>
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
const AlertDialog = AlertDialogPrimitive.Root
|
||||||
|
|
||||||
|
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
||||||
|
|
||||||
|
const AlertDialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Overlay
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const AlertDialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPortal>
|
||||||
|
<AlertDialogOverlay />
|
||||||
|
<AlertDialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</AlertDialogPortal>
|
||||||
|
))
|
||||||
|
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const AlertDialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-2 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||||
|
|
||||||
|
const AlertDialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
AlertDialogFooter.displayName = "AlertDialogFooter"
|
||||||
|
|
||||||
|
const AlertDialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-lg font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const AlertDialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogDescription.displayName =
|
||||||
|
AlertDialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
const AlertDialogAction = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Action
|
||||||
|
ref={ref}
|
||||||
|
className={cn(buttonVariants(), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||||
|
|
||||||
|
const AlertDialogCancel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<AlertDialogPrimitive.Cancel
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({ variant: "outline" }),
|
||||||
|
"mt-2 sm:mt-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogPortal,
|
||||||
|
AlertDialogOverlay,
|
||||||
|
AlertDialogTrigger,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogTitle,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const alertVariants = cva(
|
||||||
|
"relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default: "bg-background text-foreground",
|
||||||
|
destructive:
|
||||||
|
"border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const Alert = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
|
||||||
|
>(({ className, variant, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
role="alert"
|
||||||
|
className={cn(alertVariants({ variant }), className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Alert.displayName = "Alert"
|
||||||
|
|
||||||
|
const AlertTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h5
|
||||||
|
ref={ref}
|
||||||
|
className={cn("mb-1 font-medium leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertTitle.displayName = "AlertTitle"
|
||||||
|
|
||||||
|
const AlertDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm [&_p]:leading-relaxed", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
AlertDescription.displayName = "AlertDescription"
|
||||||
|
|
||||||
|
export { Alert, AlertTitle, AlertDescription }
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const badgeVariants = cva(
|
||||||
|
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
||||||
|
secondary:
|
||||||
|
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||||
|
destructive:
|
||||||
|
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||||
|
outline: "text-foreground",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface BadgeProps
|
||||||
|
extends React.HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof badgeVariants> {}
|
||||||
|
|
||||||
|
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||||
|
return (
|
||||||
|
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge, badgeVariants }
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { Slot } from "@radix-ui/react-slot"
|
||||||
|
import { cva, type VariantProps } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const buttonVariants = cva(
|
||||||
|
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||||
|
{
|
||||||
|
variants: {
|
||||||
|
variant: {
|
||||||
|
default:
|
||||||
|
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||||
|
destructive:
|
||||||
|
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||||
|
outline:
|
||||||
|
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||||
|
secondary:
|
||||||
|
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||||
|
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||||
|
link: "text-primary underline-offset-4 hover:underline",
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
default: "h-9 px-4 py-2",
|
||||||
|
sm: "h-8 rounded-md px-3 text-xs",
|
||||||
|
lg: "h-10 rounded-md px-8",
|
||||||
|
icon: "h-9 w-9",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
variant: "default",
|
||||||
|
size: "default",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||||
|
const Comp = asChild ? Slot : "button"
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={cn(buttonVariants({ variant, size, className }))}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Button.displayName = "Button"
|
||||||
|
|
||||||
|
export { Button, buttonVariants }
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Card = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"rounded-xl border bg-card text-card-foreground shadow",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
Card.displayName = "Card"
|
||||||
|
|
||||||
|
const CardHeader = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-col space-y-1.5 p-6", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardHeader.displayName = "CardHeader"
|
||||||
|
|
||||||
|
const CardTitle = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLHeadingElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<h3
|
||||||
|
ref={ref}
|
||||||
|
className={cn("font-semibold leading-none tracking-tight", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardTitle.displayName = "CardTitle"
|
||||||
|
|
||||||
|
const CardDescription = React.forwardRef<
|
||||||
|
HTMLParagraphElement,
|
||||||
|
React.HTMLAttributes<HTMLParagraphElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<p
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardDescription.displayName = "CardDescription"
|
||||||
|
|
||||||
|
const CardContent = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
|
||||||
|
))
|
||||||
|
CardContent.displayName = "CardContent"
|
||||||
|
|
||||||
|
const CardFooter = React.forwardRef<
|
||||||
|
HTMLDivElement,
|
||||||
|
React.HTMLAttributes<HTMLDivElement>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex items-center p-6 pt-0", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
CardFooter.displayName = "CardFooter"
|
||||||
|
|
||||||
|
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
// Inspired by Chatbot-UI and modified to fit the needs of this project
|
||||||
|
// @see https://github.com/mckaywrigley/chatbot-ui/blob/main/components/Markdown/CodeBlock.tsx
|
||||||
|
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import { FC, memo } from 'react'
|
||||||
|
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||||
|
import { coldarkDark } from 'react-syntax-highlighter/dist/cjs/styles/prism'
|
||||||
|
|
||||||
|
import { IconCheck, IconCopy, IconDownload } from '@/components/ui/icons'
|
||||||
|
import { Button } from '@/components/ui/button'
|
||||||
|
import { useCopyToClipboard } from '@/lib/hooks/use-copy-to-clipboard'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
language: string
|
||||||
|
value: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface languageMap {
|
||||||
|
[key: string]: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export const programmingLanguages: languageMap = {
|
||||||
|
javascript: '.js',
|
||||||
|
python: '.py',
|
||||||
|
java: '.java',
|
||||||
|
c: '.c',
|
||||||
|
cpp: '.cpp',
|
||||||
|
'c++': '.cpp',
|
||||||
|
'c#': '.cs',
|
||||||
|
ruby: '.rb',
|
||||||
|
php: '.php',
|
||||||
|
swift: '.swift',
|
||||||
|
'objective-c': '.m',
|
||||||
|
kotlin: '.kt',
|
||||||
|
typescript: '.ts',
|
||||||
|
go: '.go',
|
||||||
|
perl: '.pl',
|
||||||
|
rust: '.rs',
|
||||||
|
scala: '.scala',
|
||||||
|
haskell: '.hs',
|
||||||
|
lua: '.lua',
|
||||||
|
shell: '.sh',
|
||||||
|
sql: '.sql',
|
||||||
|
html: '.html',
|
||||||
|
css: '.css'
|
||||||
|
// add more file extensions here, make sure the key is same as language prop in CodeBlock.tsx component
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateRandomString = (length: number, lowercase = false) => {
|
||||||
|
const chars = 'ABCDEFGHJKLMNPQRSTUVWXY3456789' // excluding similar looking characters like Z, 2, I, 1, O, 0
|
||||||
|
let result = ''
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
||||||
|
}
|
||||||
|
return lowercase ? result.toLowerCase() : result
|
||||||
|
}
|
||||||
|
|
||||||
|
const CodeBlock: FC<Props> = memo(({ language, value }) => {
|
||||||
|
const { isCopied, copyToClipboard } = useCopyToClipboard({ timeout: 2000 })
|
||||||
|
|
||||||
|
const downloadAsFile = () => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const fileExtension = programmingLanguages[language] || '.file'
|
||||||
|
const suggestedFileName = `file-${generateRandomString(
|
||||||
|
3,
|
||||||
|
true
|
||||||
|
)}${fileExtension}`
|
||||||
|
const fileName = window.prompt('Enter file name' || '', suggestedFileName)
|
||||||
|
|
||||||
|
if (!fileName) {
|
||||||
|
// User pressed cancel on prompt.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = new Blob([value], { type: 'text/plain' })
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const link = document.createElement('a')
|
||||||
|
link.download = fileName
|
||||||
|
link.href = url
|
||||||
|
link.style.display = 'none'
|
||||||
|
document.body.appendChild(link)
|
||||||
|
link.click()
|
||||||
|
document.body.removeChild(link)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onCopy = () => {
|
||||||
|
if (isCopied) return
|
||||||
|
copyToClipboard(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="relative w-full font-sans codeblock bg-zinc-950">
|
||||||
|
<div className="flex items-center justify-between w-full px-6 py-2 pr-4 bg-zinc-800 text-zinc-100">
|
||||||
|
<span className="text-xs lowercase">{language}</span>
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
className="hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
|
||||||
|
onClick={downloadAsFile}
|
||||||
|
size="icon"
|
||||||
|
>
|
||||||
|
<IconDownload />
|
||||||
|
<span className="sr-only">Download</span>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="icon"
|
||||||
|
className="text-xs hover:bg-zinc-800 focus-visible:ring-1 focus-visible:ring-slate-700 focus-visible:ring-offset-0"
|
||||||
|
onClick={onCopy}
|
||||||
|
>
|
||||||
|
{isCopied ? <IconCheck /> : <IconCopy />}
|
||||||
|
<span className="sr-only">Copy code</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SyntaxHighlighter
|
||||||
|
language={language}
|
||||||
|
style={coldarkDark}
|
||||||
|
PreTag="div"
|
||||||
|
showLineNumbers
|
||||||
|
customStyle={{
|
||||||
|
margin: 0,
|
||||||
|
width: '100%',
|
||||||
|
background: 'transparent',
|
||||||
|
padding: '1.5rem 1rem'
|
||||||
|
}}
|
||||||
|
lineNumberStyle={{
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
codeTagProps={{
|
||||||
|
style: {
|
||||||
|
fontSize: '0.9rem',
|
||||||
|
fontFamily: 'var(--font-mono)'
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{value}
|
||||||
|
</SyntaxHighlighter>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
CodeBlock.displayName = 'CodeBlock'
|
||||||
|
|
||||||
|
export { CodeBlock }
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as DialogPrimitive from "@radix-ui/react-dialog"
|
||||||
|
import { Cross2Icon } from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Dialog = DialogPrimitive.Root
|
||||||
|
|
||||||
|
const DialogTrigger = DialogPrimitive.Trigger
|
||||||
|
|
||||||
|
const DialogPortal = DialogPrimitive.Portal
|
||||||
|
|
||||||
|
const DialogClose = DialogPrimitive.Close
|
||||||
|
|
||||||
|
const DialogOverlay = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Overlay>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Overlay
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
|
||||||
|
|
||||||
|
const DialogContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<DialogPortal>
|
||||||
|
<DialogOverlay />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
|
||||||
|
<Cross2Icon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">Close</span>
|
||||||
|
</DialogPrimitive.Close>
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPortal>
|
||||||
|
))
|
||||||
|
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const DialogHeader = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col space-y-1.5 text-center sm:text-left",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogHeader.displayName = "DialogHeader"
|
||||||
|
|
||||||
|
const DialogFooter = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={cn(
|
||||||
|
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
DialogFooter.displayName = "DialogFooter"
|
||||||
|
|
||||||
|
const DialogTitle = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Title
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"text-lg font-semibold leading-none tracking-tight",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||||
|
|
||||||
|
const DialogDescription = React.forwardRef<
|
||||||
|
React.ElementRef<typeof DialogPrimitive.Description>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<DialogPrimitive.Description
|
||||||
|
ref={ref}
|
||||||
|
className={cn("text-sm text-muted-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
Dialog,
|
||||||
|
DialogPortal,
|
||||||
|
DialogOverlay,
|
||||||
|
DialogTrigger,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogHeader,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogDescription,
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as HoverCardPrimitive from "@radix-ui/react-hover-card"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const HoverCard = HoverCardPrimitive.Root
|
||||||
|
|
||||||
|
const HoverCardTrigger = HoverCardPrimitive.Trigger
|
||||||
|
|
||||||
|
const HoverCardContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof HoverCardPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof HoverCardPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<HoverCardPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
HoverCardContent.displayName = HoverCardPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { HoverCard, HoverCardTrigger, HoverCardContent }
|
||||||
@@ -0,0 +1,507 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils'
|
||||||
|
|
||||||
|
function IconNextChat({
|
||||||
|
className,
|
||||||
|
inverted,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'svg'> & { inverted?: boolean }) {
|
||||||
|
const id = React.useId()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 17 17"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient
|
||||||
|
id={`gradient-${id}-1`}
|
||||||
|
x1="10.6889"
|
||||||
|
y1="10.3556"
|
||||||
|
x2="13.8445"
|
||||||
|
y2="14.2667"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop stopColor={inverted ? 'white' : 'black'} />
|
||||||
|
<stop
|
||||||
|
offset={1}
|
||||||
|
stopColor={inverted ? 'white' : 'black'}
|
||||||
|
stopOpacity={0}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient
|
||||||
|
id={`gradient-${id}-2`}
|
||||||
|
x1="11.7555"
|
||||||
|
y1="4.8"
|
||||||
|
x2="11.7376"
|
||||||
|
y2="9.50002"
|
||||||
|
gradientUnits="userSpaceOnUse"
|
||||||
|
>
|
||||||
|
<stop stopColor={inverted ? 'white' : 'black'} />
|
||||||
|
<stop
|
||||||
|
offset={1}
|
||||||
|
stopColor={inverted ? 'white' : 'black'}
|
||||||
|
stopOpacity={0}
|
||||||
|
/>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path
|
||||||
|
d="M1 16L2.58314 11.2506C1.83084 9.74642 1.63835 8.02363 2.04013 6.39052C2.4419 4.75741 3.41171 3.32057 4.776 2.33712C6.1403 1.35367 7.81003 0.887808 9.4864 1.02289C11.1628 1.15798 12.7364 1.8852 13.9256 3.07442C15.1148 4.26363 15.842 5.83723 15.9771 7.5136C16.1122 9.18997 15.6463 10.8597 14.6629 12.224C13.6794 13.5883 12.2426 14.5581 10.6095 14.9599C8.97637 15.3616 7.25358 15.1692 5.74942 14.4169L1 16Z"
|
||||||
|
fill={inverted ? 'black' : 'white'}
|
||||||
|
stroke={inverted ? 'black' : 'white'}
|
||||||
|
strokeWidth={2}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
<mask
|
||||||
|
id="mask0_91_2047"
|
||||||
|
style={{ maskType: 'alpha' }}
|
||||||
|
maskUnits="userSpaceOnUse"
|
||||||
|
x={1}
|
||||||
|
y={0}
|
||||||
|
width={16}
|
||||||
|
height={16}
|
||||||
|
>
|
||||||
|
<circle cx={9} cy={8} r={8} fill={inverted ? 'black' : 'white'} />
|
||||||
|
</mask>
|
||||||
|
<g mask="url(#mask0_91_2047)">
|
||||||
|
<circle cx={9} cy={8} r={8} fill={inverted ? 'black' : 'white'} />
|
||||||
|
<path
|
||||||
|
d="M14.2896 14.0018L7.146 4.8H5.80005V11.1973H6.87681V6.16743L13.4444 14.6529C13.7407 14.4545 14.0231 14.2369 14.2896 14.0018Z"
|
||||||
|
fill={`url(#gradient-${id}-1)`}
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="11.2222"
|
||||||
|
y="4.8"
|
||||||
|
width="1.06667"
|
||||||
|
height="6.4"
|
||||||
|
fill={`url(#gradient-${id}-2)`}
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconOpenAI({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>OpenAI icon</title>
|
||||||
|
<path d="M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3655-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8144 3.3543-2.0201 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.7735-2.7818a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconVercel({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-label="Vercel logomark"
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 74 64"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M37.5896 0.25L74.5396 64.25H0.639648L37.5896 0.25Z"
|
||||||
|
fill="currentColor"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconGitHub({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>GitHub</title>
|
||||||
|
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconSeparator({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
shapeRendering="geometricPrecision"
|
||||||
|
stroke="currentColor"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="1"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
aria-hidden="true"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M16.88 3.549L7.12 20.451"></path>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconArrowDown({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="m205.66 149.66-72 72a8 8 0 0 1-11.32 0l-72-72a8 8 0 0 1 11.32-11.32L120 196.69V40a8 8 0 0 1 16 0v156.69l58.34-58.35a8 8 0 0 1 11.32 11.32Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconArrowRight({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="m221.66 133.66-72 72a8 8 0 0 1-11.32-11.32L196.69 136H40a8 8 0 0 1 0-16h156.69l-58.35-58.34a8 8 0 0 1 11.32-11.32l72 72a8 8 0 0 1 0 11.32Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconUser({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M230.92 212c-15.23-26.33-38.7-45.21-66.09-54.16a72 72 0 1 0-73.66 0c-27.39 8.94-50.86 27.82-66.09 54.16a8 8 0 1 0 13.85 8c18.84-32.56 52.14-52 89.07-52s70.23 19.44 89.07 52a8 8 0 1 0 13.85-8ZM72 96a56 56 0 1 1 56 56 56.06 56.06 0 0 1-56-56Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconPlus({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M224 128a8 8 0 0 1-8 8h-80v80a8 8 0 0 1-16 0v-80H40a8 8 0 0 1 0-16h80V40a8 8 0 0 1 16 0v80h80a8 8 0 0 1 8 8Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconArrowElbow({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M200 32v144a8 8 0 0 1-8 8H67.31l34.35 34.34a8 8 0 0 1-11.32 11.32l-48-48a8 8 0 0 1 0-11.32l48-48a8 8 0 0 1 11.32 11.32L67.31 168H184V32a8 8 0 0 1 16 0Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconSpinner({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4 animate-spin', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M232 128a104 104 0 0 1-208 0c0-41 23.81-78.36 60.66-95.27a8 8 0 0 1 6.68 14.54C60.15 61.59 40 93.27 40 128a88 88 0 0 0 176 0c0-34.73-20.15-66.41-51.34-80.73a8 8 0 0 1 6.68-14.54C208.19 49.64 232 87 232 128Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconMessage({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M216 48H40a16 16 0 0 0-16 16v160a15.84 15.84 0 0 0 9.25 14.5A16.05 16.05 0 0 0 40 240a15.89 15.89 0 0 0 10.25-3.78.69.69 0 0 0 .13-.11L82.5 208H216a16 16 0 0 0 16-16V64a16 16 0 0 0-16-16ZM40 224Zm176-32H82.5a16 16 0 0 0-10.3 3.75l-.12.11L40 224V64h176Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconTrash({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16ZM96 40a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8v8H96Zm96 168H64V64h128Zm-80-104v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0v64a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconRefresh({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M197.67 186.37a8 8 0 0 1 0 11.29C196.58 198.73 170.82 224 128 224c-37.39 0-64.53-22.4-80-39.85V208a8 8 0 0 1-16 0v-48a8 8 0 0 1 8-8h48a8 8 0 0 1 0 16H55.44C67.76 183.35 93 208 128 208c36 0 58.14-21.46 58.36-21.68a8 8 0 0 1 11.31.05ZM216 40a8 8 0 0 0-8 8v23.85C192.53 54.4 165.39 32 128 32c-42.82 0-68.58 25.27-69.66 26.34a8 8 0 0 0 11.3 11.34C69.86 69.46 92 48 128 48c35 0 60.24 24.65 72.56 40H168a8 8 0 0 0 0 16h48a8 8 0 0 0 8-8V48a8 8 0 0 0-8-8Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconStop({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M128 24a104 104 0 1 0 104 104A104.11 104.11 0 0 0 128 24Zm0 192a88 88 0 1 1 88-88 88.1 88.1 0 0 1-88 88Zm24-120h-48a8 8 0 0 0-8 8v48a8 8 0 0 0 8 8h48a8 8 0 0 0 8-8v-48a8 8 0 0 0-8-8Zm-8 48h-32v-32h32Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconSidebar({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M216 40H40a16 16 0 0 0-16 16v144a16 16 0 0 0 16 16h176a16 16 0 0 0 16-16V56a16 16 0 0 0-16-16ZM40 56h40v144H40Zm176 144H96V56h120v144Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconMoon({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M233.54 142.23a8 8 0 0 0-8-2 88.08 88.08 0 0 1-109.8-109.8 8 8 0 0 0-10-10 104.84 104.84 0 0 0-52.91 37A104 104 0 0 0 136 224a103.09 103.09 0 0 0 62.52-20.88 104.84 104.84 0 0 0 37-52.91 8 8 0 0 0-1.98-7.98Zm-44.64 48.11A88 88 0 0 1 65.66 67.11a89 89 0 0 1 31.4-26A106 106 0 0 0 96 56a104.11 104.11 0 0 0 104 104 106 106 0 0 0 14.92-1.06 89 89 0 0 1-26.02 31.4Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconSun({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M120 40V16a8 8 0 0 1 16 0v24a8 8 0 0 1-16 0Zm72 88a64 64 0 1 1-64-64 64.07 64.07 0 0 1 64 64Zm-16 0a48 48 0 1 0-48 48 48.05 48.05 0 0 0 48-48ZM58.34 69.66a8 8 0 0 0 11.32-11.32l-16-16a8 8 0 0 0-11.32 11.32Zm0 116.68-16 16a8 8 0 0 0 11.32 11.32l16-16a8 8 0 0 0-11.32-11.32ZM192 72a8 8 0 0 0 5.66-2.34l16-16a8 8 0 0 0-11.32-11.32l-16 16A8 8 0 0 0 192 72Zm5.66 114.34a8 8 0 0 0-11.32 11.32l16 16a8 8 0 0 0 11.32-11.32ZM48 128a8 8 0 0 0-8-8H16a8 8 0 0 0 0 16h24a8 8 0 0 0 8-8Zm80 80a8 8 0 0 0-8 8v24a8 8 0 0 0 16 0v-24a8 8 0 0 0-8-8Zm112-88h-24a8 8 0 0 0 0 16h24a8 8 0 0 0 0-16Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconCopy({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M216 32H88a8 8 0 0 0-8 8v40H40a8 8 0 0 0-8 8v128a8 8 0 0 0 8 8h128a8 8 0 0 0 8-8v-40h40a8 8 0 0 0 8-8V40a8 8 0 0 0-8-8Zm-56 176H48V96h112Zm48-48h-32V88a8 8 0 0 0-8-8H96V48h112Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconCheck({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="m229.66 77.66-128 128a8 8 0 0 1-11.32 0l-56-56a8 8 0 0 1 11.32-11.32L96 188.69 218.34 66.34a8 8 0 0 1 11.32 11.32Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconDownload({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M224 152v56a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16v-56a8 8 0 0 1 16 0v56h160v-56a8 8 0 0 1 16 0Zm-101.66 5.66a8 8 0 0 0 11.32 0l40-40a8 8 0 0 0-11.32-11.32L136 132.69V40a8 8 0 0 0-16 0v92.69l-26.34-26.35a8 8 0 0 0-11.32 11.32Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconClose({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M205.66 194.34a8 8 0 0 1-11.32 11.32L128 139.31l-66.34 66.35a8 8 0 0 1-11.32-11.32L116.69 128 50.34 61.66a8 8 0 0 1 11.32-11.32L128 116.69l66.34-66.35a8 8 0 0 1 11.32 11.32L139.31 128Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconEdit({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
stroke="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
d="M16.862 4.487l1.687-1.688a1.875 1.875 0 112.652 2.652L10.582 16.07a4.5 4.5 0 01-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 011.13-1.897l8.932-8.931zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0115.75 21H5.25A2.25 2.25 0 013 18.75V8.25A2.25 2.25 0 015.25 6H10"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconShare({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="m237.66 106.35-80-80A8 8 0 0 0 144 32v40.35c-25.94 2.22-54.59 14.92-78.16 34.91-28.38 24.08-46.05 55.11-49.76 87.37a12 12 0 0 0 20.68 9.58c11-11.71 50.14-48.74 107.24-52V192a8 8 0 0 0 13.66 5.65l80-80a8 8 0 0 0 0-11.3ZM160 172.69V144a8 8 0 0 0-8-8c-28.08 0-55.43 7.33-81.29 21.8a196.17 196.17 0 0 0-36.57 26.52c5.8-23.84 20.42-46.51 42.05-64.86C99.41 99.77 127.75 88 152 88a8 8 0 0 0 8-8V51.32L220.69 112Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconUsers({ className, ...props }: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M117.25 157.92a60 60 0 1 0-66.5 0 95.83 95.83 0 0 0-47.22 37.71 8 8 0 1 0 13.4 8.74 80 80 0 0 1 134.14 0 8 8 0 0 0 13.4-8.74 95.83 95.83 0 0 0-47.22-37.71ZM40 108a44 44 0 1 1 44 44 44.05 44.05 0 0 1-44-44Zm210.14 98.7a8 8 0 0 1-11.07-2.33A79.83 79.83 0 0 0 172 168a8 8 0 0 1 0-16 44 44 0 1 0-16.34-84.87 8 8 0 1 1-5.94-14.85 60 60 0 0 1 55.53 105.64 95.83 95.83 0 0 1 47.22 37.71 8 8 0 0 1-2.33 11.07Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconExternalLink({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M224 104a8 8 0 0 1-16 0V59.32l-66.33 66.34a8 8 0 0 1-11.32-11.32L196.68 48H152a8 8 0 0 1 0-16h64a8 8 0 0 1 8 8Zm-40 24a8 8 0 0 0-8 8v72H48V80h72a8 8 0 0 0 0-16H48a16 16 0 0 0-16 16v128a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-72a8 8 0 0 0-8-8Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function IconChevronUpDown({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'svg'>) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
fill="currentColor"
|
||||||
|
className={cn('h-4 w-4', className)}
|
||||||
|
viewBox="0 0 256 256"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M181.66 170.34a8 8 0 0 1 0 11.32l-48 48a8 8 0 0 1-11.32 0l-48-48a8 8 0 0 1 11.32-11.32L128 212.69l42.34-42.35a8 8 0 0 1 11.32 0Zm-96-84.68L128 43.31l42.34 42.35a8 8 0 0 0 11.32-11.32l-48-48a8 8 0 0 0-11.32 0l-48 48a8 8 0 0 0 11.32 11.32Z" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
IconEdit,
|
||||||
|
IconNextChat,
|
||||||
|
IconOpenAI,
|
||||||
|
IconVercel,
|
||||||
|
IconGitHub,
|
||||||
|
IconSeparator,
|
||||||
|
IconArrowDown,
|
||||||
|
IconArrowRight,
|
||||||
|
IconUser,
|
||||||
|
IconPlus,
|
||||||
|
IconArrowElbow,
|
||||||
|
IconSpinner,
|
||||||
|
IconMessage,
|
||||||
|
IconTrash,
|
||||||
|
IconRefresh,
|
||||||
|
IconStop,
|
||||||
|
IconSidebar,
|
||||||
|
IconMoon,
|
||||||
|
IconSun,
|
||||||
|
IconCopy,
|
||||||
|
IconCheck,
|
||||||
|
IconDownload,
|
||||||
|
IconClose,
|
||||||
|
IconShare,
|
||||||
|
IconUsers,
|
||||||
|
IconExternalLink,
|
||||||
|
IconChevronUpDown
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
export interface InputProps
|
||||||
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
|
|
||||||
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||||
|
({ className, type, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={cn(
|
||||||
|
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Input.displayName = "Input"
|
||||||
|
|
||||||
|
export { Input }
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
||||||
|
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
||||||
|
import { cva } from "class-variance-authority"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const NavigationMenu = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-10 flex max-w-max flex-1 items-center justify-center",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<NavigationMenuViewport />
|
||||||
|
</NavigationMenuPrimitive.Root>
|
||||||
|
))
|
||||||
|
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
|
||||||
|
|
||||||
|
const NavigationMenuList = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.List>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.List
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"group flex flex-1 list-none items-center justify-center space-x-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
|
||||||
|
|
||||||
|
const NavigationMenuItem = NavigationMenuPrimitive.Item
|
||||||
|
|
||||||
|
const navigationMenuTriggerStyle = cva(
|
||||||
|
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
|
||||||
|
)
|
||||||
|
|
||||||
|
const NavigationMenuTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}{" "}
|
||||||
|
<ChevronDownIcon
|
||||||
|
className="relative top-[1px] ml-1 h-3 w-3 transition duration-300 group-data-[state=open]:rotate-180"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
</NavigationMenuPrimitive.Trigger>
|
||||||
|
))
|
||||||
|
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
|
||||||
|
|
||||||
|
const NavigationMenuContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
|
||||||
|
|
||||||
|
const NavigationMenuLink = NavigationMenuPrimitive.Link
|
||||||
|
|
||||||
|
const NavigationMenuViewport = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||||
|
<NavigationMenuPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
NavigationMenuViewport.displayName =
|
||||||
|
NavigationMenuPrimitive.Viewport.displayName
|
||||||
|
|
||||||
|
const NavigationMenuIndicator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<NavigationMenuPrimitive.Indicator
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
|
||||||
|
</NavigationMenuPrimitive.Indicator>
|
||||||
|
))
|
||||||
|
NavigationMenuIndicator.displayName =
|
||||||
|
NavigationMenuPrimitive.Indicator.displayName
|
||||||
|
|
||||||
|
export {
|
||||||
|
navigationMenuTriggerStyle,
|
||||||
|
NavigationMenu,
|
||||||
|
NavigationMenuList,
|
||||||
|
NavigationMenuItem,
|
||||||
|
NavigationMenuContent,
|
||||||
|
NavigationMenuTrigger,
|
||||||
|
NavigationMenuLink,
|
||||||
|
NavigationMenuIndicator,
|
||||||
|
NavigationMenuViewport,
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import {
|
||||||
|
ChevronLeftIcon,
|
||||||
|
ChevronRightIcon,
|
||||||
|
DotsHorizontalIcon,
|
||||||
|
} from "@radix-ui/react-icons"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { ButtonProps, buttonVariants } from "@/components/ui/button"
|
||||||
|
|
||||||
|
const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
className={cn("mx-auto flex w-full justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
Pagination.displayName = "Pagination"
|
||||||
|
|
||||||
|
const PaginationContent = React.forwardRef<
|
||||||
|
HTMLUListElement,
|
||||||
|
React.ComponentProps<"ul">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ul
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex flex-row items-center gap-1", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
PaginationContent.displayName = "PaginationContent"
|
||||||
|
|
||||||
|
const PaginationItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentProps<"li">
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={cn("", className)} {...props} />
|
||||||
|
))
|
||||||
|
PaginationItem.displayName = "PaginationItem"
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean
|
||||||
|
} & Pick<ButtonProps, "size"> &
|
||||||
|
React.ComponentProps<"a">
|
||||||
|
|
||||||
|
const PaginationLink = ({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = "icon",
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) => (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? "page" : undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? "outline" : "ghost",
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
PaginationLink.displayName = "PaginationLink"
|
||||||
|
|
||||||
|
const PaginationPrevious = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pl-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeftIcon className="h-4 w-4" />
|
||||||
|
<span>Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationPrevious.displayName = "PaginationPrevious"
|
||||||
|
|
||||||
|
const PaginationNext = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn("gap-1 pr-2.5", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<ChevronRightIcon className="h-4 w-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
)
|
||||||
|
PaginationNext.displayName = "PaginationNext"
|
||||||
|
|
||||||
|
const PaginationEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<"span">) => (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn("flex h-9 w-9 items-center justify-center", className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<DotsHorizontalIcon className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
PaginationEllipsis.displayName = "PaginationEllipsis"
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationPrevious,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationEllipsis,
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Popover = PopoverPrimitive.Root
|
||||||
|
|
||||||
|
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||||
|
|
||||||
|
const PopoverAnchor = PopoverPrimitive.Anchor
|
||||||
|
|
||||||
|
const PopoverContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||||
|
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||||
|
<PopoverPrimitive.Portal>
|
||||||
|
<PopoverPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
align={align}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</PopoverPrimitive.Portal>
|
||||||
|
))
|
||||||
|
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as SeparatorPrimitive from "@radix-ui/react-separator"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const Separator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||||
|
ref
|
||||||
|
) => (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 bg-border",
|
||||||
|
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
)
|
||||||
|
Separator.displayName = SeparatorPrimitive.Root.displayName
|
||||||
|
|
||||||
|
export { Separator }
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
function Skeleton({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("animate-pulse rounded-md bg-primary/10", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Skeleton }
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
import { Toaster as Sonner } from "sonner"
|
||||||
|
|
||||||
|
type ToasterProps = React.ComponentProps<typeof Sonner>
|
||||||
|
|
||||||
|
const Toaster = ({ ...props }: ToasterProps) => {
|
||||||
|
const { theme = "system" } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sonner
|
||||||
|
theme={theme as ToasterProps["theme"]}
|
||||||
|
className="toaster group"
|
||||||
|
toastOptions={{
|
||||||
|
classNames: {
|
||||||
|
toast:
|
||||||
|
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
|
||||||
|
description: "group-[.toast]:text-muted-foreground",
|
||||||
|
actionButton:
|
||||||
|
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
|
||||||
|
cancelButton:
|
||||||
|
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Toaster }
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import * as React from "react"
|
||||||
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
const TooltipProvider = TooltipPrimitive.Provider
|
||||||
|
|
||||||
|
const Tooltip = TooltipPrimitive.Root
|
||||||
|
|
||||||
|
const TooltipTrigger = TooltipPrimitive.Trigger
|
||||||
|
|
||||||
|
const TooltipContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof TooltipPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
|
||||||
|
>(({ className, sideOffset = 4, ...props }, ref) => (
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
"z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
TooltipContent.displayName = TooltipPrimitive.Content.displayName
|
||||||
|
|
||||||
|
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
const ExternalURL = ""
|
||||||
|
|
||||||
|
export const AttestationPath = ExternalURL + "/v1/webauthn/attestation";
|
||||||
|
export const DiscoverableAttestationPath = AttestationPath + "?discoverable=true";
|
||||||
|
export const AssertionPath = ExternalURL + "/v1/webauthn/assertion";
|
||||||
|
export const DiscoverableAssertionPath = AssertionPath + "?discoverable=true";
|
||||||
|
|
||||||
|
export const LoginPath = ExternalURL + "/api/login";
|
||||||
|
export const LogoutPath = ExternalURL + "/api/logout";
|
||||||
|
export const InfoPath = ExternalURL + "/api/info";
|
||||||
|
|
||||||
|
export const WebauthNUserNameHeader = "x-vaala-webauthn-username"
|
||||||
|
|
||||||
|
export const APIPrefix = "/api/v1"
|
||||||
|
export const FunctionPrefix = APIPrefix + "/f"
|
||||||
|
export const BizPrefix = APIPrefix + "/biz"
|
||||||
|
|
||||||
|
export const AIAPIPrefix = FunctionPrefix + "/ai.vaa.la"
|
||||||
|
export const SearchShareAPIPrefix = FunctionPrefix + "/cache.vaa.la"
|
||||||
|
export const ChatShareAPIPrefix = FunctionPrefix + "/chatshare.vaa.la"
|
||||||
|
|
||||||
|
export const HotNewsPrefix = BizPrefix + "/hotnews"
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export const ToastGlobalConfig: {
|
||||||
|
position: 'top-right' | 'top-left' | 'top-center',
|
||||||
|
dismissible: boolean,
|
||||||
|
cancel?: {
|
||||||
|
label: string;
|
||||||
|
onClick?: () => void;
|
||||||
|
},
|
||||||
|
} = {
|
||||||
|
position: 'top-center',
|
||||||
|
dismissible: true,
|
||||||
|
cancel: {
|
||||||
|
label: '关闭',
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export const ChatMessagesKey = "chat-messages-0"
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
export function useAtBottom(offset = 0) {
|
||||||
|
const [isAtBottom, setIsAtBottom] = React.useState(false)
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
const handleScroll = () => {
|
||||||
|
setIsAtBottom(
|
||||||
|
window.innerHeight + window.scrollY >=
|
||||||
|
document.body.offsetHeight - offset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', handleScroll, { passive: true })
|
||||||
|
handleScroll()
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleScroll)
|
||||||
|
}
|
||||||
|
}, [offset])
|
||||||
|
|
||||||
|
return isAtBottom
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
export interface useCopyToClipboardProps {
|
||||||
|
timeout?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCopyToClipboard({
|
||||||
|
timeout = 2000
|
||||||
|
}: useCopyToClipboardProps) {
|
||||||
|
const [isCopied, setIsCopied] = React.useState<Boolean>(false)
|
||||||
|
|
||||||
|
const copyToClipboard = (value: string) => {
|
||||||
|
if (typeof window === 'undefined' || !navigator.clipboard?.writeText) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
navigator.clipboard.writeText(value).then(() => {
|
||||||
|
setIsCopied(true)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsCopied(false)
|
||||||
|
}, timeout)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isCopied, copyToClipboard }
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { useRef, type RefObject } from 'react'
|
||||||
|
|
||||||
|
export function useEnterSubmit(): {
|
||||||
|
formRef: RefObject<HTMLFormElement>
|
||||||
|
onKeyDown: (event: React.KeyboardEvent<HTMLTextAreaElement>) => void
|
||||||
|
} {
|
||||||
|
const formRef = useRef<HTMLFormElement>(null)
|
||||||
|
|
||||||
|
const handleKeyDown = (
|
||||||
|
event: React.KeyboardEvent<HTMLTextAreaElement>
|
||||||
|
): void => {
|
||||||
|
if (
|
||||||
|
event.key === 'Enter' &&
|
||||||
|
!event.shiftKey &&
|
||||||
|
!event.nativeEvent.isComposing
|
||||||
|
) {
|
||||||
|
formRef.current?.requestSubmit()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { formRef, onKeyDown: handleKeyDown }
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"use client";
|
||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export const useLocalStorageString = (
|
||||||
|
key: string,
|
||||||
|
initialValue: string
|
||||||
|
): [string, (value: string) => void] => {
|
||||||
|
const [storedValue, setStoredValue] = useState(initialValue)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Retrieve from localStorage
|
||||||
|
if (typeof window !== 'undefined' && window.localStorage) {
|
||||||
|
const item = window.localStorage.getItem(key)
|
||||||
|
if (item) {
|
||||||
|
setStoredValue(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [key])
|
||||||
|
|
||||||
|
const setValue = (value: string) => {
|
||||||
|
if (typeof window !== 'undefined' && window.localStorage) {
|
||||||
|
// Save state
|
||||||
|
setStoredValue(value)
|
||||||
|
// Save to localStorage
|
||||||
|
|
||||||
|
window.localStorage.setItem(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [storedValue, setValue]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useLocalStorage = <T>(
|
||||||
|
key: string,
|
||||||
|
initialValue: T
|
||||||
|
): [T, (value: T) => void] => {
|
||||||
|
const [storedValue, setStoredValue] = useState(initialValue)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Retrieve from localStorage
|
||||||
|
if (typeof window !== 'undefined' && window.localStorage) {
|
||||||
|
const item = window.localStorage.getItem(key)
|
||||||
|
if (item) {
|
||||||
|
setStoredValue(JSON.parse(item))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [key])
|
||||||
|
|
||||||
|
const setValue = (value: T) => {
|
||||||
|
// Save state
|
||||||
|
if (typeof window !== 'undefined' && window.localStorage) {
|
||||||
|
setStoredValue(value)
|
||||||
|
// Save to localStorage
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [storedValue, setValue]
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { type ClassValue, clsx } from "clsx"
|
||||||
|
import { twMerge } from "tailwind-merge"
|
||||||
|
|
||||||
|
export function cn(...inputs: ClassValue[]) {
|
||||||
|
return twMerge(clsx(inputs))
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { atom } from 'nanostores'
|
||||||
|
import { type Message } from 'ai/react'
|
||||||
|
|
||||||
|
export const $messages = atom<Message[]>([])
|
||||||
|
export const $chatID = atom<string | undefined>()
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { atom } from 'nanostores'
|
||||||
|
|
||||||
|
export const $statusOnline = atom<boolean>(false)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export interface GetJWTResponse {
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface APIResponse<T> {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
body: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OptDataAPIResponse<T> {
|
||||||
|
code: number;
|
||||||
|
msg: string;
|
||||||
|
body?: T;
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export interface Relate {
|
||||||
|
question: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export interface Source {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
isFamilyFriendly: boolean;
|
||||||
|
displayUrl: string;
|
||||||
|
snippet: string;
|
||||||
|
deepLinks: { snippet: string; name: string; url: string }[];
|
||||||
|
dateLastCrawled: string;
|
||||||
|
cachedPageUrl: string;
|
||||||
|
language: string;
|
||||||
|
primaryImageOfPage?: {
|
||||||
|
thumbnailUrl: string;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
imageId: string;
|
||||||
|
};
|
||||||
|
isNavigational: boolean;
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
/*
|
||||||
|
This file is a work taken from the following location: https://gist.github.com/enepomnyaschih/72c423f727d395eeaa09697058238727
|
||||||
|
|
||||||
|
It has been modified using the below algorithms to make it work for the standard web encoding.
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Egor Nepomnyaschih
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
// This constant can also be computed with the following algorithm:
|
||||||
|
const base64Chars = [],
|
||||||
|
A = "A".charCodeAt(0),
|
||||||
|
a = "a".charCodeAt(0),
|
||||||
|
n = "0".charCodeAt(0);
|
||||||
|
for (let i = 0; i < 26; ++i) {
|
||||||
|
base64Chars.push(String.fromCharCode(A + i));
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 26; ++i) {
|
||||||
|
base64Chars.push(String.fromCharCode(a + i));
|
||||||
|
}
|
||||||
|
for (let i = 0; i < 10; ++i) {
|
||||||
|
base64Chars.push(String.fromCharCode(n + i));
|
||||||
|
}
|
||||||
|
base64Chars.push("+");
|
||||||
|
base64Chars.push("/");
|
||||||
|
*/
|
||||||
|
|
||||||
|
const base64Chars = [
|
||||||
|
"A",
|
||||||
|
"B",
|
||||||
|
"C",
|
||||||
|
"D",
|
||||||
|
"E",
|
||||||
|
"F",
|
||||||
|
"G",
|
||||||
|
"H",
|
||||||
|
"I",
|
||||||
|
"J",
|
||||||
|
"K",
|
||||||
|
"L",
|
||||||
|
"M",
|
||||||
|
"N",
|
||||||
|
"O",
|
||||||
|
"P",
|
||||||
|
"Q",
|
||||||
|
"R",
|
||||||
|
"S",
|
||||||
|
"T",
|
||||||
|
"U",
|
||||||
|
"V",
|
||||||
|
"W",
|
||||||
|
"X",
|
||||||
|
"Y",
|
||||||
|
"Z",
|
||||||
|
"a",
|
||||||
|
"b",
|
||||||
|
"c",
|
||||||
|
"d",
|
||||||
|
"e",
|
||||||
|
"f",
|
||||||
|
"g",
|
||||||
|
"h",
|
||||||
|
"i",
|
||||||
|
"j",
|
||||||
|
"k",
|
||||||
|
"l",
|
||||||
|
"m",
|
||||||
|
"n",
|
||||||
|
"o",
|
||||||
|
"p",
|
||||||
|
"q",
|
||||||
|
"r",
|
||||||
|
"s",
|
||||||
|
"t",
|
||||||
|
"u",
|
||||||
|
"v",
|
||||||
|
"w",
|
||||||
|
"x",
|
||||||
|
"y",
|
||||||
|
"z",
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"2",
|
||||||
|
"3",
|
||||||
|
"4",
|
||||||
|
"5",
|
||||||
|
"6",
|
||||||
|
"7",
|
||||||
|
"8",
|
||||||
|
"9",
|
||||||
|
"+",
|
||||||
|
"/",
|
||||||
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
// This constant can also be computed with the following algorithm:
|
||||||
|
const l = 256, base64codes = new Uint8Array(l);
|
||||||
|
for (let i = 0; i < l; ++i) {
|
||||||
|
base64codes[i] = 255; // invalid character
|
||||||
|
}
|
||||||
|
base64Chars.forEach((char, index) => {
|
||||||
|
base64codes[char.charCodeAt(0)] = index;
|
||||||
|
});
|
||||||
|
base64codes["=".charCodeAt(0)] = 0; // ignored anyway, so we just need to prevent an error
|
||||||
|
*/
|
||||||
|
|
||||||
|
const base64Codes = [
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
|
||||||
|
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
|
||||||
|
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 0, 255, 255,
|
||||||
|
255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
|
||||||
|
255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||||
|
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51
|
||||||
|
];
|
||||||
|
|
||||||
|
function getBase64Code(charCode: number) {
|
||||||
|
if (charCode >= base64Codes.length) {
|
||||||
|
throw new Error("Unable to parse base64 string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = base64Codes[charCode];
|
||||||
|
if (code === 255) {
|
||||||
|
throw new Error("Unable to parse base64 string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBase64FromBytes(bytes: number[] | Uint8Array): string {
|
||||||
|
let result = "",
|
||||||
|
i,
|
||||||
|
l = bytes.length;
|
||||||
|
|
||||||
|
for (i = 2; i < l; i += 3) {
|
||||||
|
result += base64Chars[bytes[i - 2] >> 2];
|
||||||
|
result += base64Chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
||||||
|
result += base64Chars[((bytes[i - 1] & 0x0F) << 2) | (bytes[i] >> 6)];
|
||||||
|
result += base64Chars[bytes[i] & 0x3F];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === l + 1) { // 1 octet yet to write
|
||||||
|
result += base64Chars[bytes[i - 2] >> 2];
|
||||||
|
result += base64Chars[(bytes[i - 2] & 0x03) << 4];
|
||||||
|
result += "==";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === l) { // 2 octets yet to write
|
||||||
|
result += base64Chars[bytes[i - 2] >> 2];
|
||||||
|
result += base64Chars[((bytes[i - 2] & 0x03) << 4) | (bytes[i - 1] >> 4)];
|
||||||
|
result += base64Chars[(bytes[i - 1] & 0x0F) << 2];
|
||||||
|
result += "=";
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBase64WebEncodingFromBytes(bytes: number[] | Uint8Array): string {
|
||||||
|
return getBase64FromBytes(bytes)
|
||||||
|
.replace(/\+/g, "-")
|
||||||
|
.replace(/\//g, "_")
|
||||||
|
.replace(/=/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getBytesFromBase64(str: string): Uint8Array {
|
||||||
|
str = str.replace(/\-/g, "+")
|
||||||
|
.replace(/\_/g, "/");
|
||||||
|
|
||||||
|
// if (str.length % 4 !== 0) {
|
||||||
|
// throw new Error("Unable to parse base64 string.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
const index = str.indexOf("=");
|
||||||
|
|
||||||
|
if (index !== -1 && index < str.length - 2) {
|
||||||
|
throw new Error("Unable to parse base64 string.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let missingOctets = str.endsWith("==") ? 2 : str.endsWith("=") ? 1 : 0,
|
||||||
|
n = str.length,
|
||||||
|
result = new Uint8Array(3 * (n / 4)),
|
||||||
|
buffer;
|
||||||
|
|
||||||
|
for (let i = 0, j = 0; i < n; i += 4, j += 3) {
|
||||||
|
buffer =
|
||||||
|
getBase64Code(str.charCodeAt(i)) << 18 |
|
||||||
|
getBase64Code(str.charCodeAt(i + 1)) << 12 |
|
||||||
|
getBase64Code(str.charCodeAt(i + 2)) << 6 |
|
||||||
|
getBase64Code(str.charCodeAt(i + 3));
|
||||||
|
result[j] = buffer >> 16;
|
||||||
|
result[j + 1] = (buffer >> 8) & 0xFF;
|
||||||
|
result[j + 2] = buffer & 0xFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.subarray(0, result.length - missingOctets);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
async function pump(
|
||||||
|
reader: ReadableStreamDefaultReader<Uint8Array>,
|
||||||
|
controller: ReadableStreamDefaultController,
|
||||||
|
onChunk?: (chunk: Uint8Array) => void,
|
||||||
|
onDone?: () => void,
|
||||||
|
): Promise<ReadableStreamReadResult<Uint8Array> | undefined> {
|
||||||
|
const { done, value } = await reader.read();
|
||||||
|
if (done) {
|
||||||
|
onDone && onDone();
|
||||||
|
controller.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onChunk && onChunk(value);
|
||||||
|
controller.enqueue(value);
|
||||||
|
return pump(reader, controller, onChunk, onDone);
|
||||||
|
}
|
||||||
|
export const fetchStream = (
|
||||||
|
response: Response,
|
||||||
|
onChunk?: (chunk: Uint8Array) => void,
|
||||||
|
onDone?: () => void,
|
||||||
|
): ReadableStream<string> => {
|
||||||
|
const reader = response.body!.getReader();
|
||||||
|
return new ReadableStream({
|
||||||
|
start: (controller) => pump(reader, controller, onChunk, onDone),
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export const getSearchUrl = (query: string, search_uuid: string) => {
|
||||||
|
// const prefix =
|
||||||
|
// process.env.NODE_ENV === "production" ? "/search.html" : "/search";
|
||||||
|
const prefix = "/search";
|
||||||
|
return `${prefix}?q=${encodeURIComponent(query)}&rid=${search_uuid}`;
|
||||||
|
};
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { AIAPIPrefix, SearchShareAPIPrefix } from "@/const/api";
|
||||||
|
import { Relate } from "@/types/relate";
|
||||||
|
import { Source } from "@/types/source";
|
||||||
|
import { fetchStream } from "@/utils/fetch-stream";
|
||||||
|
|
||||||
|
const LLM_SPLIT = "__LLM_RESPONSE__";
|
||||||
|
const RELATED_SPLIT = "__RELATED_QUESTIONS__";
|
||||||
|
|
||||||
|
export const parseStreaming = async (
|
||||||
|
controller: AbortController,
|
||||||
|
query: string,
|
||||||
|
search_uuid: string,
|
||||||
|
mode: "query" | "share",
|
||||||
|
onSources: (value: Source[]) => void,
|
||||||
|
onMarkdown: (value: string) => void,
|
||||||
|
onRelates: (value: Relate[]) => void,
|
||||||
|
onServerQuery: (value: string) => void,
|
||||||
|
onError?: (status: number) => void,
|
||||||
|
) => {
|
||||||
|
const decoder = new TextDecoder();
|
||||||
|
let uint8Array = new Uint8Array();
|
||||||
|
let chunks = "";
|
||||||
|
let sourcesEmitted = false;
|
||||||
|
const response = await fetch(mode === "query" ? `${AIAPIPrefix}/v1/chat/search` : `${SearchShareAPIPrefix}/v1/chat/share`, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "*./*",
|
||||||
|
},
|
||||||
|
signal: controller.signal,
|
||||||
|
body: JSON.stringify({
|
||||||
|
query,
|
||||||
|
search_uuid,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
if (response.status !== 200) {
|
||||||
|
onError?.(response.status);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
onError?.(200);
|
||||||
|
}
|
||||||
|
const markdownParse = (text: string) => {
|
||||||
|
onMarkdown(
|
||||||
|
text
|
||||||
|
.replace(/\[\[([cC])itation/g, "[citation")
|
||||||
|
.replace(/[cC]itation:(\d+)]]/g, "citation:$1]")
|
||||||
|
.replace(/\[\[([cC]itation:\d+)]](?!])/g, `[$1]`)
|
||||||
|
.replace(/\[[cC]itation:(\d+)]/g, "[citation]($1)")
|
||||||
|
.replace(/\[(\d+)\]/g, "[citation]($1)")
|
||||||
|
.replace(/\[引用:(\d+)\]/g, "[citation]($1)")
|
||||||
|
.replace(/\[引用(\d+)\]/g, "[citation]($1)"),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
fetchStream(
|
||||||
|
response,
|
||||||
|
(chunk) => {
|
||||||
|
uint8Array = new Uint8Array([...uint8Array, ...chunk]);
|
||||||
|
chunks = decoder.decode(uint8Array, { stream: true });
|
||||||
|
if (chunks.includes(LLM_SPLIT)) {
|
||||||
|
const [sources, rest] = chunks.split(LLM_SPLIT);
|
||||||
|
if (!sourcesEmitted) {
|
||||||
|
try {
|
||||||
|
onServerQuery((JSON.parse(sources)).query);
|
||||||
|
onSources((JSON.parse(sources)).contexts);
|
||||||
|
onError?.(200);
|
||||||
|
} catch (e) {
|
||||||
|
try {
|
||||||
|
const shortsource = sources.slice(1)
|
||||||
|
onServerQuery((JSON.parse(shortsource)).query);
|
||||||
|
onSources((JSON.parse(shortsource)).contexts);
|
||||||
|
onError?.(200);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("parse sources error", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sourcesEmitted = true;
|
||||||
|
if (rest.includes(RELATED_SPLIT)) {
|
||||||
|
const [md] = rest.split(RELATED_SPLIT);
|
||||||
|
markdownParse(md);
|
||||||
|
onError?.(200);
|
||||||
|
} else {
|
||||||
|
markdownParse(rest);
|
||||||
|
onError?.(200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
const [_, relates] = chunks.split(RELATED_SPLIT);
|
||||||
|
try {
|
||||||
|
onRelates(JSON.parse(relates));
|
||||||
|
onError?.(200);
|
||||||
|
window.history.replaceState(null, "", "/search?rid=" + search_uuid);
|
||||||
|
} catch (e) {
|
||||||
|
console.error("parse relates error", e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DefaultDBName = "default"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBManager interface {
|
||||||
|
GetDB(dbType, dbName string) *gorm.DB
|
||||||
|
GetDefaultDB() *gorm.DB
|
||||||
|
SetDB(dbType, dbName string, db *gorm.DB)
|
||||||
|
RemoveDB(dbType, dbName string)
|
||||||
|
Init(tables ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type dbManagerImpl struct {
|
||||||
|
DBs map[string]map[string]*gorm.DB // [key: dbname, value: dbs] => [key: dbtype value: db]
|
||||||
|
defaultDBType string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbm *dbManagerImpl) Init(tables ...interface{}) {
|
||||||
|
dbs := dbm.DBs[DefaultDBName]
|
||||||
|
for _, db := range dbs {
|
||||||
|
db.AutoMigrate(tables...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
dbm *dbManagerImpl
|
||||||
|
)
|
||||||
|
|
||||||
|
func MustInitDBManager(dbs map[string]map[string]*gorm.DB, defaultDBType string) {
|
||||||
|
if dbm == nil {
|
||||||
|
dbm = NewDBManager(dbs, defaultDBType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDBManager(dbs map[string]map[string]*gorm.DB, defaultDBType string) *dbManagerImpl {
|
||||||
|
if dbs == nil {
|
||||||
|
dbs = make(map[string]map[string]*gorm.DB)
|
||||||
|
}
|
||||||
|
return &dbManagerImpl{
|
||||||
|
DBs: dbs,
|
||||||
|
defaultDBType: defaultDBType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetDBManager() DBManager {
|
||||||
|
if dbm == nil {
|
||||||
|
dbm = NewDBManager(nil, "")
|
||||||
|
}
|
||||||
|
return dbm
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbm *dbManagerImpl) GetDB(dbType, dbName string) *gorm.DB {
|
||||||
|
return dbm.DBs[dbName][dbType]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbm *dbManagerImpl) SetDB(dbType, dbName string, db *gorm.DB) {
|
||||||
|
if dbm.DBs[dbName] == nil {
|
||||||
|
dbm.DBs[dbName] = make(map[string]*gorm.DB)
|
||||||
|
}
|
||||||
|
dbm.DBs[dbName][dbType] = db
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbm *dbManagerImpl) RemoveDB(dbType, dbName string) {
|
||||||
|
delete(dbm.DBs[dbName], dbType)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dbm *dbManagerImpl) GetDefaultDB() *gorm.DB {
|
||||||
|
return dbm.DBs[DefaultDBName][dbm.defaultDBType]
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import type { Config } from "tailwindcss"
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
darkMode: ["class"],
|
||||||
|
content: [
|
||||||
|
'./pages/**/*.{ts,tsx}',
|
||||||
|
'./components/**/*.{ts,tsx}',
|
||||||
|
'./app/**/*.{ts,tsx}',
|
||||||
|
'./src/**/*.{ts,tsx}',
|
||||||
|
],
|
||||||
|
prefix: "",
|
||||||
|
theme: {
|
||||||
|
container: {
|
||||||
|
center: true,
|
||||||
|
padding: "2rem",
|
||||||
|
screens: {
|
||||||
|
"2xl": "1400px",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
border: "hsl(var(--border))",
|
||||||
|
input: "hsl(var(--input))",
|
||||||
|
ring: "hsl(var(--ring))",
|
||||||
|
background: "hsl(var(--background))",
|
||||||
|
foreground: "hsl(var(--foreground))",
|
||||||
|
primary: {
|
||||||
|
DEFAULT: "hsl(var(--primary))",
|
||||||
|
foreground: "hsl(var(--primary-foreground))",
|
||||||
|
},
|
||||||
|
secondary: {
|
||||||
|
DEFAULT: "hsl(var(--secondary))",
|
||||||
|
foreground: "hsl(var(--secondary-foreground))",
|
||||||
|
},
|
||||||
|
destructive: {
|
||||||
|
DEFAULT: "hsl(var(--destructive))",
|
||||||
|
foreground: "hsl(var(--destructive-foreground))",
|
||||||
|
},
|
||||||
|
muted: {
|
||||||
|
DEFAULT: "hsl(var(--muted))",
|
||||||
|
foreground: "hsl(var(--muted-foreground))",
|
||||||
|
},
|
||||||
|
accent: {
|
||||||
|
DEFAULT: "hsl(var(--accent))",
|
||||||
|
foreground: "hsl(var(--accent-foreground))",
|
||||||
|
},
|
||||||
|
popover: {
|
||||||
|
DEFAULT: "hsl(var(--popover))",
|
||||||
|
foreground: "hsl(var(--popover-foreground))",
|
||||||
|
},
|
||||||
|
card: {
|
||||||
|
DEFAULT: "hsl(var(--card))",
|
||||||
|
foreground: "hsl(var(--card-foreground))",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
borderRadius: {
|
||||||
|
lg: "var(--radius)",
|
||||||
|
md: "calc(var(--radius) - 2px)",
|
||||||
|
sm: "calc(var(--radius) - 4px)",
|
||||||
|
},
|
||||||
|
keyframes: {
|
||||||
|
"accordion-down": {
|
||||||
|
from: { height: "0" },
|
||||||
|
to: { height: "var(--radix-accordion-content-height)" },
|
||||||
|
},
|
||||||
|
"accordion-up": {
|
||||||
|
from: { height: "var(--radix-accordion-content-height)" },
|
||||||
|
to: { height: "0" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
"accordion-down": "accordion-down 0.2s ease-out",
|
||||||
|
"accordion-up": "accordion-up 0.2s ease-out",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
plugins: [require("tailwindcss-animate")],
|
||||||
|
} satisfies Config
|
||||||
|
|
||||||
|
export default config
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.iterable",
|
||||||
|
"esnext"
|
||||||
|
],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"target": "es2016",
|
||||||
|
"jsx": "preserve",
|
||||||
|
"incremental": true,
|
||||||
|
"plugins": [
|
||||||
|
{
|
||||||
|
"name": "next"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"cmd/out/types/**/*.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "encoding/base64"
|
||||||
|
|
||||||
|
func Base64Encode(s string) string {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(s))
|
||||||
|
}
|
||||||
|
|
||||||
|
func Base64Decode(s string) string {
|
||||||
|
data, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return string(data)
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ValidMAC(message, messageMAC, key []byte) bool {
|
||||||
|
mac := hmac.New(sha256.New, key)
|
||||||
|
mac.Write(message)
|
||||||
|
expectedMAC := mac.Sum(nil)
|
||||||
|
return hmac.Equal(messageMAC, expectedMAC)
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GenerateUID() string {
|
||||||
|
return uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func L32UID() string {
|
||||||
|
o := GenerateUID()
|
||||||
|
n := strings.Replace(o, "-", "", -1)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenCode() string {
|
||||||
|
return strings.ToUpper(L32UID()[0:6])
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
// @secretKey: JWT 加解密密钥
|
||||||
|
// @iat: 时间戳
|
||||||
|
// @seconds: 过期时间,单位秒
|
||||||
|
// @payload: 数据载体
|
||||||
|
func GetJwtToken(secretKey string, iat, seconds int64, payload string) (string, error) {
|
||||||
|
claims := make(jwt.MapClaims)
|
||||||
|
claims["exp"] = iat + seconds
|
||||||
|
claims["iat"] = iat
|
||||||
|
claims["payload"] = payload
|
||||||
|
token := jwt.New(jwt.SigningMethodHS256)
|
||||||
|
token.Claims = claims
|
||||||
|
return token.SignedString([]byte(secretKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @secretKey: JWT 加解密密钥
|
||||||
|
// @iat: 时间戳
|
||||||
|
// @seconds: 过期时间,单位秒
|
||||||
|
// @payload: 数据载体
|
||||||
|
func GetJwtTokenFromMap(secretKey string, iat, seconds int64, payload map[string]string) (string, error) {
|
||||||
|
claims := make(jwt.MapClaims)
|
||||||
|
claims["exp"] = iat + seconds
|
||||||
|
claims["iat"] = iat
|
||||||
|
for k, v := range payload {
|
||||||
|
claims[k] = v
|
||||||
|
}
|
||||||
|
token := jwt.New(jwt.SigningMethodHS256)
|
||||||
|
token.Claims = claims
|
||||||
|
return token.SignedString([]byte(secretKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
// @secretKey: JWT 加解密密钥
|
||||||
|
// @token: JWT Token 的字符串
|
||||||
|
func ValidateJwtToken(secretKey, token string) (bool, error) {
|
||||||
|
t, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(secretKey), nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return t.Valid, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseToken(secretKey, tokenStr string) (u jwt.MapClaims, err error) {
|
||||||
|
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
|
||||||
|
return []byte(secretKey), nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("couldn't handle this token")
|
||||||
|
}
|
||||||
|
|
||||||
|
if t, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("couldn't handle this token")
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package watcher
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/nose7en/ToyBoomServer/common"
|
||||||
|
|
||||||
|
"github.com/go-co-op/gocron/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Client interface {
|
||||||
|
Run()
|
||||||
|
Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
type client struct {
|
||||||
|
s gocron.Scheduler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(f func() error) Client {
|
||||||
|
c := context.Background()
|
||||||
|
s, err := gocron.NewScheduler()
|
||||||
|
if err != nil {
|
||||||
|
common.Logger(c).WithError(err).Fatalf("create scheduler error")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = s.NewJob(
|
||||||
|
gocron.CronJob("*/30 * * * * *", true),
|
||||||
|
gocron.NewTask(f),
|
||||||
|
)
|
||||||
|
f()
|
||||||
|
if err != nil {
|
||||||
|
common.Logger(c).WithError(err).Fatalf("create job error")
|
||||||
|
}
|
||||||
|
return &client{
|
||||||
|
s: s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Run() {
|
||||||
|
ctx := context.Background()
|
||||||
|
common.Logger(ctx).Infof("start to run scheduler, interval: 30s")
|
||||||
|
c.s.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) Stop() {
|
||||||
|
ctx := context.Background()
|
||||||
|
if err := c.s.Shutdown(); err != nil {
|
||||||
|
common.Logger(ctx).WithError(err).Errorf("shutdown scheduler error")
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user