feat: initial open-source release of Pipely
OTA update server designed for high-concurrency, low-bandwidth deployments. GORM-backed PostgreSQL, JWT auth, device management, artefact versioning, deployment rollout with gated rate-limited downloads, and a React admin panel.
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
package artefact
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
|
||||
"onixbyte.com/message-converter-manifest/internal/database"
|
||||
"onixbyte.com/message-converter-manifest/internal/model"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
db *gorm.DB
|
||||
artefactDir string
|
||||
}
|
||||
|
||||
func NewHandler(db *gorm.DB, artefactDir string) *Handler {
|
||||
return &Handler{db: db, artefactDir: artefactDir}
|
||||
}
|
||||
|
||||
func (h *Handler) Upload(c *gin.Context) {
|
||||
version := c.PostForm("version")
|
||||
if version == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "version is required"})
|
||||
return
|
||||
}
|
||||
|
||||
deltaRef := c.PostForm("deltaRef")
|
||||
metadata := c.PostForm("metadata")
|
||||
if metadata == "" {
|
||||
metadata = "{}"
|
||||
}
|
||||
|
||||
file, header, err := c.Request.FormFile("file")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "file is required: " + err.Error()})
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
destDir := filepath.Join(h.artefactDir, version)
|
||||
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create directory"})
|
||||
return
|
||||
}
|
||||
|
||||
destPath := filepath.Join(destDir, header.Filename)
|
||||
out, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create file"})
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
hasher := sha256.New()
|
||||
writer := io.MultiWriter(out, hasher)
|
||||
|
||||
size, err := io.Copy(writer, file)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to write file"})
|
||||
return
|
||||
}
|
||||
|
||||
sha256Hash := fmt.Sprintf("%x", hasher.Sum(nil))
|
||||
|
||||
artefact := &model.Artefact{
|
||||
Version: version,
|
||||
FileName: header.Filename,
|
||||
FilePath: destPath,
|
||||
FileSize: size,
|
||||
SHA256Hash: sha256Hash,
|
||||
DeltaRef: deltaRef,
|
||||
IsDelta: deltaRef != "",
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
if err := database.CreateArtefact(h.db, artefact); err != nil {
|
||||
os.Remove(destPath)
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save artefact: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, artefact)
|
||||
}
|
||||
|
||||
func (h *Handler) List(c *gin.Context) {
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "20"))
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if pageSize < 1 || pageSize > 100 {
|
||||
pageSize = 20
|
||||
}
|
||||
|
||||
artefacts, total, err := database.ListArtefacts(h.db, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, model.PaginatedResponse{
|
||||
Data: artefacts,
|
||||
Total: total,
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) Get(c *gin.Context) {
|
||||
id, err := strconv.ParseInt(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
|
||||
return
|
||||
}
|
||||
|
||||
artefact, err := database.GetArtefactByID(h.db, id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Artefact not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, artefact)
|
||||
}
|
||||
Reference in New Issue
Block a user