Files
protoc-frontend/backend/handler.go
2026-03-27 00:18:42 +03:00

727 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"encoding/json"
"fmt"
"io"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"gorm.io/gorm"
)
// --- Helpers ---
func parseUUID(c *gin.Context, param string) (uuid.UUID, bool) {
id, err := uuid.Parse(c.Param(param))
if err != nil {
c.JSON(400, gin.H{"detail": "invalid uuid"})
return uuid.Nil, false
}
return id, true
}
func templateAttributeDetails(db *gorm.DB, templateID uuid.UUID) []gin.H {
var allAttrs []Attribute
db.Find(&allAttrs)
var tas []TemplateAttribute
db.Where("template_id = ?", templateID).Preload("Attribute").Find(&tas)
taMap := map[uuid.UUID]*TemplateAttribute{}
for i := range tas {
taMap[tas[i].AttributeID] = &tas[i]
}
result := []gin.H{}
for _, a := range allAttrs {
detail := gin.H{
"attribute_id": a.ID,
"attribute_name": a.Name,
"is_required": a.IsRequired,
"value": nil,
}
if ta, ok := taMap[a.ID]; ok {
detail["value"] = ta.Value
}
result = append(result, detail)
}
return result
}
func templateWithAttrs(db *gorm.DB, t Template) gin.H {
return gin.H{
"id": t.ID,
"name": t.Name,
"description": t.Description,
"elements": json.RawMessage(t.Elements),
"group_id": t.GroupID,
"attributes": templateAttributeDetails(db, t.ID),
"order": t.Order,
"created_at": t.CreatedAt,
"updated_at": t.UpdatedAt,
"deleted_at": t.DeletedAt,
}
}
// --- Templates ---
func getTemplates(c *gin.Context, db *gorm.DB) {
var templates []Template
db.Find(&templates)
result := []gin.H{}
for _, t := range templates {
result = append(result, templateWithAttrs(db, t))
}
c.JSON(200, gin.H{"templates": result})
}
func getTemplate(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var t Template
if db.First(&t, "id = ?", id).Error != nil {
c.JSON(200, gin.H{"template": nil})
return
}
c.JSON(200, gin.H{"template": templateWithAttrs(db, t)})
}
func createTemplate(c *gin.Context, db *gorm.DB) {
var input struct {
Name string `json:"name"`
Description string `json:"description"`
Elements json.RawMessage `json:"elements"`
Attributes []setTemplateAttrInput `json:"attributes"`
GroupID *uuid.UUID `json:"group_id"`
}
if c.BindJSON(&input) != nil {
return
}
t := Template{Name: input.Name, Description: &input.Description, Elements: JSON(input.Elements), GroupID: input.GroupID}
db.Create(&t)
for _, a := range input.Attributes {
db.Create(&TemplateAttribute{TemplateID: t.ID, AttributeID: a.AttributeID, Value: a.Value})
}
c.JSON(200, gin.H{"id": t.ID})
}
type setTemplateAttrInput struct {
AttributeID uuid.UUID `json:"attribute_id"`
Value *string `json:"value"`
}
func patchTemplate(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var t Template
if db.First(&t, "id = ?", id).Error != nil {
c.JSON(404, gin.H{"detail": "Template not found"})
return
}
var input struct {
Name *string `json:"name"`
Description *string `json:"description"`
Elements *json.RawMessage `json:"elements"`
Attributes []setTemplateAttrInput `json:"attributes"`
GroupID interface{} `json:"group_id"`
Order *float64 `json:"order"`
}
if c.BindJSON(&input) != nil {
return
}
updates := map[string]interface{}{}
if input.Name != nil {
updates["name"] = *input.Name
}
if input.Description != nil {
updates["description"] = *input.Description
}
if input.Elements != nil {
updates["elements"] = JSON(*input.Elements)
}
if input.Order != nil {
updates["order"] = *input.Order
}
if input.GroupID != nil {
switch v := input.GroupID.(type) {
case string:
if v == "ungroup" {
updates["group_id"] = nil
} else {
if uid, err := uuid.Parse(v); err == nil {
updates["group_id"] = uid
}
}
}
}
if len(updates) > 0 {
db.Model(&t).Updates(updates)
}
for _, a := range input.Attributes {
res := db.Model(&TemplateAttribute{}).Where("template_id = ? AND attribute_id = ?", id, a.AttributeID).Update("value", a.Value)
if res.RowsAffected == 0 {
db.Create(&TemplateAttribute{TemplateID: id, AttributeID: a.AttributeID, Value: a.Value})
}
}
c.JSON(200, gin.H{"id": t.ID})
}
func deleteTemplate(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
res := db.Delete(&Template{}, "id = ?", id)
if res.RowsAffected == 0 {
c.JSON(404, gin.H{"detail": "Template not found"})
return
}
c.JSON(200, gin.H{"success": true, "template_id": id})
}
func copyTemplate(c *gin.Context, db *gorm.DB) {
var input struct {
TemplateID uuid.UUID `json:"template_id"`
NewName string `json:"new_name"`
NewDescription *string `json:"new_description"`
NewAttributes []setTemplateAttrInput `json:"new_attributes"`
GroupID *uuid.UUID `json:"group_id"`
CopyFirstSheetEmpty bool `json:"copy_first_sheet_empty"`
}
if c.BindJSON(&input) != nil {
return
}
var orig Template
if db.First(&orig, "id = ?", input.TemplateID).Error != nil {
c.JSON(400, gin.H{"detail": "Template not found"})
return
}
// Update element IDs
newElements := updateElementIDs(orig.Elements)
newT := Template{Name: input.NewName, Description: input.NewDescription, Elements: newElements, GroupID: input.GroupID}
db.Create(&newT)
// Copy files
var files []File
db.Where("template_id = ?", input.TemplateID).Find(&files)
for _, f := range files {
newF := File{Name: f.Name, Content: f.Content, Cells: f.Cells, TemplateID: &newT.ID}
db.Create(&newF)
}
// Copy sheets
var sheets []Sheet
db.Where("template_id = ?", input.TemplateID).Find(&sheets)
for _, s := range sheets {
cells := s.Cells
if s.Name == "L" && input.CopyFirstSheetEmpty {
cells = JSON("{}")
}
newS := Sheet{Name: s.Name, Cells: cells, TemplateID: newT.ID}
db.Create(&newS)
}
// Copy or set attributes
if len(input.NewAttributes) > 0 {
for _, a := range input.NewAttributes {
db.Create(&TemplateAttribute{TemplateID: newT.ID, AttributeID: a.AttributeID, Value: a.Value})
}
} else {
var origAttrs []TemplateAttribute
db.Where("template_id = ?", input.TemplateID).Find(&origAttrs)
for _, a := range origAttrs {
db.Create(&TemplateAttribute{TemplateID: newT.ID, AttributeID: a.AttributeID, Value: a.Value})
}
}
c.JSON(200, gin.H{"new_template_id": newT.ID})
}
func updateElementIDs(elements JSON) JSON {
var parsed map[string]interface{}
if json.Unmarshal(elements, &parsed) != nil || parsed == nil {
return elements
}
newElements := map[string]interface{}{}
for _, data := range parsed {
newID := uuid.New().String()
if m, ok := data.(map[string]interface{}); ok {
m["id"] = newID
}
newElements[newID] = data
}
result, _ := json.Marshal(newElements)
return JSON(result)
}
// --- Files ---
func getFiles(c *gin.Context, db *gorm.DB) {
var files []File
q := db.Model(&File{})
if tid := c.Query("template_id"); tid != "" {
q = q.Where("template_id = ?", tid)
}
q.Find(&files)
c.JSON(200, gin.H{"files": files})
}
func getFile(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var f File
if db.First(&f, "id = ?", id).Error != nil {
c.JSON(200, gin.H{"file": nil})
return
}
c.JSON(200, gin.H{"file": f})
}
func uploadFile(c *gin.Context, db *gorm.DB) {
templateID, err := uuid.Parse(c.Query("template_id"))
if err != nil {
c.JSON(400, gin.H{"detail": "invalid template_id"})
return
}
fileHeader, err := c.FormFile("file")
if err != nil {
c.JSON(400, gin.H{"detail": "file required"})
return
}
f, _ := fileHeader.Open()
defer f.Close()
content, _ := io.ReadAll(f)
cellsData, err := ReadCells(content)
if err != nil {
c.JSON(500, gin.H{"detail": fmt.Sprintf("failed to read cells: %v", err)})
return
}
cellsJSON, _ := json.Marshal(cellsData)
file := File{Name: fileHeader.Filename, Content: content, Cells: JSON(cellsJSON), TemplateID: &templateID}
db.Create(&file)
c.JSON(200, gin.H{"file_id": file.ID, "cells": cellsData})
}
// --- Sheets ---
func getSheets(c *gin.Context, db *gorm.DB) {
var sheets []Sheet
q := db.Model(&Sheet{})
if tid := c.Query("template_id"); tid != "" {
q = q.Where("template_id = ?", tid)
}
q.Find(&sheets)
c.JSON(200, gin.H{"sheets": sheets})
}
func getSheet(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var s Sheet
if db.First(&s, "id = ?", id).Error != nil {
c.JSON(200, gin.H{"sheet": nil})
return
}
c.JSON(200, gin.H{"sheet": s})
}
func createSheet(c *gin.Context, db *gorm.DB) {
var input struct {
Name string `json:"name"`
Cells json.RawMessage `json:"cells"`
TemplateID uuid.UUID `json:"template_id"`
}
if c.BindJSON(&input) != nil {
return
}
s := Sheet{Name: input.Name, Cells: JSON(input.Cells), TemplateID: input.TemplateID}
db.Create(&s)
c.JSON(200, gin.H{"id": s.ID})
}
func patchSheet(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var s Sheet
if db.First(&s, "id = ?", id).Error != nil {
c.JSON(404, gin.H{"detail": "Sheet not found"})
return
}
var input struct {
Name *string `json:"name"`
Cells *json.RawMessage `json:"cells"`
}
if c.BindJSON(&input) != nil {
return
}
updates := map[string]interface{}{}
if input.Name != nil {
updates["name"] = *input.Name
}
if input.Cells != nil {
updates["cells"] = JSON(*input.Cells)
}
if len(updates) > 0 {
db.Model(&s).Updates(updates)
}
c.JSON(200, gin.H{"id": s.ID})
}
// --- Attributes ---
func getAttributes(c *gin.Context, db *gorm.DB) {
var attrs []Attribute
db.Find(&attrs)
c.JSON(200, gin.H{"attributes": attrs})
}
func createAttribute(c *gin.Context, db *gorm.DB) {
var input struct {
Name string `json:"name"`
IsRequired bool `json:"is_required"`
}
if c.BindJSON(&input) != nil {
return
}
var existing Attribute
if db.Where("name = ?", input.Name).First(&existing).Error == nil {
c.JSON(400, gin.H{"detail": fmt.Sprintf("Атрибут с именем '%s' уже существует", input.Name)})
return
}
a := Attribute{Name: input.Name, IsRequired: input.IsRequired}
db.Create(&a)
c.JSON(200, gin.H{"id": a.ID})
}
func getAttributeValues(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var attr Attribute
if db.First(&attr, "id = ?", id).Error != nil {
c.JSON(404, gin.H{"detail": "Attribute not found"})
return
}
type valCount struct {
Value string `json:"value"`
UsageCount int `json:"usage_count"`
}
var values []valCount
db.Model(&TemplateAttribute{}).
Select("value, count(template_id) as usage_count").
Where("attribute_id = ? AND value IS NOT NULL", id).
Group("value").
Order("usage_count DESC").
Find(&values)
c.JSON(200, gin.H{
"attribute_id": attr.ID,
"attribute_name": attr.Name,
"values": values,
"total_count": len(values),
})
}
// --- Standards ---
func getStandards(c *gin.Context, db *gorm.DB) {
var standards []Standard
db.Find(&standards)
c.JSON(200, gin.H{"standards": standards})
}
func getStandard(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var s Standard
if db.First(&s, "id = ?", id).Error != nil {
c.JSON(200, gin.H{"standard": nil})
return
}
c.JSON(200, gin.H{"standard": s})
}
func createStandard(c *gin.Context, db *gorm.DB) {
var input struct {
Name string `json:"name"`
ProtocolName string `json:"protocol_name"`
RegistryNumber string `json:"registry_number"`
Range string `json:"range"`
Accuracy string `json:"accuracy"`
ValidUntil string `json:"valid_until"`
}
if c.BindJSON(&input) != nil {
return
}
validUntil, _ := time.Parse("2006-01-02", input.ValidUntil)
s := Standard{
Name: input.Name, ProtocolName: input.ProtocolName, RegistryNumber: input.RegistryNumber,
Range: input.Range, Accuracy: input.Accuracy, ValidUntil: validUntil,
}
db.Create(&s)
c.JSON(200, gin.H{"id": s.ID})
}
func patchStandard(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var s Standard
if db.First(&s, "id = ?", id).Error != nil {
c.JSON(404, gin.H{"detail": "Standard not found"})
return
}
var raw map[string]interface{}
if c.BindJSON(&raw) != nil {
return
}
db.Model(&s).Updates(raw)
c.JSON(200, gin.H{"id": s.ID})
}
func deleteStandard(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
res := db.Delete(&Standard{}, "id = ?", id)
if res.RowsAffected == 0 {
c.JSON(404, gin.H{"detail": "Standard not found"})
return
}
c.JSON(200, gin.H{"message": "Standard deleted successfully"})
}
// --- Laboratories ---
func getLaboratories(c *gin.Context, db *gorm.DB) {
var labs []Laboratory
db.Preload("ConditionTypes").Find(&labs)
c.JSON(200, gin.H{"laboratories": labs})
}
func getLaboratory(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var lab Laboratory
if db.Preload("ConditionTypes").First(&lab, "id = ?", id).Error != nil {
c.JSON(200, gin.H{"laboratory": nil})
return
}
c.JSON(200, gin.H{"laboratory": lab})
}
// --- Daily Conditions ---
func getDailyConditions(c *gin.Context, db *gorm.DB) {
q := db.Model(&DailyCondition{})
if lid := c.Query("laboratory_id"); lid != "" {
q = q.Where("laboratory_id = ?", lid)
}
if sd := c.Query("start_date"); sd != "" {
q = q.Where("measurement_date >= ?", sd)
}
if ed := c.Query("end_date"); ed != "" {
q = q.Where("measurement_date <= ?", ed)
}
var dcs []DailyCondition
q.Find(&dcs)
c.JSON(200, gin.H{"daily_conditions": dcs})
}
func getDailyCondition(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var dc DailyCondition
if db.First(&dc, "id = ?", id).Error != nil {
c.JSON(200, gin.H{"daily_condition": nil})
return
}
c.JSON(200, gin.H{"daily_condition": dc})
}
func createDailyCondition(c *gin.Context, db *gorm.DB) {
var input struct {
LaboratoryID uuid.UUID `json:"laboratory_id"`
MeasurementDate string `json:"measurement_date"`
Conditions json.RawMessage `json:"conditions"`
}
if c.BindJSON(&input) != nil {
return
}
var lab Laboratory
if db.First(&lab, "id = ?", input.LaboratoryID).Error != nil {
c.JSON(404, gin.H{"detail": "Laboratory not found"})
return
}
md, _ := time.Parse("2006-01-02", input.MeasurementDate)
dc := DailyCondition{LaboratoryID: input.LaboratoryID, MeasurementDate: md, Conditions: JSON(input.Conditions)}
db.Create(&dc)
c.JSON(200, gin.H{"id": dc.ID})
}
func updateDailyCondition(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var dc DailyCondition
if db.First(&dc, "id = ?", id).Error != nil {
c.JSON(404, gin.H{"detail": "Daily condition not found"})
return
}
var raw map[string]interface{}
if c.BindJSON(&raw) != nil {
return
}
db.Model(&dc).Updates(raw)
c.JSON(200, gin.H{"id": dc.ID})
}
func deleteDailyCondition(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
res := db.Delete(&DailyCondition{}, "id = ?", id)
if res.RowsAffected == 0 {
c.JSON(404, gin.H{"detail": "Daily condition not found"})
return
}
c.JSON(200, gin.H{"message": "Daily condition deleted successfully"})
}
// --- Groups ---
func getGroups(c *gin.Context, db *gorm.DB) {
var groups []Group
db.Order("\"order\", name").Find(&groups)
c.JSON(200, gin.H{"groups": groups})
}
func getGroup(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var g Group
if db.First(&g, "id = ?", id).Error != nil {
c.JSON(200, gin.H{"group": nil})
return
}
c.JSON(200, gin.H{"group": g})
}
func createGroup(c *gin.Context, db *gorm.DB) {
var input struct {
Name string `json:"name"`
Order float64 `json:"order"`
IsVisible *bool `json:"is_visible"`
}
if c.BindJSON(&input) != nil {
return
}
isVisible := true
if input.IsVisible != nil {
isVisible = *input.IsVisible
}
g := Group{Name: input.Name, Order: input.Order, IsVisible: isVisible}
db.Create(&g)
c.JSON(200, gin.H{"id": g.ID})
}
func patchGroup(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
var g Group
if db.First(&g, "id = ?", id).Error != nil {
c.JSON(404, gin.H{"detail": "Group not found"})
return
}
var raw map[string]interface{}
if c.BindJSON(&raw) != nil {
return
}
db.Model(&g).Updates(raw)
db.First(&g, "id = ?", id) // reload
c.JSON(200, g)
}
func deleteGroup(c *gin.Context, db *gorm.DB) {
id, ok := parseUUID(c, "id")
if !ok {
return
}
res := db.Delete(&Group{}, "id = ?", id)
c.JSON(200, gin.H{"success": res.RowsAffected > 0})
}
// --- Protocols ---
func createProtocol(c *gin.Context, db *gorm.DB) {
var input struct {
TemplateID uuid.UUID `json:"template_id"`
FileID uuid.UUID `json:"file_id"`
CellsToUpdate map[string]interface{} `json:"cells_to_update"`
PrintArea string `json:"print_area"`
}
if c.BindJSON(&input) != nil {
return
}
var file File
if db.First(&file, "id = ?", input.FileID).Error != nil {
c.JSON(404, gin.H{"detail": "File not found"})
return
}
modified, err := FillCells(input.CellsToUpdate, file.Content, input.PrintArea)
if err != nil {
c.JSON(500, gin.H{"detail": fmt.Sprintf("Excel error: %v", err)})
return
}
protocolID := uuid.New()
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=protocol_%s.xlsx", protocolID))
c.Data(200, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", modified)
}