mirror of https://github.com/veypi/OneAuth.git
change to new version
parent
b32b12878c
commit
5112f1ab7d
@ -1,40 +1,9 @@
|
||||
major=$(strip $(shell awk -F ':' '/major/ {print $$2;}' cfg.yml))
|
||||
minor=$(strip $(shell awk -F ':' '/minor/ {print $$2;}' cfg.yml))
|
||||
patch=$(strip $(shell awk -F ':' '/patch/ {print $$2;}' cfg.yml))
|
||||
version=v$(major).$(minor).$(patch)
|
||||
vinfo=$(strip $(shell awk -F '$(version)' '/$(version)/ {print $$2;}' vuf.md))
|
||||
|
||||
|
||||
version:
|
||||
@echo $(version) $(vinfo)
|
||||
|
||||
addtag:
|
||||
ifeq ($(vinfo),)
|
||||
@echo please add version info in vuf.md
|
||||
else
|
||||
@git tag -m '$(vinfo)' $(version)
|
||||
@git push origin $(version)
|
||||
endif
|
||||
|
||||
dropTag:
|
||||
@git tag -d $(version)
|
||||
@git push origin :refs/tags/$(version)
|
||||
#
|
||||
# Makefile
|
||||
# Copyright (C) 2024 veypi <i@veypi.com>
|
||||
# 2025-03-04 16:08:06
|
||||
# Distributed under terms of the GPL license.
|
||||
#
|
||||
|
||||
run:
|
||||
@cd oab && cargo run -- -c ./cfg-demo.yml
|
||||
|
||||
|
||||
syncDB:
|
||||
@scp -P 19529 oa.db root@alco.host:/root/
|
||||
|
||||
buildweb:
|
||||
@cd oaweb && yarn build
|
||||
@rm -rf ./oab/dist/*
|
||||
@mv ./oaweb/dist/spa/* ./oab/dist/
|
||||
|
||||
nats:
|
||||
@nats-server -c ./script/nats.cfg
|
||||
|
||||
.PHONY:oab
|
||||
oab:
|
||||
@cd oab && cargo run -- -c ./cfg-demo.yml
|
||||
@go run ./cli/*.go -f ./cfg/dev.yml -l debug
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
#
|
||||
# Makefile
|
||||
# Copyright (C) 2024 veypi <i@veypi.com>
|
||||
# 2025-03-04 16:08:06
|
||||
# Distributed under terms of the GPL license.
|
||||
#
|
||||
|
||||
run:
|
||||
@go run ./cli/*.go -f ./cfg/dev.yml -l debug
|
||||
@ -1,3 +0,0 @@
|
||||
build
|
||||
static
|
||||
dist
|
||||
@ -1,16 +0,0 @@
|
||||
#
|
||||
# Makefile
|
||||
# Copyright (C) 2024 veypi <i@veypi.com>
|
||||
# 2024-10-22 16:36
|
||||
# Distributed under terms of the GPL license.
|
||||
#
|
||||
cfg=~/.config/oa.yaml
|
||||
|
||||
run:
|
||||
@go run main.go -f ${cfg}
|
||||
|
||||
dbrest:
|
||||
@go run main.go -f ${cfg} db drop
|
||||
@go run main.go -f ${cfg} db migrate
|
||||
@go run main.go -f ${cfg} db init
|
||||
|
||||
@ -1,109 +0,0 @@
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"oa/cfg"
|
||||
M "oa/models"
|
||||
)
|
||||
|
||||
func useAccess(r rest.Router) {
|
||||
r.Get("/", accessList)
|
||||
r.Post("/", accessPost)
|
||||
r.Get("/:access_id", accessGet)
|
||||
r.Patch("/:access_id", accessPatch)
|
||||
r.Delete("/:access_id", accessDelete)
|
||||
}
|
||||
func accessList(x *rest.X) (any, error) {
|
||||
opts := &M.AccessList{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*M.Access, 0, 10)
|
||||
|
||||
query := cfg.DB()
|
||||
if opts.CreatedAt != nil {
|
||||
query = query.Where("created_at > ?", opts.CreatedAt)
|
||||
}
|
||||
if opts.UpdatedAt != nil {
|
||||
query = query.Where("updated_at > ?", opts.UpdatedAt)
|
||||
}
|
||||
query = query.Where("app_id LIKE ?", opts.AppID)
|
||||
if opts.UserID != nil {
|
||||
query = query.Where("user_id LIKE ?", opts.UserID)
|
||||
}
|
||||
if opts.RoleID != nil {
|
||||
query = query.Where("role_id LIKE ?", opts.RoleID)
|
||||
}
|
||||
if opts.Name != nil {
|
||||
query = query.Where("name LIKE ?", opts.Name)
|
||||
}
|
||||
err = query.Find(&data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func accessPost(x *rest.X) (any, error) {
|
||||
opts := &M.AccessPost{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Access{}
|
||||
|
||||
data.AppID = opts.AppID
|
||||
data.UserID = opts.UserID
|
||||
data.RoleID = opts.RoleID
|
||||
data.Name = opts.Name
|
||||
data.TID = opts.TID
|
||||
data.Level = opts.Level
|
||||
err = cfg.DB().Create(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func accessGet(x *rest.X) (any, error) {
|
||||
opts := &M.AccessGet{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Access{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func accessDelete(x *rest.X) (any, error) {
|
||||
opts := &M.AccessDelete{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Access{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func accessPatch(x *rest.X) (any, error) {
|
||||
opts := &M.AccessPatch{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Access{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.TID != nil {
|
||||
optsMap["tid"] = opts.TID
|
||||
}
|
||||
if opts.Level != nil {
|
||||
optsMap["level"] = opts.Level
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-23 16:28:13
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
// Auto generated by OneBD. DO NOT EDIT
|
||||
|
||||
package access
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
)
|
||||
|
||||
func Use(r rest.Router) {
|
||||
useAccess(r)
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"oa/cfg"
|
||||
"oa/errs"
|
||||
"oa/libs/auth"
|
||||
M "oa/models"
|
||||
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"github.com/veypi/utils"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func useApp(r rest.Router) {
|
||||
r.Delete("/:app_id", auth.Check("app", "app_id", auth.DoDelete), appDelete)
|
||||
r.Get("/:app_id", auth.Check("app", "app_id", auth.DoRead), appGet)
|
||||
r.Get("/:app_id/key", auth.Check("app", "app_id", auth.DoDelete), appKey)
|
||||
r.Get("/", appList)
|
||||
r.Patch("/:app_id", auth.Check("app", "app_id", auth.DoUpdate), appPatch)
|
||||
r.Post("/", auth.Check("app", "", auth.DoCreate), appPost)
|
||||
}
|
||||
func appKey(x *rest.X) (any, error) {
|
||||
id := x.Params.GetStr("app_id")
|
||||
if id == "" {
|
||||
return nil, errs.ArgsInvalid.WithStr("missing app_id")
|
||||
}
|
||||
data := &M.App{}
|
||||
data.ID = id
|
||||
key := utils.RandSeq(32)
|
||||
// err := cfg.DB().Where("id = ?", x.Params.GetStr("app_id")).First(data).Error
|
||||
err := cfg.DB().Model(data).Update("key", key).Error
|
||||
|
||||
return key, err
|
||||
}
|
||||
func appDelete(x *rest.X) (any, error) {
|
||||
opts := &M.AppDelete{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.App{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func appGet(x *rest.X) (any, error) {
|
||||
opts := &M.AppGet{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.App{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func appList(x *rest.X) (any, error) {
|
||||
opts := &M.AppList{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*struct {
|
||||
M.App
|
||||
UserStatus string `json:"user_status"`
|
||||
}, 0, 10)
|
||||
token, err := auth.CheckJWT(x)
|
||||
if err == nil {
|
||||
uid := token.UID
|
||||
query := cfg.DB().Table("apps").Select("apps.*,app_users.status user_status")
|
||||
if opts.Name != nil {
|
||||
query = query.Joins("LEFT JOIN app_users ON app_users.app_id = apps.id AND app_users.user_id = ? AND apps.name LIKE ?", uid, opts.Name)
|
||||
} else {
|
||||
query = query.Joins("LEFT JOIN app_users ON app_users.app_id = apps.id AND app_users.user_id = ?", uid)
|
||||
}
|
||||
err = query.Scan(&data).Error
|
||||
} else {
|
||||
err = cfg.DB().Table("apps").Select("id", "name", "icon").Find(&data).Error
|
||||
}
|
||||
|
||||
// logv.AssertError(cfg.DB().Table("accesses a").
|
||||
// Select("a.name, a.t_id, a.level").
|
||||
// Joins("INNER JOIN user_roles ur ON ur.role_id = a.role_id AND ur.user_id = ?", refresh.UID).
|
||||
// Scan(&acList).Error)
|
||||
|
||||
return data, err
|
||||
}
|
||||
func appPost(x *rest.X) (any, error) {
|
||||
opts := &M.AppPost{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.App{}
|
||||
|
||||
data.Name = opts.Name
|
||||
data.Icon = opts.Icon
|
||||
data.Typ = opts.Typ
|
||||
if opts.Des != nil {
|
||||
data.Des = *opts.Des
|
||||
}
|
||||
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||
data.Key = utils.RandSeq(32)
|
||||
err := tx.Create(data).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
au := &M.AppUser{
|
||||
AppID: data.ID,
|
||||
UserID: x.Request.Context().Value("uid").(string),
|
||||
Status: M.AUSTATUS_OK,
|
||||
}
|
||||
return tx.Create(au).Error
|
||||
})
|
||||
|
||||
return data, err
|
||||
}
|
||||
func appPatch(x *rest.X) (any, error) {
|
||||
opts := &M.AppPatch{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.App{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.Name != nil {
|
||||
optsMap["name"] = opts.Name
|
||||
}
|
||||
if opts.Icon != nil {
|
||||
optsMap["icon"] = opts.Icon
|
||||
}
|
||||
if opts.Des != nil {
|
||||
optsMap["des"] = opts.Des
|
||||
}
|
||||
if opts.Typ != nil {
|
||||
optsMap["typ"] = opts.Typ
|
||||
}
|
||||
if opts.InitRoleID != nil {
|
||||
optsMap["init_role_id"] = opts.InitRoleID
|
||||
}
|
||||
if opts.Status != nil {
|
||||
optsMap["status"] = opts.Status
|
||||
}
|
||||
if opts.InitUrl != nil {
|
||||
optsMap["init_url"] = opts.InitUrl
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,99 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"oa/cfg"
|
||||
M "oa/models"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func useAppUser(r rest.Router) {
|
||||
r.Post("/", appUserPost)
|
||||
r.Delete("/:app_user_id", appUserDelete)
|
||||
r.Get("/:app_user_id", appUserGet)
|
||||
r.Get("/", appUserList)
|
||||
r.Patch("/:app_user_id", appUserPatch)
|
||||
}
|
||||
func appUserPost(x *rest.X) (any, error) {
|
||||
opts := &M.AppUserPost{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.AppUser{}
|
||||
|
||||
data.ID = strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
data.AppID = opts.AppID
|
||||
data.UserID = opts.UserID
|
||||
data.Status = opts.Status
|
||||
err = cfg.DB().Create(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func appUserDelete(x *rest.X) (any, error) {
|
||||
opts := &M.AppUserDelete{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.AppUser{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func appUserGet(x *rest.X) (any, error) {
|
||||
opts := &M.AppUserGet{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.AppUser{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func appUserList(x *rest.X) (any, error) {
|
||||
opts := &M.AppUserList{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*M.AppUser, 0, 10)
|
||||
|
||||
query := cfg.DB()
|
||||
if opts.AppID != nil {
|
||||
query = query.Where("app_id LIKE ?", opts.AppID)
|
||||
}
|
||||
if opts.UserID != nil {
|
||||
query = query.Where("user_id LIKE ?", opts.UserID)
|
||||
}
|
||||
if opts.Status != nil {
|
||||
query = query.Where("status LIKE ?", opts.Status)
|
||||
}
|
||||
err = query.Find(&data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func appUserPatch(x *rest.X) (any, error) {
|
||||
opts := &M.AppUserPatch{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.AppUser{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.Status != nil {
|
||||
optsMap["status"] = opts.Status
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,19 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-23 16:28:13
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
// Auto generated by OneBD. DO NOT EDIT
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
)
|
||||
|
||||
func Use(r rest.Router) {
|
||||
useApp(r)
|
||||
useAppUser(r.SubRouter(":app_id/app_user"))
|
||||
useResource(r.SubRouter(":app_id/resource"))
|
||||
useRole(r.SubRouter(":app_id/role"))
|
||||
}
|
||||
@ -1,91 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"oa/cfg"
|
||||
M "oa/models"
|
||||
)
|
||||
|
||||
func useResource(r rest.Router) {
|
||||
r.Post("/", resourcePost)
|
||||
r.Delete("/", resourceDelete)
|
||||
r.Get("/", resourceList)
|
||||
r.Delete("/:resource_id", resourceDelete)
|
||||
r.Get("/:resource_id", resourceGet)
|
||||
r.Patch("/:resource_id", resourcePatch)
|
||||
}
|
||||
func resourceGet(x *rest.X) (any, error) {
|
||||
opts := &M.ResourceGet{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Resource{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func resourcePatch(x *rest.X) (any, error) {
|
||||
opts := &M.ResourcePatch{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Resource{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.Des != nil {
|
||||
optsMap["des"] = opts.Des
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func resourceDelete(x *rest.X) (any, error) {
|
||||
opts := &M.ResourceDelete{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Resource{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func resourceList(x *rest.X) (any, error) {
|
||||
opts := &M.ResourceList{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*M.Resource, 0, 10)
|
||||
|
||||
query := cfg.DB()
|
||||
if opts.AppID != nil {
|
||||
query = query.Where("app_id LIKE ?", opts.AppID)
|
||||
}
|
||||
err = query.Find(&data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func resourcePost(x *rest.X) (any, error) {
|
||||
opts := &M.ResourcePost{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Resource{}
|
||||
|
||||
data.AppID = opts.AppID
|
||||
data.Name = opts.Name
|
||||
data.Des = opts.Des
|
||||
err = cfg.DB().Create(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"oa/cfg"
|
||||
M "oa/models"
|
||||
)
|
||||
|
||||
func useRole(r rest.Router) {
|
||||
r.Delete("/:role_id", roleDelete)
|
||||
r.Get("/", roleList)
|
||||
r.Post("/", rolePost)
|
||||
r.Get("/:role_id", roleGet)
|
||||
r.Patch("/:role_id", rolePatch)
|
||||
}
|
||||
func roleDelete(x *rest.X) (any, error) {
|
||||
opts := &M.RoleDelete{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Role{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func roleList(x *rest.X) (any, error) {
|
||||
opts := &M.RoleList{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*M.Role, 0, 10)
|
||||
|
||||
query := cfg.DB()
|
||||
if opts.AppID != nil {
|
||||
query = query.Where("app_id LIKE ?", opts.AppID)
|
||||
}
|
||||
if opts.Name != nil {
|
||||
query = query.Where("name LIKE ?", opts.Name)
|
||||
}
|
||||
err = query.Find(&data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func rolePost(x *rest.X) (any, error) {
|
||||
opts := &M.RolePost{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Role{}
|
||||
|
||||
data.AppID = opts.AppID
|
||||
data.Name = opts.Name
|
||||
data.Des = opts.Des
|
||||
err = cfg.DB().Create(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func roleGet(x *rest.X) (any, error) {
|
||||
opts := &M.RoleGet{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Role{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func rolePatch(x *rest.X) (any, error) {
|
||||
opts := &M.RolePatch{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Role{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.Name != nil {
|
||||
optsMap["name"] = opts.Name
|
||||
}
|
||||
if opts.Des != nil {
|
||||
optsMap["des"] = opts.Des
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-20 16:10:16
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"oa/cfg"
|
||||
"oa/libs"
|
||||
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"github.com/veypi/OneBD/rest/middlewares/crud"
|
||||
)
|
||||
|
||||
func Use(r rest.Router) {
|
||||
r.Set("/*", "OPTIONS", libs.CorsAllowAny)
|
||||
r.Use(libs.CorsAllowAny)
|
||||
r.Get("", baseInfo)
|
||||
cfg.StaticObjs.RegistRouter(r, crud.ArgParser)
|
||||
// access.Use(r.SubRouter("access"))
|
||||
// app.Use(r.SubRouter("app"))
|
||||
// user.Use(r.SubRouter("user"))
|
||||
// token.Use(r.SubRouter("token"))
|
||||
cfg.StaticObjs.RegistRouter(r, crud.CRUD)
|
||||
}
|
||||
|
||||
func baseInfo(x *rest.X) (any, error) {
|
||||
return map[string]any{
|
||||
"id": cfg.Config.ID,
|
||||
}, nil
|
||||
}
|
||||
@ -1,16 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-24 22:37:12
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
// Auto generated by OneBD. DO NOT EDIT
|
||||
|
||||
package token
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
)
|
||||
|
||||
func Use(r rest.Router) {
|
||||
useToken(r)
|
||||
}
|
||||
@ -1,258 +0,0 @@
|
||||
package token
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"oa/cfg"
|
||||
"oa/errs"
|
||||
"oa/libs/auth"
|
||||
M "oa/models"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"github.com/veypi/utils"
|
||||
"github.com/veypi/utils/logv"
|
||||
)
|
||||
|
||||
func useToken(r rest.Router) {
|
||||
r.Post("/salt", tokenSalt)
|
||||
r.Post("/", tokenPost)
|
||||
r.Get("/:token_id", tokenGet)
|
||||
r.Patch("/:token_id", tokenPatch)
|
||||
r.Delete("/:token_id", tokenDelete)
|
||||
r.Get("/", tokenList)
|
||||
}
|
||||
func tokenSalt(x *rest.X) (any, error) {
|
||||
opts := &M.TokenSalt{}
|
||||
err := x.Parse(opts)
|
||||
logv.Warn().Msg(opts.Username)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.User{}
|
||||
query := "username = ?"
|
||||
if opts.Typ == nil {
|
||||
} else if *opts.Typ == "email" {
|
||||
query = "email = ?"
|
||||
} else if *opts.Typ == "phone" {
|
||||
query = "phone = ?"
|
||||
}
|
||||
|
||||
err = cfg.DB().Where(query, opts.Username).First(data).Error
|
||||
return map[string]string{"salt": data.Salt, "id": data.ID}, err
|
||||
}
|
||||
|
||||
// for user login app
|
||||
func tokenPost(x *rest.X) (any, error) {
|
||||
opts := &M.TokenPost{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
aid := cfg.Config.ID
|
||||
if opts.AppID != nil && *opts.AppID != "" {
|
||||
aid = *opts.AppID
|
||||
}
|
||||
data := &M.Token{}
|
||||
claim := &auth.Claims{}
|
||||
claim.IssuedAt = jwt.NewNumericDate(time.Now())
|
||||
claim.Issuer = cfg.Config.ID
|
||||
if opts.Refresh != nil {
|
||||
typ := "app"
|
||||
if opts.Typ != nil {
|
||||
typ = *opts.Typ
|
||||
}
|
||||
// for other app redirect
|
||||
refresh, err := auth.ParseJwt(*opts.Refresh)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if refresh.ID == "" {
|
||||
return nil, errs.AuthInvalid
|
||||
}
|
||||
err = cfg.DB().Where("id = ?", refresh.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
claim.AID = aid
|
||||
claim.UID = refresh.UID
|
||||
claim.Name = refresh.Name
|
||||
claim.Icon = refresh.Icon
|
||||
claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 10))
|
||||
if typ == "app" {
|
||||
if refresh.AID == aid {
|
||||
// refresh token
|
||||
acList := make(auth.Access, 0, 10)
|
||||
logv.AssertError(cfg.DB().Table("accesses a").
|
||||
Select("a.name, a.t_id, a.level").
|
||||
Joins("INNER JOIN user_roles ur ON ur.role_id = a.role_id AND ur.user_id = ? AND a.app_id = ?", refresh.UID, aid).
|
||||
Scan(&acList).Error)
|
||||
claim.Access = acList
|
||||
if aid == cfg.Config.ID {
|
||||
return auth.GenJwt(claim)
|
||||
}
|
||||
app := &M.App{}
|
||||
err = cfg.DB().Where("id = ?", aid).First(app).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return auth.GenJwtWithKey(claim, app.Key)
|
||||
} else if aid != cfg.Config.ID {
|
||||
// 只能生成其他应用的refresh token
|
||||
newToken := &M.Token{}
|
||||
newToken.UserID = refresh.UID
|
||||
newToken.AppID = aid
|
||||
newToken.ExpiredAt = time.Now().Add(time.Hour * 24)
|
||||
if opts.OverPerm != nil {
|
||||
newToken.OverPerm = *opts.OverPerm
|
||||
}
|
||||
if opts.Device != nil {
|
||||
newToken.Device = *opts.Device
|
||||
}
|
||||
newToken.Ip = x.GetRemoteIp()
|
||||
logv.AssertError(cfg.DB().Create(newToken).Error)
|
||||
// gen other app token
|
||||
claim.ID = newToken.ID
|
||||
claim.ExpiresAt = jwt.NewNumericDate(newToken.ExpiredAt)
|
||||
return auth.GenJwt(claim)
|
||||
} else {
|
||||
// 其他应用获取访问oa的token
|
||||
claim.Access = make(auth.Access, 0, 10)
|
||||
if data.OverPerm != "" {
|
||||
err = json.Unmarshal([]byte(data.OverPerm), &claim.Access)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return auth.GenJwt(claim)
|
||||
}
|
||||
} else if typ == "ufs" {
|
||||
claim.AID = refresh.AID
|
||||
claim.UID = refresh.UID
|
||||
claim.Name = refresh.Name
|
||||
claim.Icon = refresh.Icon
|
||||
claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 10))
|
||||
claim.Access = auth.Access{
|
||||
{Name: "fs", TID: "/", Level: auth.Do},
|
||||
}
|
||||
token := logv.AssertFuncErr(auth.GenJwt(claim))
|
||||
|
||||
cookie := &http.Cookie{
|
||||
Name: "fstoken", // Cookie 的名称
|
||||
Value: token, // Cookie 的值
|
||||
Path: "/fs/u/", // Cookie 的路径,通常是根路径
|
||||
MaxAge: 600, // Cookie 的最大年龄,单位是秒
|
||||
HttpOnly: true, // 是否仅限 HTTP(S) 访问
|
||||
Secure: false, // 是否通过安全连接传输 Cookie
|
||||
}
|
||||
http.SetCookie(x, cookie)
|
||||
|
||||
return token, nil
|
||||
} else {
|
||||
return nil, errs.ArgsInvalid
|
||||
}
|
||||
} else if opts.Code != nil && aid == cfg.Config.ID && opts.Salt != nil && opts.UserID != nil {
|
||||
// for oa login
|
||||
user := &M.User{}
|
||||
err = cfg.DB().Where("id = ?", opts.UserID).Find(user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logv.Info().Str("user", user.ID).Msg("login")
|
||||
code := *opts.Code
|
||||
salt := logv.AssertFuncErr(hex.DecodeString(*opts.Salt))
|
||||
key := logv.AssertFuncErr(hex.DecodeString(user.Code))
|
||||
de, err := utils.AesDecrypt([]byte(code), key, salt)
|
||||
if err != nil || de != user.ID {
|
||||
return nil, errs.AuthFailed
|
||||
}
|
||||
data.UserID = *opts.UserID
|
||||
data.AppID = aid
|
||||
data.ExpiredAt = time.Now().Add(time.Hour * 72)
|
||||
if opts.OverPerm != nil {
|
||||
data.OverPerm = *opts.OverPerm
|
||||
}
|
||||
if opts.Device != nil {
|
||||
data.Device = *opts.Device
|
||||
}
|
||||
data.Ip = x.GetRemoteIp()
|
||||
logv.AssertError(cfg.DB().Create(data).Error)
|
||||
claim.ID = data.ID
|
||||
claim.AID = aid
|
||||
claim.UID = user.ID
|
||||
claim.Name = user.Username
|
||||
claim.Icon = user.Icon
|
||||
claim.ExpiresAt = jwt.NewNumericDate(data.ExpiredAt)
|
||||
if user.Nickname != "" {
|
||||
claim.Name = user.Nickname
|
||||
}
|
||||
return auth.GenJwt(claim)
|
||||
} else {
|
||||
return nil, errs.ArgsInvalid
|
||||
}
|
||||
}
|
||||
|
||||
func tokenGet(x *rest.X) (any, error) {
|
||||
opts := &M.TokenGet{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Token{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func tokenPatch(x *rest.X) (any, error) {
|
||||
opts := &M.TokenPatch{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Token{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.ExpiredAt != nil {
|
||||
optsMap["expired_at"] = opts.ExpiredAt
|
||||
}
|
||||
if opts.OverPerm != nil {
|
||||
optsMap["over_perm"] = opts.OverPerm
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func tokenDelete(x *rest.X) (any, error) {
|
||||
opts := &M.TokenDelete{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.Token{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func tokenList(x *rest.X) (any, error) {
|
||||
opts := &M.TokenList{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*M.Token, 0, 10)
|
||||
|
||||
query := cfg.DB()
|
||||
query = query.Where("user_id = ?", opts.UserID)
|
||||
query = query.Where("app_id = ?", opts.AppID)
|
||||
err = query.Limit(opts.Limit).Find(&data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-23 16:28:13
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
// Auto generated by OneBD. DO NOT EDIT
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
)
|
||||
|
||||
func Use(r rest.Router) {
|
||||
useUser(r)
|
||||
useUserRole(r.SubRouter(":user_id/user_role"))
|
||||
}
|
||||
@ -1,171 +0,0 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"oa/cfg"
|
||||
"oa/errs"
|
||||
"oa/libs/auth"
|
||||
M "oa/models"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func useUser(r rest.Router) {
|
||||
r.Delete("/:user_id", auth.Check("user", "user_id", auth.DoDelete), userDelete)
|
||||
r.Get("/:user_id", auth.Check("user", "user_id", auth.DoRead), userGet)
|
||||
r.Get("/", auth.Check("user", "", auth.DoRead), userList)
|
||||
r.Patch("/:user_id", auth.Check("user", "user_id", auth.DoUpdate), userPatch)
|
||||
r.Post("/", userPost)
|
||||
}
|
||||
|
||||
func userDelete(x *rest.X) (any, error) {
|
||||
opts := &M.UserDelete{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.User{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func userGet(x *rest.X) (any, error) {
|
||||
opts := &M.UserGet{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.User{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func userList(x *rest.X) (any, error) {
|
||||
opts := &M.UserList{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*M.User, 0, 10)
|
||||
|
||||
query := cfg.DB()
|
||||
if opts.Username != nil {
|
||||
query = query.Where("username LIKE ?", opts.Username)
|
||||
}
|
||||
if opts.Nickname != nil {
|
||||
query = query.Where("nickname LIKE ?", opts.Nickname)
|
||||
}
|
||||
if opts.Email != nil {
|
||||
query = query.Where("email LIKE ?", opts.Email)
|
||||
}
|
||||
if opts.Phone != nil {
|
||||
query = query.Where("phone LIKE ?", opts.Phone)
|
||||
}
|
||||
if opts.Status != nil {
|
||||
query = query.Where("status = ?", opts.Status)
|
||||
}
|
||||
err = query.Find(&data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func userPatch(x *rest.X) (any, error) {
|
||||
opts := &M.UserPatch{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.User{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.Username != nil {
|
||||
optsMap["username"] = opts.Username
|
||||
}
|
||||
if opts.Nickname != nil {
|
||||
optsMap["nickname"] = opts.Nickname
|
||||
}
|
||||
if opts.Icon != nil {
|
||||
optsMap["icon"] = opts.Icon
|
||||
}
|
||||
if opts.Email != nil {
|
||||
optsMap["email"] = opts.Email
|
||||
}
|
||||
if opts.Phone != nil {
|
||||
optsMap["phone"] = opts.Phone
|
||||
}
|
||||
if opts.Status != nil {
|
||||
optsMap["status"] = opts.Status
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func userPost(x *rest.X) (any, error) {
|
||||
opts := &M.UserPost{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.User{}
|
||||
|
||||
data.ID = strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||
data.Username = opts.Username
|
||||
data.Salt = opts.Salt
|
||||
data.Code = opts.Code
|
||||
if data.Username == "" || len(data.Salt) != 32 || len(data.Code) != 64 {
|
||||
return nil, errs.ArgsInvalid.WithStr("username/salt/code length")
|
||||
}
|
||||
if opts.Nickname != nil {
|
||||
data.Nickname = *opts.Nickname
|
||||
}
|
||||
if opts.Icon != nil {
|
||||
data.Icon = *opts.Icon
|
||||
} else {
|
||||
data.Icon = fmt.Sprintf("https://public.veypi.com/img/avatar/%04d.jpg", rand.New(rand.NewSource(time.Now().UnixNano())).Intn(220))
|
||||
}
|
||||
if opts.Email != nil {
|
||||
data.Email = *opts.Email
|
||||
}
|
||||
if opts.Phone != nil {
|
||||
data.Phone = *opts.Phone
|
||||
}
|
||||
data.Status = 1
|
||||
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||
err := tx.Create(data).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
app := &M.App{}
|
||||
err = tx.Where("id = ?", cfg.Config.ID).First(app).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
status := "ok"
|
||||
switch app.Typ {
|
||||
case "private":
|
||||
return errs.AuthNoPerm.WithStr("not enable register")
|
||||
case "apply":
|
||||
status = "applying"
|
||||
case "public":
|
||||
}
|
||||
if app.Typ != "public" {
|
||||
}
|
||||
|
||||
return tx.Create(&M.AppUser{
|
||||
UserID: data.ID,
|
||||
AppID: cfg.Config.ID,
|
||||
Status: status,
|
||||
}).Error
|
||||
})
|
||||
return data, nil
|
||||
}
|
||||
@ -1,109 +0,0 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"oa/cfg"
|
||||
M "oa/models"
|
||||
)
|
||||
|
||||
func useUserRole(r rest.Router) {
|
||||
r.Delete("/:user_role_id", userRoleDelete)
|
||||
r.Get("/:user_role_id", userRoleGet)
|
||||
r.Get("/", userRoleList)
|
||||
r.Patch("/:user_role_id", userRolePatch)
|
||||
r.Post("/", userRolePost)
|
||||
}
|
||||
func userRoleList(x *rest.X) (any, error) {
|
||||
opts := &M.UserRoleList{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := make([]*struct {
|
||||
M.UserRole
|
||||
Username string `json:"username"`
|
||||
Nickname string `json:"nickname"`
|
||||
Icon string `json:"icon"`
|
||||
RoleName string `json:"role_name"`
|
||||
}, 0, 10)
|
||||
// data := make([]*M.UserRole, 0, 10)
|
||||
|
||||
query := cfg.DB().Debug().Table("user_roles").Select("user_roles.*,users.username,users.nickname,users.icon,roles.name as role_name").
|
||||
Joins("JOIN users ON users.id = user_roles.user_id").
|
||||
Joins("JOIN roles ON roles.id = user_roles.role_id")
|
||||
if opts.UserID != nil && *opts.UserID != "-" {
|
||||
query = query.Where("user_id LIKE ?", opts.UserID)
|
||||
}
|
||||
if opts.RoleID != nil {
|
||||
query = query.Where("role_id LIKE ?", opts.RoleID)
|
||||
}
|
||||
if opts.AppID != nil {
|
||||
query = query.Where("app_id LIKE ?", opts.AppID)
|
||||
}
|
||||
if opts.Status != nil {
|
||||
query = query.Where("status LIKE ?", opts.Status)
|
||||
}
|
||||
err = query.Scan(&data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func userRolePost(x *rest.X) (any, error) {
|
||||
opts := &M.UserRolePost{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.UserRole{}
|
||||
|
||||
data.UserID = opts.UserID
|
||||
data.RoleID = opts.RoleID
|
||||
data.AppID = opts.AppID
|
||||
data.Status = opts.Status
|
||||
err = cfg.DB().Create(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func userRoleGet(x *rest.X) (any, error) {
|
||||
opts := &M.UserRoleGet{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.UserRole{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func userRolePatch(x *rest.X) (any, error) {
|
||||
opts := &M.UserRolePatch{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.UserRole{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).First(data).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
optsMap := make(map[string]interface{})
|
||||
if opts.Status != nil {
|
||||
optsMap["status"] = opts.Status
|
||||
}
|
||||
err = cfg.DB().Model(data).Updates(optsMap).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
func userRoleDelete(x *rest.X) (any, error) {
|
||||
opts := &M.UserRoleDelete{}
|
||||
err := x.Parse(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := &M.UserRole{}
|
||||
|
||||
err = cfg.DB().Where("id = ?", opts.ID).Delete(data).Error
|
||||
|
||||
return data, err
|
||||
}
|
||||
@ -1,78 +0,0 @@
|
||||
//
|
||||
// app.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-10-22 15:42
|
||||
// Distributed under terms of the GPL license.
|
||||
//
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"oa/cfg"
|
||||
"oa/errs"
|
||||
"oa/libs/auth"
|
||||
"oa/libs/webdav"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/veypi/utils"
|
||||
"github.com/veypi/utils/logv"
|
||||
)
|
||||
|
||||
func NewAppFs(prefix string) func(http.ResponseWriter, *http.Request) {
|
||||
if strings.HasSuffix(prefix, "/") {
|
||||
prefix = prefix[:len(prefix)-1]
|
||||
}
|
||||
tmp := utils.PathJoin(cfg.Config.FsPath, "app")
|
||||
if !utils.FileExists(tmp) {
|
||||
logv.AssertError(os.MkdirAll(tmp, 0744))
|
||||
}
|
||||
|
||||
client := webdav.NewWebdav(tmp)
|
||||
client.Prefix = prefix
|
||||
client.RootIndex = 3
|
||||
client.GenSubPathFunc = func(r *http.Request) (string, error) {
|
||||
// /:aid/*p
|
||||
aid, root := getid(r.URL.Path, prefix)
|
||||
if root == "/" {
|
||||
// if !utils.FileExists(tmp + "/" + aid) {
|
||||
// os.MkdirAll(tmp+"/"+aid, 0744)
|
||||
// }
|
||||
}
|
||||
if aid == "" {
|
||||
return "", errs.AuthNoPerm
|
||||
}
|
||||
if root == "/pub" || strings.HasPrefix(root, "/pub/") {
|
||||
switch r.Method {
|
||||
case "GET", "HEAD", "POST":
|
||||
return "", nil
|
||||
default:
|
||||
}
|
||||
}
|
||||
// appfs权限等于app权限
|
||||
// TODO: 存在空文件覆盖重要文件的风险
|
||||
handlerLevle := auth.Do
|
||||
switch r.Method {
|
||||
case "PUT", "MKCOL", "COPY", "MOVE":
|
||||
handlerLevle = auth.DoCreate
|
||||
case "DELETE":
|
||||
handlerLevle = auth.DoDelete
|
||||
case "OPTIONS":
|
||||
// options请求不需要权限
|
||||
return "", nil
|
||||
default:
|
||||
handlerLevle = auth.DoRead
|
||||
}
|
||||
|
||||
payload, err := getToken(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if payload.Access.Check("app", aid, handlerLevle) {
|
||||
return "", nil
|
||||
}
|
||||
return "", errs.AuthNoPerm
|
||||
}
|
||||
return client.ServeHTTP
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
//
|
||||
// fs.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-10-22 15:51
|
||||
// Distributed under terms of the GPL license.
|
||||
//
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"oa/libs/webdav"
|
||||
)
|
||||
|
||||
func NewFs(dir_path, prefix string) *webdav.Handler {
|
||||
client := webdav.NewWebdav(dir_path)
|
||||
client.Prefix = prefix
|
||||
return client
|
||||
}
|
||||
@ -1,120 +0,0 @@
|
||||
//
|
||||
// user.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-10-22 15:49
|
||||
// Distributed under terms of the GPL license.
|
||||
//
|
||||
|
||||
package fs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"oa/cfg"
|
||||
"oa/errs"
|
||||
"oa/libs/auth"
|
||||
"oa/libs/webdav"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/veypi/utils"
|
||||
"github.com/veypi/utils/logv"
|
||||
)
|
||||
|
||||
func NewUserFs(prefix string) func(http.ResponseWriter, *http.Request) {
|
||||
if strings.HasSuffix(prefix, "/") {
|
||||
prefix = prefix[:len(prefix)-1]
|
||||
}
|
||||
tmp := utils.PathJoin(cfg.Config.FsPath, "u")
|
||||
if !utils.FileExists(tmp) {
|
||||
logv.AssertError(os.MkdirAll(tmp, 0744))
|
||||
}
|
||||
|
||||
client := webdav.NewWebdav(tmp)
|
||||
client.Prefix = prefix
|
||||
client.RootIndex = 4
|
||||
client.Logger = func(r *http.Request, err error) {
|
||||
}
|
||||
client.GenSubPathFunc = func(r *http.Request) (string, error) {
|
||||
// /:aid/*p
|
||||
uid, root := getid(r.URL.Path, prefix)
|
||||
if root == "/" {
|
||||
if !utils.FileExists(tmp + "/" + uid) {
|
||||
os.MkdirAll(tmp+"/"+uid, 0744)
|
||||
}
|
||||
}
|
||||
|
||||
if r.Method == "OPTIONS" {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
payload, err := getToken(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if payload.Access.CheckPrefix("fs", root, auth.Do) && payload.UID == uid {
|
||||
return "", nil
|
||||
}
|
||||
return "", errs.AuthNoPerm
|
||||
}
|
||||
return client.ServeHTTP
|
||||
}
|
||||
|
||||
func getid(url, prefix string) (string, string) {
|
||||
if strings.HasSuffix(prefix, "/") {
|
||||
prefix = prefix[:len(prefix)-1]
|
||||
}
|
||||
dir := strings.TrimPrefix(url, prefix)
|
||||
dirs := strings.Split(dir[1:], "/")
|
||||
id := ""
|
||||
root := "/"
|
||||
if len(dirs) > 0 {
|
||||
id = dirs[0]
|
||||
}
|
||||
if len(dirs) > 1 {
|
||||
root = "/" + strings.Join(dirs[1:], "/")
|
||||
}
|
||||
return id, root
|
||||
}
|
||||
|
||||
func getToken(r *http.Request) (*auth.Claims, error) {
|
||||
authHeader := r.Header.Get("Authorization")
|
||||
token := ""
|
||||
if authHeader != "" {
|
||||
typ := ""
|
||||
if tags := strings.Split(authHeader, " "); len(tags) > 1 {
|
||||
typ = strings.ToLower(tags[0])
|
||||
}
|
||||
if typ == "basic" {
|
||||
decodedAuth, err := base64.StdEncoding.DecodeString(authHeader[6:])
|
||||
if err != nil {
|
||||
return nil, errs.AuthInvalid
|
||||
}
|
||||
// 通常认证信息格式为 username:password
|
||||
credentials := string(decodedAuth)
|
||||
reader := bufio.NewReader(strings.NewReader(credentials))
|
||||
username, _ := reader.ReadString(':')
|
||||
password := credentials[len(username):]
|
||||
|
||||
username = strings.TrimSuffix(username, "\n")
|
||||
username = strings.TrimSuffix(username, ":")
|
||||
token = strings.TrimPrefix(password, "\n")
|
||||
} else if typ == "bearer" {
|
||||
token = authHeader[7:]
|
||||
}
|
||||
} else {
|
||||
acookie, err := r.Cookie("fstoken")
|
||||
if err == nil {
|
||||
token = acookie.Value
|
||||
}
|
||||
}
|
||||
if token == "" {
|
||||
return nil, errs.AuthNotFound
|
||||
}
|
||||
payload, err := auth.ParseJwt(token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
//
|
||||
// init.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-10-18 17:07
|
||||
// Distributed under terms of the GPL license.
|
||||
//
|
||||
|
||||
package builtin
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"oa/builtin/fs"
|
||||
"oa/cfg"
|
||||
"oa/libs"
|
||||
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"github.com/veypi/utils/logv"
|
||||
)
|
||||
|
||||
func Enable(app *rest.Application) {
|
||||
if cfg.Config.FsPath != "" {
|
||||
r := app.Router().SubRouter("fs")
|
||||
r.Set("/*", "OPTIONS", libs.CorsAllowAny)
|
||||
r.Use(libs.CorsAllowAny)
|
||||
r.Any("/a/:aid/*p", fs.NewAppFs("/fs/a"))
|
||||
r.Any("/u/:uid/*p", fs.NewUserFs("/fs/u"))
|
||||
}
|
||||
tsPorxy := httputil.NewSingleHostReverseProxy(logv.AssertFuncErr(url.Parse("http://v.v:8428")))
|
||||
fsProxy := fs.NewFs("/home/v/cache/", "")
|
||||
|
||||
app.SetMux(func(w http.ResponseWriter, r *http.Request) func(http.ResponseWriter, *http.Request) {
|
||||
if r.Host == "ts.oa.v" || r.Header.Get("mux") == "ts" {
|
||||
return tsPorxy.ServeHTTP
|
||||
} else if r.Host == "fs.oa.v" {
|
||||
return fsProxy.ServeHTTP
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@ -1,54 +0,0 @@
|
||||
//
|
||||
// cfg.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-20 16:10:16
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"github.com/veypi/utils"
|
||||
"github.com/veypi/utils/flags"
|
||||
"github.com/veypi/utils/logv"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
rest.RestConf
|
||||
DSN string `json:"dsn"`
|
||||
ID string `json:"id"`
|
||||
Key string `json:"key"`
|
||||
FsPath string `json:"fs"`
|
||||
AccessUrl string `json:"access_url"`
|
||||
}
|
||||
|
||||
var Config = &config{}
|
||||
|
||||
var CMD = flags.New("oa", "the backend server of oa")
|
||||
var CfgDump = CMD.SubCommand("cfg", "generate cfg file")
|
||||
|
||||
var configFile = CMD.String("f", "./dev.yaml", "the config file")
|
||||
|
||||
func init() {
|
||||
CMD.StringVar(&Config.Host, "h", "0.0.0.0", "host")
|
||||
CMD.IntVar(&Config.Port, "p", 4000, "port")
|
||||
CMD.StringVar(&Config.LoggerLevel, "l", "info", "log level")
|
||||
CMD.StringVar(&Config.DSN, "dsn", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8&parseTime=True&loc=Local", "data source name")
|
||||
CMD.Before = func() error {
|
||||
flags.LoadCfg(*configFile, Config)
|
||||
CMD.Parse()
|
||||
if Config.Key == "" {
|
||||
Config.Key = utils.RandSeq(32)
|
||||
}
|
||||
if Config.ID == "" {
|
||||
Config.ID = utils.RandSeq(32)
|
||||
}
|
||||
logv.SetLevel(logv.AssertFuncErr(logv.ParseLevel(Config.LoggerLevel)))
|
||||
return nil
|
||||
}
|
||||
CfgDump.Command = func() error {
|
||||
flags.DumpCfg(*configFile, Config)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -1,47 +0,0 @@
|
||||
//
|
||||
// db.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-09-20 16:10:16
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"github.com/veypi/OneBD/rest/middlewares/crud"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
)
|
||||
|
||||
var db *gorm.DB
|
||||
|
||||
var CmdDB = CMD.SubCommand("db", "database operations")
|
||||
var StaticObjs = crud.New()
|
||||
|
||||
func init() {
|
||||
CmdDB.SubCommand("migrate", "migrate database").Command = func() error {
|
||||
return crud.AutoMigrate(DB(), StaticObjs)
|
||||
}
|
||||
CmdDB.SubCommand("drop", "drop database").Command = func() error {
|
||||
return crud.AutoDrop(DB(), StaticObjs)
|
||||
}
|
||||
}
|
||||
|
||||
func DB() *gorm.DB {
|
||||
if db == nil {
|
||||
var err error
|
||||
db, err = gorm.Open(mysql.New(mysql.Config{
|
||||
DSN: Config.DSN,
|
||||
}), &gorm.Config{
|
||||
Logger: logger.Default.LogMode(logger.Silent),
|
||||
})
|
||||
// db, err = gorm.Open(postgres.Open(Config.DSN), &gorm.Config{
|
||||
// Logger: logger.Default.LogMode(logger.Silent),
|
||||
// })
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
return db
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
//
|
||||
// nats.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-10-11 17:32
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package cfg
|
||||
|
||||
import (
|
||||
"github.com/nats-io/nats-server/v2/server"
|
||||
"github.com/veypi/utils/logv"
|
||||
)
|
||||
|
||||
func RunNats() {
|
||||
opts := &server.Options{
|
||||
ConfigFile: "~/.config/oa/nats.cfg2",
|
||||
}
|
||||
ns, err := server.NewServer(opts)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// Start the nats server
|
||||
logv.Info().Msg("nats server start")
|
||||
ns.Start()
|
||||
}
|
||||
@ -1,105 +0,0 @@
|
||||
//
|
||||
// errors.go
|
||||
// Copyright (C) 2024 veypi <i@veypi.com>
|
||||
// 2024-08-01 15:40
|
||||
// Distributed under terms of the MIT license.
|
||||
//
|
||||
|
||||
package errs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
"github.com/veypi/OneBD/rest"
|
||||
"github.com/veypi/utils/logv"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func JsonResponse(x *rest.X, data any) error {
|
||||
x.WriteHeader(http.StatusOK)
|
||||
return x.JSON(map[string]any{"code": 0, "data": data})
|
||||
}
|
||||
|
||||
func JsonErrorResponse(x *rest.X, err error) {
|
||||
code, msg := errIter(err)
|
||||
x.WriteHeader(code / 100)
|
||||
x.JSON(map[string]any{"code": code, "err": msg})
|
||||
}
|
||||
|
||||
func errIter(err error) (code int, msg string) {
|
||||
code = 50000
|
||||
msg = err.Error()
|
||||
switch e := err.(type) {
|
||||
case *CodeErr:
|
||||
code = e.Code
|
||||
msg = e.Msg
|
||||
case *mysql.MySQLError:
|
||||
if e.Number == 1062 {
|
||||
code = DuplicateKey.Code
|
||||
msg = DuplicateKey.Msg
|
||||
} else {
|
||||
logv.Warn().Msgf("unhandled db error %d: %s", e.Number, err)
|
||||
msg = "db error"
|
||||
}
|
||||
case interface{ Unwrap() error }:
|
||||
code, _ = errIter(e.Unwrap())
|
||||
default:
|
||||
if errors.Is(e, gorm.ErrRecordNotFound) {
|
||||
code = ResourceNotFound.Code
|
||||
msg = ResourceNotFound.Msg
|
||||
} else if errors.Is(e, rest.ErrParse) {
|
||||
code = ArgsInvalid.Code
|
||||
msg = e.Error()
|
||||
} else {
|
||||
logv.Warn().Msgf("unhandled error type: %T\n%s", err, err)
|
||||
msg = e.Error()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type CodeErr struct {
|
||||
Code int
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (c *CodeErr) Error() string {
|
||||
return fmt.Sprintf("code: %d, msg: %s", c.Code, c.Msg)
|
||||
}
|
||||
|
||||
func (c *CodeErr) WithErr(e error) error {
|
||||
nerr := &CodeErr{
|
||||
Code: c.Code,
|
||||
Msg: fmt.Errorf("%s: %w", c.Msg, e).Error(),
|
||||
}
|
||||
return nerr
|
||||
}
|
||||
|
||||
func (c *CodeErr) WithStr(m string) error {
|
||||
nerr := &CodeErr{
|
||||
Code: c.Code,
|
||||
Msg: fmt.Errorf("%s: %s", c.Msg, m).Error(),
|
||||
}
|
||||
return nerr
|
||||
}
|
||||
|
||||
// New creates a new CodeMsg.
|
||||
func New(code int, msg string) *CodeErr {
|
||||
return &CodeErr{Code: code, Msg: msg}
|
||||
}
|
||||
|
||||
var (
|
||||
ArgsInvalid = New(40001, "args invalid")
|
||||
DuplicateKey = New(40002, "duplicate key")
|
||||
AuthNotFound = New(40100, "auth not found")
|
||||
AuthFailed = New(40101, "auth failed")
|
||||
AuthExpired = New(40102, "auth expired")
|
||||
AuthInvalid = New(40103, "auth invalid")
|
||||
AuthNoPerm = New(40104, "no permission")
|
||||
NotFound = New(40400, "not found")
|
||||
ResourceNotFound = New(40401, "resource not found")
|
||||
DBError = New(50010, "db error")
|
||||
)
|
||||
@ -1,50 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Music Player</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
</head>
|
||||
|
||||
<body class="flex items-center justify-center min-h-screen bg-gray-900">
|
||||
<div class="bg-gray-800 p-6 rounded-lg shadow-lg text-white w-full max-w-md space-y-4">
|
||||
<audio id="player" src="https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3"></audio>
|
||||
<button id="playButton"
|
||||
class="w-full py-2 px-4 bg-green-500 hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-green-500 transition duration-300 ease-in-out transform active:scale-95 flex items-center justify-center space-x-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-6 w-6">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2}
|
||||
d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.26a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
<span>Play</span>
|
||||
</button>
|
||||
<progress id="progressBar" value="0" max="1" class="w-full h-2 bg-gray-700 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-green-500 rounded-full"></div>
|
||||
</progress>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const player = document.getElementById('player');
|
||||
const playButton = document.getElementById('playButton');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
|
||||
playButton.addEventListener('click', () => {
|
||||
if (player.paused) {
|
||||
player.play();
|
||||
playButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-6 w-6"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg><span>Pause</span>';
|
||||
} else {
|
||||
player.pause();
|
||||
playButton.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor" class="h-6 w-6"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.26a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"/></svg><span>Play</span>';
|
||||
}
|
||||
});
|
||||
|
||||
player.addEventListener('timeupdate', () => {
|
||||
console.log(player.currentTime, player.duration);
|
||||
progressBar.value = player.currentTime / player.duration;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -1,26 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Card</title>
|
||||
<script src='v.js'></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="h-6 w-40 bg-green-100 flex gap-4">
|
||||
<slot name="a">slot a</slot>
|
||||
</div>
|
||||
<div class="h-14 w-40">
|
||||
<slot>
|
||||
<div ref="audio"></div>
|
||||
</slot>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
return {
|
||||
data: {a: 2}
|
||||
}
|
||||
</script>
|
||||
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.6 KiB |
@ -1,200 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>公司网站</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<script type="module" src="./v.js"></script>
|
||||
<script type="module" src="./t.js"></script>
|
||||
<style>
|
||||
div[ref] {
|
||||
position: relative;
|
||||
background: #999;
|
||||
width: 10rem;
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
div[ref]::before {
|
||||
content: 'dom组件';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn 1s ease-in-out;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body class="bg-gray-100 text-gray-800">
|
||||
|
||||
<div id='app'>
|
||||
<!-- 导航栏 -->
|
||||
<nav class="fixed w-full bg-white shadow-lg z-50">
|
||||
<div class="container mx-auto px-6 py-4 flex justify-between items-center">
|
||||
<div class="flex items-center">
|
||||
<a href="#" class="text-xl font-bold text-gray-800">公司名称</a>
|
||||
</div>
|
||||
<ul class="hidden md:flex space-x-6">
|
||||
<li><a href="#about" class="hover:text-blue-500 transition duration-300">关于我们</a></li>
|
||||
<li><a href="#services" class="hover:text-blue-500 transition duration-300">服务</a></li>
|
||||
<li><a href="#cases" class="hover:text-blue-500 transition duration-300">案例研究</a></li>
|
||||
<li><a href="#testimonials" class="hover:text-blue-500 transition duration-300">客户评价</a></li>
|
||||
<li><a href="#contact" class="hover:text-blue-500 transition duration-300">联系我们</a></li>
|
||||
</ul>
|
||||
<button class="md:hidden focus:outline-none">
|
||||
<svg class="h-6 w-6" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path d="M4 6h16M4 12h16m-7 6h7"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- 首页 -->
|
||||
<div id="home" class="relative h-screen flex items-center justify-center bg-cover bg-no-repeat fade-in"
|
||||
style="background-image:url('https://picsum.photos/1600/900');">
|
||||
<div class="absolute inset-0 bg-black opacity-50"></div>
|
||||
<div class="relative text-center max-w-2xl mx-auto p-4">
|
||||
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-extrabold tracking-tight text-white mb-6">欢迎来到我们的公司</h1>
|
||||
<p class="text-lg sm:text-xl leading-relaxed text-white">我们专注于提供高质量的服务和解决方案。</p>
|
||||
<a href="#contact"
|
||||
class="mt-8 inline-block px-6 py-3 bg-blue-500 hover:bg-blue-600 rounded-md text-white font-semibold transition duration-300">联系我们</a>
|
||||
<div ref="./t2/ta.js"></div>
|
||||
<div class="text-red-400" ref="./t2/input.js"></div>
|
||||
<div id="terminal"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 关于我们 -->
|
||||
<div id="about" class="py-20">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="max-w-4xl mx-auto text-center fade-in">
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight mb-6">关于我们</h2>
|
||||
<p class="text-lg sm:text-xl leading-relaxed text-gray-600">我们是一家致力于提供优质服务和技术支持的公司。</p>
|
||||
</div>
|
||||
<div class="mt-12 grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
<div ref="t2/companyHistory" class="fade-in"></div>
|
||||
<div class='abmem' ref="t2/teamMembers" class="fade-in"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 服务 -->
|
||||
<div id="services" class="py-20 bg-gray-200">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="max-w-4xl mx-auto text-center fade-in">
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight mb-6">我们的服务</h2>
|
||||
<p class="text-lg sm:text-xl leading-relaxed text-gray-600">我们提供一系列专业服务,满足不同客户的需求。</p>
|
||||
</div>
|
||||
<div class="mt-12 grid grid-cols-1 md:grid-cols-3 gap-12">
|
||||
<div ref="serviceOne" class="fade-in"></div>
|
||||
<div ref="serviceTwo" class="fade-in"></div>
|
||||
<div ref="serviceThree" class="fade-in"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 案例研究 -->
|
||||
<div id="cases" class="py-20">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="max-w-4xl mx-auto text-center fade-in">
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight mb-6">案例研究</h2>
|
||||
<p class="text-lg sm:text-xl leading-relaxed text-gray-600">查看我们过去成功的项目案例。</p>
|
||||
</div>
|
||||
<div class="mt-12 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-12">
|
||||
<div ref="caseStudyOne" class="fade-in"></div>
|
||||
<div ref="caseStudyTwo" class="fade-in"></div>
|
||||
<div ref="caseStudyThree" class="fade-in"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 客户评价 -->
|
||||
<div id="testimonials" class="py-20 bg-gray-200">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="max-w-4xl mx-auto text-center fade-in">
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight mb-6">客户评价</h2>
|
||||
<p class="text-lg sm:text-xl leading-relaxed text-gray-600">听听我们的客户怎么说。</p>
|
||||
</div>
|
||||
<div class="mt-12 grid grid-cols-1 md:grid-cols-2 gap-12">
|
||||
<div ref="testimonialOne" class="fade-in"></div>
|
||||
<div ref="testimonialTwo" class="fade-in"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 联系我们 -->
|
||||
<div id="contact" class="py-20">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="max-w-4xl mx-auto text-center fade-in">
|
||||
<h2 class="text-3xl sm:text-4xl lg:text-5xl font-bold tracking-tight mb-6">联系我们</h2>
|
||||
<p class="text-lg sm:text-xl leading-relaxed text-gray-600">如果您有任何问题或需要帮助,请随时与我们联系。</p>
|
||||
</div>
|
||||
<div class="mt-12 flex justify-center">
|
||||
<div ref="contactForm" class="w-full max-w-md fade-in"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部 -->
|
||||
<footer class="bg-gray-800 text-white py-8">
|
||||
<div class="container mx-auto px-6">
|
||||
<div class="flex flex-col md:flex-row justify-between items-center">
|
||||
<div class="mb-4 md:mb-0">
|
||||
<p>© 2023 公司名称. All rights reserved.</p>
|
||||
</div>
|
||||
<div>
|
||||
<ul class="flex space-x-6">
|
||||
<li><a href="#" class="hover:text-gray-300 transition duration-300">隐私政策</a></li>
|
||||
<li><a href="#" class="hover:text-gray-300 transition duration-300">条款条件</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script type="module">
|
||||
import v from './vyes.js'
|
||||
// 添加滚动监听事件以激活导航栏高亮
|
||||
document.addEventListener("scroll", function () {
|
||||
const sections = ["home", "about", "services", "cases", "testimonials", "contact"];
|
||||
let currentSection = "";
|
||||
|
||||
sections.forEach(section => {
|
||||
const element = document.getElementById(section);
|
||||
if (element.offsetTop <= window.scrollY + 100) {
|
||||
currentSection = section;
|
||||
}
|
||||
});
|
||||
|
||||
sections.forEach(section => {
|
||||
const link = document.querySelector(`a[href="#${section}"]`);
|
||||
if (link) {
|
||||
link.classList.toggle("text-blue-500", section === currentSection);
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -1,12 +0,0 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Default</title>
|
||||
</head>
|
||||
|
||||
<div>
|
||||
<div class="header flex"></div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
<script>
|
||||
console.log('default')
|
||||
</script>
|
||||
@ -1,6 +0,0 @@
|
||||
<body>
|
||||
<slot>></slot>
|
||||
</body>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
@ -1,183 +0,0 @@
|
||||
(function () {
|
||||
const style = document.createElement('style');
|
||||
|
||||
// 定义 CSS 文本
|
||||
const cssText = `
|
||||
.selected {
|
||||
outline: 2px solid #44f;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.panel {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 9999;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background-color: #f4f4f4;
|
||||
border-left: 1px solid #ccc;
|
||||
padding: 10px;
|
||||
box-shadow: -2px 0 5px rgba(0,0,0,0.1);
|
||||
display: none;
|
||||
resize: both;
|
||||
overflow: auto;
|
||||
}
|
||||
.resizer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
cursor: se-resize;
|
||||
background-color: #aaa;
|
||||
}
|
||||
.domline {
|
||||
width: 100%;
|
||||
}
|
||||
.domline:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
`;
|
||||
|
||||
style.innerHTML = cssText;
|
||||
let selectedElement = null;
|
||||
const panel = document.createElement('div');
|
||||
panel.classList.add('panel');
|
||||
|
||||
|
||||
const treeContainer = document.createElement('div');
|
||||
treeContainer.style.marginBottom = '10px';
|
||||
|
||||
const htmlEditor = document.createElement('textarea');
|
||||
htmlEditor.style.width = '100%';
|
||||
htmlEditor.rows = '10';
|
||||
htmlEditor.cols = '30';
|
||||
htmlEditor.placeholder = 'Edit element innerHTML here...';
|
||||
|
||||
const header = document.createElement('div');
|
||||
header.style.cursor = 'move';
|
||||
header.style.backgroundColor = '#ddd';
|
||||
header.style.padding = '5px';
|
||||
header.textContent = 'DOM Inspector';
|
||||
|
||||
panel.appendChild(style);
|
||||
panel.appendChild(header);
|
||||
panel.appendChild(treeContainer);
|
||||
panel.appendChild(htmlEditor);
|
||||
|
||||
document.body.appendChild(panel);
|
||||
|
||||
function createTree(node, parent, iter = 0) {
|
||||
if (!node || node.nodeType !== Node.ELEMENT_NODE) return;
|
||||
|
||||
if (iter > 3) {
|
||||
return;
|
||||
}
|
||||
const item = document.createElement('div');
|
||||
item.style.cursor = 'pointer';
|
||||
item.style.marginLeft = '10px';
|
||||
|
||||
const line = document.createElement('div');
|
||||
line.textContent = node.tagName.toLowerCase();
|
||||
line.classList.add('domline');
|
||||
line.addEventListener('click', (e) => {
|
||||
console.log(node)
|
||||
e.preventDefault();
|
||||
selectElement(node);
|
||||
});
|
||||
|
||||
item.appendChild(line);
|
||||
parent.appendChild(item);
|
||||
|
||||
Array.from(node.children).forEach(child => {
|
||||
createTree(child, item, iter + 1);
|
||||
});
|
||||
}
|
||||
|
||||
function selectElement(element) {
|
||||
if (selectedElement) {
|
||||
selectedElement.classList.remove('selected')
|
||||
// document.querySelectorAll('.blurred').forEach(el => el.classList.remove('blurred'));
|
||||
}
|
||||
selectedElement = element;
|
||||
selectedElement.classList.add('selected')
|
||||
|
||||
updatePanel(selectedElement);
|
||||
|
||||
// let pele = element.parentElement
|
||||
// while (pele && pele !== document.documentElement) {
|
||||
// pele.classList.add('blurred');
|
||||
// pele = pele.parentElement;
|
||||
// }
|
||||
}
|
||||
|
||||
function updatePanel(element) {
|
||||
|
||||
treeContainer.innerHTML = '';
|
||||
if (element.parentElement) {
|
||||
const parentItem = document.createElement('div');
|
||||
parentItem.textContent = `parent (${element.parentElement.tagName.toLowerCase()})`;
|
||||
parentItem.style.cursor = 'pointer';
|
||||
parentItem.style.color = 'blue';
|
||||
parentItem.style.fontWeight = 'bold';
|
||||
|
||||
parentItem.addEventListener('click', () => {
|
||||
selectElement(element.parentElement);
|
||||
});
|
||||
|
||||
treeContainer.appendChild(parentItem);
|
||||
}
|
||||
|
||||
createTree(element, treeContainer);
|
||||
|
||||
htmlEditor.value = element.outerHTML || '';
|
||||
|
||||
htmlEditor.oninput = () => {
|
||||
element.outerHTML = htmlEditor.value;
|
||||
};
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Control') {
|
||||
document.addEventListener('mousedown', mouseDownHandler);
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keyup', event => {
|
||||
if (event.key === 'Control') {
|
||||
document.removeEventListener('mousedown', mouseDownHandler);
|
||||
}
|
||||
});
|
||||
|
||||
function mouseDownHandler(event) {
|
||||
if (event.button === 0 && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
selectElement(event.target);
|
||||
panel.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Add draggable functionality to the sidebar
|
||||
let isDragging = false;
|
||||
let dragOffsetX, dragOffsetY;
|
||||
|
||||
header.addEventListener('mousedown', (e) => {
|
||||
isDragging = true;
|
||||
dragOffsetX = e.clientX - panel.offsetLeft;
|
||||
dragOffsetY = e.clientY - panel.offsetTop;
|
||||
});
|
||||
document.addEventListener('mousemove', (e) => {
|
||||
if (isDragging) {
|
||||
let top = e.clientY - dragOffsetY;
|
||||
if (top < 0) top = 0;
|
||||
let left = e.clientX - dragOffsetX;
|
||||
if (left < 0) left = 0;
|
||||
panel.style.left = `${left}px`;
|
||||
panel.style.top = `${top}px`;
|
||||
}
|
||||
});
|
||||
document.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
});
|
||||
})();
|
||||
@ -1,134 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Data Structure Topology with D3.js</title>
|
||||
<style>
|
||||
.node {
|
||||
fill: #f9f9f9;
|
||||
stroke: #ccc;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.field {
|
||||
font-size: 14px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.edge {
|
||||
fill: none;
|
||||
stroke: #333;
|
||||
stroke-width: 2px;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 12px;
|
||||
font-family: Arial, sans-serif;
|
||||
fill: #555;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="graph"></div>
|
||||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||||
<script>
|
||||
const data = {
|
||||
nodes: [
|
||||
{id: "Node A", fields: ["Field1: int", "Field2: string"]},
|
||||
{id: "Node B", fields: ["Field1: float", "Field2: bool"]},
|
||||
{id: "Node C", fields: ["Field1: date", "Field2: char"]}
|
||||
],
|
||||
links: [
|
||||
{source: "Node A", target: "Node B", type: "One-to-One"},
|
||||
{source: "Node A", target: "Node C", type: "One-to-Many"},
|
||||
{source: "Node B", target: "Node C", type: "Many-to-Many"}
|
||||
]
|
||||
};
|
||||
|
||||
const width = 800;
|
||||
const height = 600;
|
||||
|
||||
const svg = d3.select("#graph")
|
||||
.append("svg")
|
||||
.attr("width", width)
|
||||
.attr("height", height);
|
||||
|
||||
const simulation = d3.forceSimulation(data.nodes)
|
||||
.force("link", d3.forceLink(data.links).id(d => d.id))
|
||||
.force("charge", d3.forceManyBody().strength(-400))
|
||||
.force("center", d3.forceCenter(width / 2, height / 2));
|
||||
|
||||
const linkElements = svg.selectAll(".edge")
|
||||
.data(data.links)
|
||||
.enter()
|
||||
.append("line")
|
||||
.attr("class", "edge");
|
||||
|
||||
const nodeElements = svg.selectAll(".node")
|
||||
.data(data.nodes)
|
||||
.enter()
|
||||
.append("g")
|
||||
.attr("class", "node")
|
||||
.call(d3.drag()
|
||||
.on("start", dragstarted)
|
||||
.on("drag", dragged)
|
||||
.on("end", dragended));
|
||||
|
||||
nodeElements.append("rect")
|
||||
.attr("x", -75)
|
||||
.attr("y", -50)
|
||||
.attr("width", 150)
|
||||
.attr("height", 100)
|
||||
.attr("rx", 10)
|
||||
.attr("ry", 10);
|
||||
|
||||
nodeElements.append("text")
|
||||
.attr("dx", 0)
|
||||
.attr("dy", -20)
|
||||
.text(d => d.id)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("class", "field");
|
||||
|
||||
nodeElements.selectAll(".field-text")
|
||||
.data(d => d.fields)
|
||||
.enter()
|
||||
.append("text")
|
||||
.attr("dx", 0)
|
||||
.attr("dy", (d, i) => i * 20 + 5)
|
||||
.text(d => d)
|
||||
.attr("text-anchor", "middle")
|
||||
.attr("class", "field");
|
||||
|
||||
simulation.on("tick", () => {
|
||||
linkElements
|
||||
.attr("x1", d => d.source.x)
|
||||
.attr("y1", d => d.source.y)
|
||||
.attr("x2", d => d.target.x)
|
||||
.attr("y2", d => d.target.y);
|
||||
|
||||
nodeElements.attr("transform", d => `translate(${d.x},${d.y})`);
|
||||
});
|
||||
|
||||
function dragstarted(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0.3).restart();
|
||||
d.fx = d.x;
|
||||
d.fy = d.y;
|
||||
}
|
||||
|
||||
function dragged(event, d) {
|
||||
d.fx = event.x;
|
||||
d.fy = event.y;
|
||||
}
|
||||
|
||||
function dragended(event, d) {
|
||||
if (!event.active) simulation.alphaTarget(0);
|
||||
d.fx = null;
|
||||
d.fy = null;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@ -1,25 +0,0 @@
|
||||
|
||||
import v, { proxy } from '../vyes.js'
|
||||
|
||||
const data = proxy.Watch({ a: 2 })
|
||||
|
||||
const body = v('bg-gray-200 flex gap-4 h-12 items-center justify-evenly rounded', [
|
||||
v('', [v('span', 'count:'), v('span', () => data.a)]),
|
||||
v({
|
||||
typ: 'input',
|
||||
vbind: [data, 'a']
|
||||
}),
|
||||
v({
|
||||
class: 'select-none cursor-pointer hover:bg-gray-300 p-2 rounded',
|
||||
children: 'add',
|
||||
onclick: () => {
|
||||
data.a++
|
||||
}
|
||||
}),
|
||||
])
|
||||
|
||||
export const setup = (node, data) => {
|
||||
console.log(node, data)
|
||||
}
|
||||
|
||||
export default body
|
||||
@ -1,16 +0,0 @@
|
||||
/*
|
||||
* ta.js
|
||||
* Copyright (C) 2024 veypi <i@veypi.com>
|
||||
*
|
||||
* Distributed under terms of the GPL license.
|
||||
*/
|
||||
|
||||
import v from '../vyes.js'
|
||||
|
||||
const body = v('', '123')
|
||||
|
||||
export const setup = (node, data) => {
|
||||
console.log(node, data)
|
||||
}
|
||||
|
||||
export default body
|
||||
@ -1,28 +0,0 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: [
|
||||
"./components/**/*.{js,vue,ts}",
|
||||
"./layouts/**/*.vue",
|
||||
"./pages/**/*.vue",
|
||||
"./plugins/**/*.{js,ts}",
|
||||
"./app.vue",
|
||||
"./error.vue",
|
||||
"*.html",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
vprimary: '#2196f3',
|
||||
vsecondary: '#ecc94b',
|
||||
vaccents: '#ff9800',
|
||||
verror: '#f44336',
|
||||
vwaring: '#ff5722',
|
||||
vinfo: '#ffc107',
|
||||
vsuccess: '#53de58',
|
||||
vignore: '#d1d5db',
|
||||
}
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
}
|
||||
|
||||
@ -1,335 +0,0 @@
|
||||
/*
|
||||
* v.js
|
||||
* Copyright (C) 2024 veypi <i@veypi.com>
|
||||
*
|
||||
* Distributed under terms of the GPL license.
|
||||
*/
|
||||
import { proxy } from './vyes.js'
|
||||
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
const config = { childList: true, subtree: true };
|
||||
/**
|
||||
* Represents a book.
|
||||
* @param {Array} mutationsList
|
||||
* @param {MutationObserver} observer
|
||||
*/
|
||||
const callback = function(mutationsList, observer) {
|
||||
for (let mutation of mutationsList) {
|
||||
if (mutation.type === 'childList') {
|
||||
// console.log(mutation)
|
||||
// console.log('A child node has been added or removed.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const observer = new MutationObserver(callback);
|
||||
|
||||
var cacheUrl = {}
|
||||
var pendingRequests = {};
|
||||
|
||||
async function fetchFileWithCache(url) {
|
||||
if (cacheUrl[url]) {
|
||||
return Promise.resolve(cacheUrl[url]);
|
||||
}
|
||||
if (pendingRequests[url]) {
|
||||
return pendingRequests[url];
|
||||
}
|
||||
const promise = fetch(url)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
return response.text();
|
||||
})
|
||||
.then(txt => {
|
||||
cacheUrl[url] = txt;
|
||||
return txt;
|
||||
})
|
||||
.catch(error => {
|
||||
// console.error('Error fetching the file:', error);
|
||||
delete pendingRequests[url];
|
||||
throw error;
|
||||
})
|
||||
.finally(() => {
|
||||
delete pendingRequests[url];
|
||||
});
|
||||
pendingRequests[url] = promise;
|
||||
return promise;
|
||||
}
|
||||
|
||||
function extractDoubleBraceContents(str) {
|
||||
const regex = /{{|}}|{for{|{end{/g;
|
||||
let depth = 0;
|
||||
let currentIndex = 0
|
||||
let results = [];
|
||||
let match;
|
||||
let flag = ''
|
||||
|
||||
while ((match = regex.exec(str)) !== null) {
|
||||
if (match[0] === '{{') {
|
||||
if (depth === 0) {
|
||||
// 开始一个新的匹配
|
||||
results.push([flag, str.slice(currentIndex, match.index)]);
|
||||
currentIndex = match.index + 2
|
||||
flag = 'val'
|
||||
}
|
||||
depth++;
|
||||
} else if (match[0] === '}}') {
|
||||
depth--;
|
||||
if (depth === 0) {
|
||||
results.push([flag, str.slice(currentIndex, match.index)]);
|
||||
currentIndex = match.index + 2
|
||||
flag = ''
|
||||
}
|
||||
} else if (match[0] === '{for{') {
|
||||
if (depth === 0) {
|
||||
// 开始一个新的匹配
|
||||
results.push([flag, str.slice(currentIndex, match.index)]);
|
||||
currentIndex = match.index + 5
|
||||
flag = 'for'
|
||||
}
|
||||
depth++;
|
||||
} else if (match[0] === '{end{') {
|
||||
if (depth === 0) {
|
||||
// 开始一个新的匹配
|
||||
results.push([flag, str.slice(currentIndex, match.index)]);
|
||||
currentIndex = match.index + 5
|
||||
flag = 'end'
|
||||
}
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
results.push([flag, str.slice(currentIndex)]);
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
function extractBodyAndScript(htmlString) {
|
||||
const bodyMatch = htmlString.match(/<body[^>]*>([\s\S]*)<\/body>/i);
|
||||
const bodyContent = bodyMatch ? bodyMatch[1] : '';
|
||||
const bodyItems = extractDoubleBraceContents(bodyContent);
|
||||
|
||||
let scriptMatches = htmlString.match(/<script[^>]*>([\s\S]*?)<\/script>/ig) || []
|
||||
|
||||
|
||||
let scriptContents = []
|
||||
let srcReg = /<script\s+[^>]*src=["']([^"']+)["'][^>]*>/
|
||||
for (let s of scriptMatches) {
|
||||
let match = srcReg.exec(s)
|
||||
if (match && match.length) {
|
||||
// handle src script
|
||||
// console.log(match[1])
|
||||
} else {
|
||||
scriptContents.push(s.replace(/<script[^>]*>|<\/script>/ig, ''))
|
||||
}
|
||||
}
|
||||
|
||||
// 提取 <style> 内容
|
||||
const styleRegex = /<style[^>]*>([\s\S]*?)<\/style>/gi;
|
||||
const styleMatches = [...htmlString.matchAll(styleRegex)];
|
||||
const styleContents = styleMatches.map(match => match[1].trim());
|
||||
|
||||
// 解析 <style> 内容,按类名存储
|
||||
//
|
||||
const stylesBySelector = {};
|
||||
if (styleContents.length > 0) {
|
||||
styleContents.forEach(styleContent => {
|
||||
const ruleRegex = /(@[\w-]+|[^{]+)\s*\{([\s\S]*?)\}/g;
|
||||
let ruleMatch;
|
||||
|
||||
while ((ruleMatch = ruleRegex.exec(styleContent)) !== null) {
|
||||
const selector = ruleMatch[1].trim();
|
||||
const declarations = ruleMatch[2].trim();
|
||||
|
||||
if (selector.startsWith('@')) {
|
||||
// 处理 @keyframes 等特殊规则
|
||||
const keyframesName = selector.split(' ')[1];
|
||||
stylesBySelector[keyframesName] = declarations;
|
||||
} else {
|
||||
// 处理普通选择器
|
||||
stylesBySelector[selector] = declarations;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
bodyContent,
|
||||
bodyItems,
|
||||
scriptContents
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param{HTMLElement} targetDiv
|
||||
* @param{HTMLElement} tmplDom
|
||||
* */
|
||||
function replaceDom(targetDiv, tmplDom) {
|
||||
for (var i = 0; i < targetDiv.attributes.length; i++) {
|
||||
var attr = targetDiv.attributes[i];
|
||||
if (attr.name === 'class') {
|
||||
tmplDom.className = tmplDom.className + ' ' + attr.value
|
||||
} else {
|
||||
tmplDom.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
}
|
||||
var parent = targetDiv.parentNode;
|
||||
if (parent) {
|
||||
parent.replaceChild(tmplDom, targetDiv);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param{string} code
|
||||
* @param{Object} data
|
||||
* **/
|
||||
function run(code, data) {
|
||||
let args = '{' + Object.keys(data).join(',') + '}'
|
||||
if (code.indexOf('\n') === -1) {
|
||||
code = 'return ' + code
|
||||
}
|
||||
return new Function(args, code)(data)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param{HTMLElement} node
|
||||
* */
|
||||
async function setupRef(node, data, itercount = 0) {
|
||||
if (itercount > 100) {
|
||||
log.warn('infinite loop')
|
||||
return
|
||||
}
|
||||
const ref = node.getAttribute('ref') || ''
|
||||
node.setAttribute('tag', ref)
|
||||
node.removeAttribute('ref')
|
||||
if (!ref) {
|
||||
return
|
||||
}
|
||||
if (ref.endsWith('.js')) {
|
||||
let mod = await import(ref)
|
||||
let div = document.createElement('div')
|
||||
replaceDom(node, mod.default)
|
||||
node = mod.default
|
||||
if (typeof mod.setup == 'function') {
|
||||
let resp = mod.setup(node, data)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
const txt = await fetchFileWithCache(ref + ".html")
|
||||
|
||||
proxy.DomListen(node, () => {
|
||||
handleHtml(txt, node, data)
|
||||
})
|
||||
// let resp = new Function("{vnode,data}", ast.scriptContents.join('\n'))({ vnode: node, data: data })
|
||||
}
|
||||
for (let n of node.querySelectorAll("div[ref]")) {
|
||||
// 确保子元素先加载完
|
||||
// await setupRef(n, resp?.data || {}, itercount + 1)
|
||||
}
|
||||
}
|
||||
|
||||
function handleHtml(txt, node, data) {
|
||||
let ast = extractBodyAndScript(txt)
|
||||
let resp = new Function("", ast.scriptContents.join('\n'))()
|
||||
if (resp && typeof resp.data == 'function') {
|
||||
data = proxy.Watch(resp.data(data))
|
||||
}
|
||||
let bodyContent = ast.bodyContent
|
||||
if (ast.bodyItems.length > 1) {
|
||||
bodyContent = ''
|
||||
console.log(ast.bodyItems)
|
||||
let dataWrap = []
|
||||
let tmpData = Object.assign({}, data)
|
||||
let assign = true
|
||||
let idx
|
||||
for (idx = 0; idx < ast.bodyItems.length; idx++) {
|
||||
let item = ast.bodyItems[idx]
|
||||
if (!assign) {
|
||||
assign = true
|
||||
tmpData = Object.assign({}, data)
|
||||
dataWrap.forEach((item) => {
|
||||
if (item[1] >= 0) {
|
||||
tmpData = Object.assign(tmpData, item[0][item[2]])
|
||||
}
|
||||
})
|
||||
console.log(tmpData)
|
||||
}
|
||||
if (item[0] === 'val') {
|
||||
let valr = run(item[1], tmpData)
|
||||
bodyContent += valr
|
||||
} else if (item[0] === '') {
|
||||
bodyContent += item[1]
|
||||
} else if (item[0] == 'for') {
|
||||
dataWrap.push([run(item[1], tmpData), idx, 0])
|
||||
assign = false
|
||||
} else if (item[0] == 'end') {
|
||||
let tdw = dataWrap[dataWrap.length - 1]
|
||||
if (tdw[1] >= 0) {
|
||||
if (tdw[2] < tdw[0].length - 1) {
|
||||
tdw[2]++
|
||||
idx = tdw[1]
|
||||
} else {
|
||||
dataWrap.pop()
|
||||
}
|
||||
}
|
||||
assign = false
|
||||
}
|
||||
}
|
||||
}
|
||||
let div = document.createElement('div')
|
||||
div.innerHTML = bodyContent
|
||||
handleSlots(node, div)
|
||||
// node.innerHTML = div.innerHTML
|
||||
// let div = document.createElement('div')
|
||||
replaceDom(node, div)
|
||||
return resp
|
||||
}
|
||||
|
||||
function handleSlots(origin, template) {
|
||||
// handle slots
|
||||
let originSnap = { '': [] }
|
||||
let i = 0
|
||||
let child
|
||||
while (child = origin.children.item(i)) {
|
||||
i++
|
||||
let slotname = child.getAttribute ? child.getAttribute('slot') || '' : ''
|
||||
if (slotname !== '') {
|
||||
child.removeAttribute('slot')
|
||||
originSnap[slotname] = child
|
||||
} else {
|
||||
originSnap[''].push(child)
|
||||
}
|
||||
}
|
||||
|
||||
let slots = template.querySelectorAll('slot')
|
||||
slots.forEach((slot) => {
|
||||
let slotname = slot.getAttribute('name') || ''
|
||||
let snap = originSnap[slotname]
|
||||
if (snap && snap.length !== 0) {
|
||||
if (slotname === '') {
|
||||
slot.replaceWith(...snap)
|
||||
} else {
|
||||
slot.replaceWith(snap)
|
||||
}
|
||||
} else {
|
||||
slot.replaceWith(...slot.childNodes)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('DOMContentLoaded');
|
||||
const node = document.getElementById('app');
|
||||
// const node = document.body
|
||||
observer.observe(node, config);
|
||||
const sync = () => {
|
||||
let test = node.querySelectorAll("div[ref]")
|
||||
test.forEach(n => setupRef(n, {}))
|
||||
}
|
||||
sync()
|
||||
// setInterval(sync, 10)
|
||||
});
|
||||
|
||||
})();
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue