cyi-box: Memphis-style toolbox admin platform

Go(fun)+SQLite backend, Vue 3 frontend. Modules: dashboard, pay gateway
(Epay-compatible + CDK stock), license/card system (per-app appid, domain
binding), AI memory bank with built-in MCP server (/mcp, FTS5 search,
signed read links), server SSH monitor + terminal + SFTP, proxy pool with
protocol-level alive checks, DB manager (mysql/pg/sqlite + SQL
autocomplete + import/export), disk share, vault (AES-GCM + TOTP),
bookmarks, dev tools.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
2026-10-01 01:58:47 +08:00
co-authored by Devin
commit 834ecacdb9
326 changed files with 27813 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
package service
import (
"errors"
"fmt"
"net/url"
"strings"
"github.com/cyi-cc/fun"
"github.com/cyi-cc/cyi-box/backend/internal/dto"
"github.com/cyi-cc/cyi-box/backend/internal/store"
)
// BookmarkSvc 个人书签:列表驱动侧边栏快捷入口与书签页
type BookmarkSvc struct {
fun.Ctx
Store *store.Store
}
func (s *BookmarkSvc) List() ([]dto.BookmarkView, error) {
su, err := sessionFrom(s.RequestCtx)
if err != nil {
return nil, err
}
items, err := s.Store.ListBookmarks(su.User.Id)
if err != nil {
return nil, fmt.Errorf("bookmark: list failed: %w", err)
}
views := make([]dto.BookmarkView, 0, len(items))
for _, b := range items {
views = append(views, bookmarkView(b))
}
return views, nil
}
func (s *BookmarkSvc) Save(d dto.SaveBookmarkDto) (dto.BookmarkView, error) {
su, err := sessionFrom(s.RequestCtx)
if err != nil {
return dto.BookmarkView{}, err
}
title := strings.TrimSpace(d.Title)
raw := strings.TrimSpace(d.Url)
if title == "" || len(title) > 64 {
return dto.BookmarkView{}, fun.Error(4001, "名称必填且不超过 64 字符")
}
if raw != "" && !strings.Contains(raw, "://") {
raw = "https://" + raw // 裸域名自动补协议
}
u, err := url.Parse(raw)
if err != nil || u.Scheme == "" || u.Host == "" ||
(u.Scheme != "http" && u.Scheme != "https") {
return dto.BookmarkView{}, fun.Error(4001, "网址无效,需 http(s) 地址")
}
b := store.Bookmark{UserID: su.User.Id, Title: title, Url: raw}
if d.Id != nil {
b.Id = *d.Id
}
if d.Icon != nil {
b.Icon = strings.TrimSpace(*d.Icon)
}
if d.Sort != nil {
b.Sort = *d.Sort
}
if err := s.Store.SaveBookmark(&b); err != nil {
if errors.Is(err, store.ErrNotFound) {
return dto.BookmarkView{}, fun.Error(4001, "书签不存在")
}
return dto.BookmarkView{}, fmt.Errorf("bookmark: save failed: %w", err)
}
return bookmarkView(b), nil
}
func (s *BookmarkSvc) Delete(d dto.BookmarkIdDto) (dto.OkView, error) {
su, err := sessionFrom(s.RequestCtx)
if err != nil {
return dto.OkView{}, err
}
if err := s.Store.DeleteBookmark(su.User.Id, d.Id); err != nil {
if errors.Is(err, store.ErrNotFound) {
return dto.OkView{}, fun.Error(4001, "书签不存在")
}
return dto.OkView{}, fmt.Errorf("bookmark: delete failed: %w", err)
}
return dto.OkView{Message: "已删除"}, nil
}
func bookmarkView(b store.Bookmark) dto.BookmarkView {
return dto.BookmarkView{Id: b.Id, Title: b.Title, Url: b.Url, Icon: b.Icon, Sort: b.Sort}
}