init: 初始化项目

This commit is contained in:
0264408
2026-03-10 16:26:48 +08:00
commit 57e0ef2cf6
79 changed files with 8943 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
package handler
import (
"nebula/internal/api/response"
"nebula/internal/app"
"github.com/gin-gonic/gin"
)
type AppHandler struct {
service *app.AppService
}
func NewAppHandler(service *app.AppService) *AppHandler {
return &AppHandler{service: service}
}
func (h *AppHandler) List(c *gin.Context) {
apps, err := h.service.List()
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, apps)
}
func (h *AppHandler) Get(c *gin.Context) {
id := c.Param("id")
app, err := h.service.Get(id)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, app)
}
func (h *AppHandler) Create(c *gin.Context) {
var app app.App
if err := c.ShouldBindJSON(&app); err != nil {
response.FailBadRequest(c, err.Error())
return
}
err := h.service.Create(app)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "created")
}
func (h *AppHandler) Update(c *gin.Context) {
id := c.Param("id")
var app app.App
if err := c.ShouldBindJSON(&app); err != nil {
response.FailBadRequest(c, err.Error())
return
}
err := h.service.Update(id, map[string]any{
"name": app.Name,
"description": app.Description,
})
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "updated")
}
func (h *AppHandler) Delete(c *gin.Context) {
id := c.Param("id")
err := h.service.Delete(id)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "deleted")
}

View File

@@ -0,0 +1,212 @@
package handler
import (
"nebula/internal/api/response"
"nebula/internal/asset"
"path/filepath"
"strconv"
"github.com/gin-gonic/gin"
)
type AssetHandler struct {
service *asset.AssetService
}
func NewAssetHandler(service *asset.AssetService) *AssetHandler {
return &AssetHandler{service: service}
}
// List 获取所有资源列表
// GET /api/assets
func (h *AssetHandler) List(c *gin.Context) {
assets, err := h.service.List()
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, assets)
}
// ListByRelease 获取指定发布版本的所有资源
// GET /api/releases/:id/assets
func (h *AssetHandler) ListByRelease(c *gin.Context) {
releaseID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid release id")
return
}
assets, err := h.service.ListByRelease(uint(releaseID))
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, assets)
}
// Get 获取单个资源详情
// GET /api/assets/:id
func (h *AssetHandler) Get(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid asset id")
return
}
ast, err := h.service.Get(uint(id))
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, ast)
}
// Upload 上传文件并创建资源
// POST /api/releases/:id/assets/upload
func (h *AssetHandler) Upload(c *gin.Context) {
releaseID, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid release id")
return
}
// 获取表单参数
platform := c.PostForm("platform")
arch := c.PostForm("arch")
if platform == "" || arch == "" {
response.FailBadRequest(c, "platform and arch are required")
return
}
// 获取上传的文件
file, err := c.FormFile("file")
if err != nil {
response.FailBadRequest(c, "file is required")
return
}
// 验证文件扩展名(可选)
ext := filepath.Ext(file.Filename)
allowedExts := map[string]bool{
".exe": true, ".dmg": true, ".pkg": true, ".deb": true,
".rpm": true, ".appimage": true, ".zip": true, ".tar.gz": true,
".msi": true, ".app": true,
}
if !allowedExts[ext] {
response.FailBadRequest(c, "unsupported file type: "+ext)
return
}
// 上传文件
ast, err := h.service.Upload(uint(releaseID), platform, arch, file)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, ast)
}
// Create 创建资源记录(用于外部 URL
// POST /api/assets
func (h *AssetHandler) Create(c *gin.Context) {
var ast asset.Asset
if err := c.ShouldBindJSON(&ast); err != nil {
response.FailBadRequest(c, err.Error())
return
}
// 基本验证
if ast.ReleaseID == 0 {
response.FailBadRequest(c, "releaseId is required")
return
}
if ast.Platform == "" {
response.FailBadRequest(c, "platform is required")
return
}
if ast.Arch == "" {
response.FailBadRequest(c, "arch is required")
return
}
if ast.URL == "" {
response.FailBadRequest(c, "url is required")
return
}
err := h.service.Create(ast)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "created")
}
// Update 更新资源信息
// PUT /api/assets/:id
func (h *AssetHandler) Update(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid asset id")
return
}
var ast asset.Asset
if err := c.ShouldBindJSON(&ast); err != nil {
response.FailBadRequest(c, err.Error())
return
}
data := map[string]any{
"platform": ast.Platform,
"arch": ast.Arch,
"signature": ast.Signature,
"checksum": ast.Checksum,
}
err = h.service.Update(uint(id), data)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "updated")
}
// Delete 删除资源
// DELETE /api/assets/:id
func (h *AssetHandler) Delete(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid asset id")
return
}
err = h.service.Delete(uint(id))
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "deleted")
}
// Download 下载资源文件
// GET /api/assets/:id/download
func (h *AssetHandler) Download(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid asset id")
return
}
// 获取存储路径
storagePath, err := h.service.GetStoragePath(uint(id))
if err != nil {
response.FailServer(c, err.Error())
return
}
// 返回文件Gin 会自动处理文件下载)
c.File(storagePath)
}

View File

@@ -0,0 +1,124 @@
package handler
import (
"nebula/internal/api/response"
"nebula/internal/auth"
"github.com/gin-gonic/gin"
)
type AuthHandler struct {
service *auth.AuthService
}
func NewAuthHandler(service *auth.AuthService) *AuthHandler {
return &AuthHandler{service: service}
}
// Register 用户注册
// POST /api/auth/register
func (h *AuthHandler) Register(c *gin.Context) {
var req auth.RegisterRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.FailBadRequest(c, err.Error())
return
}
user, tokens, err := h.service.Register(req)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, gin.H{
"user": user,
"tokens": tokens,
})
}
// Login 用户登录
// POST /api/auth/login
func (h *AuthHandler) Login(c *gin.Context) {
var req auth.LoginRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.FailBadRequest(c, err.Error())
return
}
user, tokens, err := h.service.Login(req)
if err != nil {
response.Fail(c, 401, err.Error())
return
}
response.Ok(c, gin.H{
"user": user,
"tokens": tokens,
})
}
// RefreshToken 刷新访问令牌
// POST /api/auth/refresh
func (h *AuthHandler) RefreshToken(c *gin.Context) {
var req auth.RefreshTokenRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.FailBadRequest(c, err.Error())
return
}
tokens, err := h.service.RefreshToken(req)
if err != nil {
response.Fail(c, 401, err.Error())
return
}
response.Ok(c, tokens)
}
// GetProfile 获取当前用户信息
// GET /api/auth/profile
func (h *AuthHandler) GetProfile(c *gin.Context) {
userID, exists := auth.GetCurrentUserID(c)
if !exists {
response.Fail(c, 401, "unauthorized")
return
}
user, err := h.service.GetUserByID(userID)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, user)
}
// ChangePasswordRequest 修改密码请求
type ChangePasswordRequest struct {
OldPassword string `json:"oldPassword" binding:"required"`
NewPassword string `json:"newPassword" binding:"required,min=6"`
}
// ChangePassword 修改密码
// POST /api/auth/change-password
func (h *AuthHandler) ChangePassword(c *gin.Context) {
userID, exists := auth.GetCurrentUserID(c)
if !exists {
response.Fail(c, 401, "unauthorized")
return
}
var req ChangePasswordRequest
if err := c.ShouldBindJSON(&req); err != nil {
response.FailBadRequest(c, err.Error())
return
}
err := h.service.ChangePassword(userID, req.OldPassword, req.NewPassword)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "password changed successfully")
}

View File

@@ -0,0 +1,11 @@
package handler
import "gorm.io/gorm"
type Handler struct {
db *gorm.DB
}
func New(db *gorm.DB) *Handler {
return &Handler{db: db}
}

View File

@@ -0,0 +1,155 @@
package handler
import (
"nebula/internal/api/response"
"nebula/internal/release"
"strconv"
"github.com/gin-gonic/gin"
)
type ReleaseHandler struct {
service *release.ReleaseService
}
func NewReleaseHandler(service *release.ReleaseService) *ReleaseHandler {
return &ReleaseHandler{service: service}
}
// List 获取所有版本列表
// GET /api/releases
func (h *ReleaseHandler) List(c *gin.Context) {
releases, err := h.service.List()
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, releases)
}
// ListByApp 获取指定应用的所有版本
// GET /api/apps/:id/releases
func (h *ReleaseHandler) ListByApp(c *gin.Context) {
appID := c.Param("id")
channel := c.Query("channel") // 可选的渠道过滤
var releases []release.Release
var err error
if channel != "" {
releases, err = h.service.ListByAppAndChannel(appID, channel)
} else {
releases, err = h.service.ListByApp(appID)
}
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, releases)
}
// Get 获取单个版本详情
// GET /api/releases/:id
func (h *ReleaseHandler) Get(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid release id")
return
}
rel, err := h.service.Get(uint(id))
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, rel)
}
// GetLatest 获取应用的最新版本
// GET /api/apps/:id/releases/latest
func (h *ReleaseHandler) GetLatest(c *gin.Context) {
appID := c.Param("id")
channel := c.Query("channel")
rel, err := h.service.GetLatest(appID, channel)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, rel)
}
// Create 创建新版本
// POST /api/releases
func (h *ReleaseHandler) Create(c *gin.Context) {
var rel release.Release
if err := c.ShouldBindJSON(&rel); err != nil {
response.FailBadRequest(c, err.Error())
return
}
// 基本验证
if rel.AppID == "" {
response.FailBadRequest(c, "app_id is required")
return
}
if rel.Version == "" {
response.FailBadRequest(c, "version is required")
return
}
err := h.service.Create(rel)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "created")
}
// Update 更新版本信息
// PUT /api/releases/:id
func (h *ReleaseHandler) Update(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid release id")
return
}
var rel release.Release
if err := c.ShouldBindJSON(&rel); err != nil {
response.FailBadRequest(c, err.Error())
return
}
data := map[string]any{
"version": rel.Version,
"notes": rel.Notes,
"channel": rel.Channel,
"pub_date": rel.PubDate,
}
err = h.service.Update(uint(id), data)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "updated")
}
// Delete 删除版本
// DELETE /api/releases/:id
func (h *ReleaseHandler) Delete(c *gin.Context) {
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
if err != nil {
response.FailBadRequest(c, "invalid release id")
return
}
err = h.service.Delete(uint(id))
if err != nil {
response.FailServer(c, err.Error())
return
}
response.OkMsg(c, "deleted")
}

View File

@@ -0,0 +1,26 @@
package handler
import (
"nebula/internal/api/response"
"nebula/internal/updater"
"github.com/gin-gonic/gin"
)
func (h *Handler) CheckUpdate(c *gin.Context) {
req := updater.CheckRequest{
App: c.Query("app"),
Version: c.Query("version"),
Platform: c.Query("platform"),
Arch: c.Query("arch"),
}
resp, err := updater.CheckUpdate(h.db, req)
if err != nil {
response.FailServer(c, err.Error())
return
}
response.Ok(c, resp)
}