cdrop — 跨 OS 剪贴板与文件传输服务
Commilitia Drop:自托管的跨设备剪贴板同步与点对点文件传输。 - 后端 Go(chi / SQLite WAL / SSE Hub / WebRTC signaling + 状态机 / Relay ring buffer),编译进单个 distroless 镜像(前端 go:embed)。 - 前端 React + TanStack Router + Zustand,自实现 SSE + WebRTC P2P,NAT 受阻时回退服务端中继;聚珍(Juzhen)CJK 综合排版。 - 桌面端 Wails v2(macOS / Windows),瘦客户端复用 web。 - 鉴权 OIDC PKCE(自建 Casdoor 等),refresh_token 信封加密存系统密钥库;iOS Shortcut 用 HS256 scoped token。 架构文档与变更记录见 docs 分支(PROJECT_BRIEF / FRONTEND_DESIGN / CHANGELOG)。 本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"commilitia.net/cdrop/internal/db"
|
||||
"commilitia.net/cdrop/internal/jwtauth"
|
||||
)
|
||||
|
||||
type deviceItem struct {
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Online bool `json:"online"`
|
||||
LastSeen int64 `json:"last_seen"`
|
||||
}
|
||||
|
||||
// handleDevices returns the calling user's known devices with live online flags.
|
||||
// Online = currently connected to /api/events; LastSeen = epoch from devices.last_seen.
|
||||
func (s *Server) handleDevices(w http.ResponseWriter, r *http.Request) {
|
||||
claims, _ := jwtauth.ClaimsFromContext(r.Context())
|
||||
|
||||
devs, err := s.queries.ListDevicesByUser(r.Context(), claims.UserID)
|
||||
if err != nil {
|
||||
slog.Error("list devices failed", "err", err, "user", claims.UserID)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "db"})
|
||||
return
|
||||
}
|
||||
|
||||
out := make([]deviceItem, 0, len(devs))
|
||||
for _, d := range devs {
|
||||
out = append(out, deviceItem{
|
||||
Name: d.Name,
|
||||
Type: d.Type,
|
||||
Online: s.hub.Online(claims.UserID, d.Name),
|
||||
LastSeen: d.LastSeen,
|
||||
})
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]any{"devices": out})
|
||||
}
|
||||
|
||||
// handleDeleteDevice 注销设备:删除 (user, name) 行 + Kick 在线 SSE + 立刻广播
|
||||
// 新的 presence。请求方若注销的是自己当前设备,前端需同步调用 logout,否则
|
||||
// 任何后续带 X-Device-Name 的认证请求都会被中间件再次 UPSERT 回来。
|
||||
func (s *Server) handleDeleteDevice(w http.ResponseWriter, r *http.Request) {
|
||||
claims, _ := jwtauth.ClaimsFromContext(r.Context())
|
||||
name := chi.URLParam(r, "name")
|
||||
if name == "" {
|
||||
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "missing device name"})
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := s.queries.DeleteDevice(r.Context(), db.DeleteDeviceParams{
|
||||
UserID: claims.UserID,
|
||||
Name: name,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("delete device failed", "err", err, "user", claims.UserID, "name", name)
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "db"})
|
||||
return
|
||||
}
|
||||
if rows == 0 {
|
||||
writeJSON(w, http.StatusNotFound, map[string]string{"error": "device not found"})
|
||||
return
|
||||
}
|
||||
|
||||
s.hub.Kick(claims.UserID, name)
|
||||
s.hub.PublishPresence(r.Context(), claims.UserID)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
Reference in New Issue
Block a user