f21fa5b5e8
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)。 本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
73 lines
2.2 KiB
Go
73 lines
2.2 KiB
Go
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)
|
|
}
|