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:
@@ -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}
|
||||
}
|
||||
Reference in New Issue
Block a user