1018 lines
26 KiB
Go
1018 lines
26 KiB
Go
package controller
|
||
|
||
import (
|
||
"bytes"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"jr-shikoku-backend-system/appinfo"
|
||
"jr-shikoku-backend-system/model"
|
||
types "jr-shikoku-backend-system/type"
|
||
"net/http"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
type Handler struct {
|
||
DB *gorm.DB
|
||
}
|
||
|
||
// sendDiscordWebhook はDiscordにWebhookを送信する
|
||
func sendDiscordWebhook(content string) error {
|
||
webhookURL := os.Getenv("DISCORD_WEBHOOK_URL")
|
||
if webhookURL == "" {
|
||
return fmt.Errorf("DISCORD_WEBHOOK_URL is not set")
|
||
}
|
||
|
||
payload := map[string]string{"content": content}
|
||
jsonData, err := json.Marshal(payload)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
resp, err := http.Post(webhookURL, "application/json", bytes.NewBuffer(jsonData))
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
return nil
|
||
}
|
||
|
||
func (handler Handler) Pong(c *gin.Context) {
|
||
|
||
name := c.Query("name") // ?name=値 で取得
|
||
|
||
c.JSON(200, gin.H{"message": "pong pong" + name})
|
||
|
||
}
|
||
|
||
func (handler Handler) GetVersion(c *gin.Context) {
|
||
c.JSON(http.StatusOK, appinfo.CurrentVersion())
|
||
}
|
||
|
||
func (handler Handler) Hello(c *gin.Context) {
|
||
|
||
series_id := c.Query("series_id") // ?series_id=値 で取得
|
||
base := handler.DB
|
||
if series_id == "" {
|
||
if test, err := model.GetTrainSeries(base, nil); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
} else {
|
||
c.JSON(200, gin.H{"data": test})
|
||
}
|
||
} else {
|
||
if test, err := model.GetTrainSeriesByID(base, series_id); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
} else {
|
||
c.JSON(200, gin.H{"data": test})
|
||
}
|
||
}
|
||
}
|
||
func (handler Handler) GetTrainSeries(c *gin.Context) {
|
||
|
||
series_id := c.Query("series_id") // ?series_id=値 で取得
|
||
dateStr := c.Query("date") // ?date=値 で取得(例: "yesterday", "2026-02-07")
|
||
base := handler.DB
|
||
|
||
// dateパラメータの処理
|
||
var date *string
|
||
if dateStr != "" {
|
||
if dateStr == "yesterday" {
|
||
// "yesterday"の場合はYESTERDAYマーカーを渡す
|
||
yesterday := "YESTERDAY"
|
||
date = &yesterday
|
||
} else {
|
||
// それ以外は指定された日付をそのまま使用
|
||
date = &dateStr
|
||
}
|
||
}
|
||
|
||
if series_id == "" {
|
||
if test, err := model.GetTrainSeries(base, date); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
} else {
|
||
c.JSON(200, gin.H{"data": test})
|
||
}
|
||
} else {
|
||
if test, err := model.GetTrainSeriesByID(base, series_id); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
} else {
|
||
c.JSON(200, gin.H{"data": test})
|
||
}
|
||
}
|
||
}
|
||
|
||
func normalizeHistoryDateParam(dateStr string) (string, bool) {
|
||
if dateStr == "" {
|
||
return "", false
|
||
}
|
||
|
||
if dateStr == "yesterday" {
|
||
dateStr = time.Now().AddDate(0, 0, -1).Format("2006-01-02")
|
||
}
|
||
|
||
return dateStr, true
|
||
}
|
||
|
||
func normalizeHistorySeriesIDParam(c *gin.Context) *string {
|
||
seriesID := strings.TrimSpace(c.Query("series-id"))
|
||
if seriesID == "" {
|
||
seriesID = strings.TrimSpace(c.Query("series_id"))
|
||
}
|
||
if seriesID == "" {
|
||
return nil
|
||
}
|
||
|
||
return &seriesID
|
||
}
|
||
|
||
// GetHistoryCounts は過去3週間分の1日ごとの履歴数(無効を除くユニーク運用数)を返す
|
||
// クエリパラメータ: ?date=YYYY-MM-DD または ?date=yesterday
|
||
// レスポンスは JSON で日付/件数の配列を返す
|
||
func (handler Handler) GetHistoryCounts(c *gin.Context) {
|
||
dateStr := c.Query("date")
|
||
dateStr, ok := normalizeHistoryDateParam(dateStr)
|
||
if !ok {
|
||
c.JSON(400, gin.H{"error": "date parameter is required"})
|
||
return
|
||
}
|
||
|
||
// DB 呼び出し
|
||
counts, err := model.GetPastHistoryCounts(handler.DB, dateStr)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(200, gin.H{"data": counts})
|
||
}
|
||
|
||
// GetHistoryVehicleCounts は過去3週間分の1日ごとの履歴車両数(無効を除くユニーク編成数)を返す
|
||
// クエリパラメータ: ?date=YYYY-MM-DD または ?date=yesterday、任意で ?series-id=SERIES_ID
|
||
// レスポンスは JSON で日付/件数の配列を返す
|
||
func (handler Handler) GetHistoryVehicleCounts(c *gin.Context) {
|
||
dateStr := c.Query("date")
|
||
dateStr, ok := normalizeHistoryDateParam(dateStr)
|
||
if !ok {
|
||
c.JSON(400, gin.H{"error": "date parameter is required"})
|
||
return
|
||
}
|
||
|
||
counts, err := model.GetPastHistoryVehicleCounts(handler.DB, dateStr, normalizeHistorySeriesIDParam(c))
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(200, gin.H{"data": counts})
|
||
}
|
||
func (handler Handler) PutTrainSeries(c *gin.Context) {
|
||
base := handler.DB
|
||
var input types.TrainSeries
|
||
machineID := c.Query("machine_id")
|
||
|
||
if machineID == "" {
|
||
c.JSON(403, gin.H{"error": "machine_id is required for permission check"})
|
||
return
|
||
}
|
||
|
||
isAdmin, err := model.CheckPermission(base, machineID)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !isAdmin {
|
||
c.JSON(403, gin.H{"error": "Access denied. Administrator permission required."})
|
||
return
|
||
}
|
||
|
||
// JSONボディから取得を試す
|
||
if err := c.ShouldBindJSON(&input); err == nil {
|
||
// JSONボディから正常に取得できた場合
|
||
if input.SeriesID == "" {
|
||
c.JSON(400, gin.H{"error": "series_id is required"})
|
||
return
|
||
}
|
||
} else {
|
||
// クエリパラメータから取得
|
||
series_id := c.Query("series_id")
|
||
series_name := c.Query("series_name")
|
||
description := c.Query("description")
|
||
|
||
if series_id == "" {
|
||
c.JSON(400, gin.H{"error": "series_id is required"})
|
||
return
|
||
}
|
||
|
||
input = types.TrainSeries{
|
||
SeriesID: series_id,
|
||
SeriesName: &series_name,
|
||
Description: &description,
|
||
}
|
||
}
|
||
|
||
if err := model.PutTrainSeries(base, &input); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// Discord通知
|
||
seriesName := ""
|
||
if input.SeriesName != nil {
|
||
seriesName = *input.SeriesName
|
||
}
|
||
description := ""
|
||
if input.Description != nil {
|
||
description = *input.Description
|
||
}
|
||
go sendDiscordWebhook(fmt.Sprintf("🚆 **系列情報が更新されました**\n系列ID: %s\n系列名: %s\n説明: %s", input.SeriesID, seriesName, description))
|
||
|
||
c.JSON(200, gin.H{"data": input})
|
||
}
|
||
|
||
func (handler Handler) GetUnitInfo(c *gin.Context) {
|
||
|
||
series_id := c.Query("series_id") // ?series_id=値 で取得
|
||
unit_id := c.Query("unit_id") // ?unit_id=値 で取得(カンマ区切りで複数指定可能)
|
||
base := handler.DB
|
||
|
||
var seriesIDPtr *string
|
||
if series_id != "" {
|
||
seriesIDPtr = &series_id
|
||
}
|
||
|
||
var unitIDPtr *string
|
||
if unit_id != "" {
|
||
unitIDPtr = &unit_id
|
||
}
|
||
|
||
test, err := model.GetUnitInfo(base, seriesIDPtr, unitIDPtr)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// update_by と create_by を空文字にする
|
||
for i := range test {
|
||
emptyStr := ""
|
||
test[i].UpdateBy = &emptyStr
|
||
test[i].CreateBy = &emptyStr
|
||
}
|
||
|
||
c.JSON(200, gin.H{"data": test})
|
||
}
|
||
func (handler Handler) PutUnitInfo(c *gin.Context) {
|
||
base := handler.DB
|
||
var input types.UnitInfo
|
||
|
||
// JSONボディから取得
|
||
if err := c.ShouldBindJSON(&input); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid JSON format", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 必須フィールドのバリデーション
|
||
if input.UnitID == "" {
|
||
c.JSON(400, gin.H{"error": "unit_id is required"})
|
||
return
|
||
}
|
||
if input.SeriesID == "" {
|
||
c.JSON(400, gin.H{"error": "series_id is required"})
|
||
return
|
||
}
|
||
if len(input.VehicleNumber) == 0 {
|
||
c.JSON(400, gin.H{"error": "vehicle_number is required"})
|
||
return
|
||
}
|
||
|
||
// 権限チェック: update_byのIDで権限を検証
|
||
if input.UpdateBy == nil || *input.UpdateBy == "" {
|
||
c.JSON(403, gin.H{"error": "update_by (machine_id) is required for permission check"})
|
||
return
|
||
}
|
||
|
||
isAdmin, err := model.CheckPermission(base, *input.UpdateBy)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !isAdmin {
|
||
c.JSON(403, gin.H{"error": "Access denied. Administrator permission required."})
|
||
return
|
||
}
|
||
|
||
if err := model.PutUnitInfo(base, &input); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// Discord通知
|
||
updateBy := ""
|
||
if input.UpdateBy != nil {
|
||
updateBy = *input.UpdateBy
|
||
}
|
||
go sendDiscordWebhook(fmt.Sprintf("🚃 **編成情報が更新されました**\n担当: %s\n編成番号: %s\n系列ID: %s\n車両番号: %v", updateBy, input.UnitID, input.SeriesID, input.VehicleNumber))
|
||
|
||
c.JSON(200, gin.H{"data": input})
|
||
}
|
||
|
||
func (handler Handler) GetUnitOperation(c *gin.Context) {
|
||
|
||
unit_id := c.Query("unit_id") // ?unit_id=値 で取得
|
||
trainID := c.Query("train_id") // ?train_id=値 で取得
|
||
base := handler.DB
|
||
if unit_id == "" && trainID == "" {
|
||
if test, err := model.GetUnitOperation(base); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
} else {
|
||
// update_by を空文字にする
|
||
for i := range test {
|
||
emptyStr := ""
|
||
test[i].UpdateBy = &emptyStr
|
||
}
|
||
c.JSON(200, gin.H{"data": test})
|
||
}
|
||
} else {
|
||
if test, err := model.GetUnitOperationByUnitID(base, unit_id, trainID); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
} else {
|
||
// update_by を空文字にする
|
||
for i := range test {
|
||
emptyStr := ""
|
||
test[i].UpdateBy = &emptyStr
|
||
}
|
||
c.JSON(200, gin.H{"data": test})
|
||
}
|
||
}
|
||
}
|
||
|
||
func (handler Handler) PutUnitOperation(c *gin.Context) {
|
||
base := handler.DB
|
||
var input types.UnitOperationList
|
||
|
||
// JSONボディから取得
|
||
if err := c.ShouldBindJSON(&input); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid JSON format", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 必須フィールドのバリデーション(IDは自動生成されるので不要)
|
||
if len(input.TrainIds) == 0 {
|
||
c.JSON(400, gin.H{"error": "train_ids is required"})
|
||
return
|
||
}
|
||
if input.OperationType == "" {
|
||
c.JSON(400, gin.H{"error": "operation_type is required"})
|
||
return
|
||
}
|
||
if len(input.DefaultTrainSeries) == 0 {
|
||
c.JSON(400, gin.H{"error": "default_train_series is required"})
|
||
return
|
||
}
|
||
|
||
// 空文字のtomorrow_idは未設定として扱う
|
||
if input.TomorrowID != nil && *input.TomorrowID == "" {
|
||
input.TomorrowID = nil
|
||
}
|
||
|
||
// 権限チェック: update_byのIDで権限を検証
|
||
if input.UpdateBy == nil || *input.UpdateBy == "" {
|
||
c.JSON(403, gin.H{"error": "update_by (machine_id) is required for permission check"})
|
||
return
|
||
}
|
||
|
||
isAdmin, err := model.CheckPermission(base, *input.UpdateBy)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !isAdmin {
|
||
c.JSON(403, gin.H{"error": "Access denied. Administrator permission required."})
|
||
return
|
||
}
|
||
if err := model.PutUnitOperation(base, &input); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// Discord通知
|
||
updateBy := ""
|
||
if input.UpdateBy != nil {
|
||
updateBy = *input.UpdateBy
|
||
}
|
||
go sendDiscordWebhook(fmt.Sprintf("🔄 **運用情報が更新されました**\n担当: %s\n運用ID: %s\n列車番号: %v\n運用種別: %s", updateBy, input.ID, input.TrainIds, input.OperationType))
|
||
|
||
c.JSON(200, gin.H{"data": input})
|
||
}
|
||
|
||
func (handler Handler) CreateRelatedUnitOperationWithLinks(c *gin.Context) {
|
||
base := handler.DB
|
||
var input types.CreateRelatedUnitOperationRequest
|
||
|
||
if err := c.ShouldBindJSON(&input); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid JSON format", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
hasNonEmptyString := func(values []string) bool {
|
||
for _, value := range values {
|
||
if strings.TrimSpace(value) != "" {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
if !hasNonEmptyString(input.TrainIDs) {
|
||
c.JSON(400, gin.H{"error": "train_ids is required"})
|
||
return
|
||
}
|
||
if !hasNonEmptyString(input.SourceOperationIDs) {
|
||
c.JSON(400, gin.H{"error": "source_operation_ids is required"})
|
||
return
|
||
}
|
||
if !hasNonEmptyString(input.DefaultTrainSeries) {
|
||
c.JSON(400, gin.H{"error": "default_train_series is required"})
|
||
return
|
||
}
|
||
if input.UpdateBy == nil || strings.TrimSpace(*input.UpdateBy) == "" {
|
||
c.JSON(403, gin.H{"error": "update_by (machine_id) is required for permission check"})
|
||
return
|
||
}
|
||
|
||
trimmedUpdateBy := strings.TrimSpace(*input.UpdateBy)
|
||
input.UpdateBy = &trimmedUpdateBy
|
||
if input.OperationName != nil && strings.TrimSpace(*input.OperationName) == "" {
|
||
input.OperationName = nil
|
||
}
|
||
if input.Railyard != nil {
|
||
trimmedRailyard := strings.TrimSpace(*input.Railyard)
|
||
if trimmedRailyard == "" {
|
||
input.Railyard = nil
|
||
} else {
|
||
input.Railyard = &trimmedRailyard
|
||
}
|
||
}
|
||
if input.TomorrowID != nil {
|
||
trimmedTomorrowID := strings.TrimSpace(*input.TomorrowID)
|
||
if trimmedTomorrowID == "" {
|
||
input.TomorrowID = nil
|
||
} else {
|
||
input.TomorrowID = &trimmedTomorrowID
|
||
}
|
||
}
|
||
|
||
isAdmin, err := model.CheckPermission(base, trimmedUpdateBy)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !isAdmin {
|
||
c.JSON(403, gin.H{"error": "Access denied. Administrator permission required."})
|
||
return
|
||
}
|
||
|
||
result, err := model.CreateRelatedUnitOperationWithLinks(base, &input)
|
||
if err != nil {
|
||
var missingErr *model.MissingUnitOperationsError
|
||
if errors.As(err, &missingErr) {
|
||
c.JSON(404, gin.H{"error": missingErr.Error(), "missing_source_operation_ids": missingErr.IDs})
|
||
return
|
||
}
|
||
if errors.Is(err, gorm.ErrInvalidData) {
|
||
c.JSON(400, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
go sendDiscordWebhook(fmt.Sprintf("🔗 **関連運用が作成されました**\n担当: %s\n運用ID: %s\n更新元運用: %v\n列車番号: %v", trimmedUpdateBy, result.CreatedOperation.ID, result.UpdatedSourceOperationIDs, result.CreatedOperation.TrainIds))
|
||
|
||
c.JSON(200, gin.H{"data": result})
|
||
}
|
||
|
||
func (handler Handler) GetTempOperations(c *gin.Context) {
|
||
trainID := c.Query("train_id")
|
||
dateStr := c.Query("date")
|
||
weekStr := c.Query("week")
|
||
allStr := c.Query("all")
|
||
base := handler.DB
|
||
|
||
var trainIDPtr *string
|
||
if trainID != "" {
|
||
trainIDPtr = &trainID
|
||
}
|
||
|
||
var datePtr *string
|
||
if dateStr != "" {
|
||
datePtr = &dateStr
|
||
}
|
||
|
||
var week *int
|
||
if weekStr != "" {
|
||
var parsedWeek int
|
||
if _, err := fmt.Sscanf(weekStr, "%d", &parsedWeek); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid week format"})
|
||
return
|
||
}
|
||
week = &parsedWeek
|
||
}
|
||
|
||
var all *bool
|
||
if allStr != "" {
|
||
allValue := true
|
||
all = &allValue
|
||
}
|
||
|
||
tempOps, err := model.GetTempOperations(base, trainIDPtr, datePtr, week, all)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(200, gin.H{"data": tempOps})
|
||
}
|
||
|
||
func (handler Handler) GetDiagram(c *gin.Context) {
|
||
trainID := c.Query("train_id")
|
||
priorityStr := c.Query("priority")
|
||
dateStr := c.Query("date")
|
||
base := handler.DB
|
||
|
||
var priority *float64
|
||
if priorityStr != "" {
|
||
var p float64
|
||
if _, err := fmt.Sscanf(priorityStr, "%f", &p); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid priority format"})
|
||
return
|
||
}
|
||
priority = &p
|
||
}
|
||
|
||
var date *string
|
||
if dateStr != "" {
|
||
date = &dateStr
|
||
}
|
||
|
||
diagrams, err := model.GetTrainDiagram(base, trainID, priority, date)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// updated_by を空文字にする
|
||
for i := range diagrams {
|
||
emptyStr := ""
|
||
diagrams[i].UpdatedBy = &emptyStr
|
||
}
|
||
|
||
c.JSON(200, gin.H{"data": diagrams})
|
||
}
|
||
|
||
func (handler Handler) PutDiagram(c *gin.Context) {
|
||
base := handler.DB
|
||
var input types.TrainDiagram
|
||
|
||
// JSONボディから取得
|
||
if err := c.ShouldBindJSON(&input); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid JSON format", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 必須フィールドのバリデーション
|
||
if input.TrainID == "" {
|
||
c.JSON(400, gin.H{"error": "train_id is required"})
|
||
return
|
||
}
|
||
if input.Priority == 0 {
|
||
c.JSON(400, gin.H{"error": "priority is required"})
|
||
return
|
||
}
|
||
if input.Priority == 100 {
|
||
c.JSON(403, gin.H{"error": "priority 100 is immutable and cannot be modified"})
|
||
return
|
||
}
|
||
if input.DiagramText == "" {
|
||
c.JSON(400, gin.H{"error": "diagram_text is required"})
|
||
return
|
||
}
|
||
|
||
// 権限チェック: updated_byのIDで権限を検証
|
||
if input.UpdatedBy == nil || *input.UpdatedBy == "" {
|
||
c.JSON(403, gin.H{"error": "updated_by (machine_id) is required for permission check"})
|
||
return
|
||
}
|
||
|
||
isAdmin, err := model.CheckPermission(base, *input.UpdatedBy)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !isAdmin {
|
||
c.JSON(403, gin.H{"error": "Access denied. Administrator permission required."})
|
||
return
|
||
}
|
||
|
||
if err := model.PutTrainDiagram(base, &input); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// Discord通知
|
||
updatedBy := ""
|
||
if input.UpdatedBy != nil {
|
||
updatedBy = *input.UpdatedBy
|
||
}
|
||
go sendDiscordWebhook(fmt.Sprintf("📋 **ダイヤ情報が更新されました**\n担当: %s\n列車番号: %s\n優先度: %.0f", updatedBy, input.TrainID, input.Priority))
|
||
|
||
c.JSON(200, gin.H{"data": input})
|
||
}
|
||
|
||
func (handler Handler) ArchiveExpiredDiagrams(c *gin.Context) {
|
||
base := handler.DB
|
||
|
||
// 権限チェック用のmachine_idを取得
|
||
type ArchiveRequest struct {
|
||
MachineID string `json:"machine_id" binding:"required"`
|
||
Force bool `json:"force"`
|
||
}
|
||
|
||
var req ArchiveRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(400, gin.H{"error": "machine_id is required", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 権限チェック
|
||
isDailybot, err := model.CheckPermissionDailyReset(base, req.MachineID)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !isDailybot {
|
||
c.JSON(403, gin.H{"error": "Access denied. Dailybot permission required."})
|
||
return
|
||
}
|
||
|
||
rowsAffected, err := model.ArchiveExpiredDiagrams(base)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(200, gin.H{"message": "Expired diagrams archived successfully", "rows_affected": rowsAffected})
|
||
}
|
||
|
||
func (handler Handler) GetTrainData(c *gin.Context) {
|
||
trainID := c.Query("train_id")
|
||
priorityStr := c.Query("priority")
|
||
dateStr := c.Query("date")
|
||
base := handler.DB
|
||
|
||
var priority *float64
|
||
if priorityStr != "" {
|
||
var p float64
|
||
if _, err := fmt.Sscanf(priorityStr, "%f", &p); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid priority format"})
|
||
return
|
||
}
|
||
priority = &p
|
||
}
|
||
|
||
var date *string
|
||
if dateStr != "" {
|
||
date = &dateStr
|
||
}
|
||
|
||
trainData, err := model.GetTrainData(base, trainID, priority, date)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// updated_by を空文字にする
|
||
for i := range trainData {
|
||
emptyStr := ""
|
||
trainData[i].UpdatedBy = &emptyStr
|
||
}
|
||
|
||
c.JSON(200, gin.H{"data": trainData})
|
||
}
|
||
|
||
func (handler Handler) PutTrainData(c *gin.Context) {
|
||
base := handler.DB
|
||
var input types.TrainDatum
|
||
|
||
// JSONボディから取得
|
||
if err := c.ShouldBindJSON(&input); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid JSON format", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 必須フィールドのバリデーション
|
||
if input.TrainID == "" {
|
||
c.JSON(400, gin.H{"error": "train_id is required"})
|
||
return
|
||
}
|
||
if input.Priority == 0 {
|
||
c.JSON(400, gin.H{"error": "priority is required"})
|
||
return
|
||
}
|
||
if input.Priority == 100 {
|
||
c.JSON(403, gin.H{"error": "priority 100 is immutable and cannot be modified"})
|
||
return
|
||
}
|
||
|
||
// 権限チェック: updated_byのIDで権限を検証
|
||
if input.UpdatedBy == nil || *input.UpdatedBy == "" {
|
||
c.JSON(403, gin.H{"error": "updated_by (machine_id) is required for permission check"})
|
||
return
|
||
}
|
||
|
||
isAdmin, err := model.CheckPermission(base, *input.UpdatedBy)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !isAdmin {
|
||
c.JSON(403, gin.H{"error": "Access denied. Administrator permission required."})
|
||
return
|
||
}
|
||
|
||
if err := model.PutTrainData(base, &input); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// Discord通知
|
||
updatedBy := ""
|
||
if input.UpdatedBy != nil {
|
||
updatedBy = *input.UpdatedBy
|
||
}
|
||
trainName := ""
|
||
if input.TrainName != nil {
|
||
trainName = *input.TrainName
|
||
}
|
||
toData := ""
|
||
if input.ToData != nil {
|
||
toData = *input.ToData
|
||
}
|
||
viaData := ""
|
||
if input.ViaData != nil {
|
||
viaData = *input.ViaData
|
||
}
|
||
imgURL := ""
|
||
if input.TrainInfoImg != nil {
|
||
imgURL = *input.TrainInfoImg
|
||
}
|
||
imgHubURL := ""
|
||
if input.TrainInfoImgHub != nil {
|
||
imgHubURL = *input.TrainInfoImgHub
|
||
}
|
||
uwasa := ""
|
||
if input.Uwasa != nil {
|
||
uwasa = *input.Uwasa
|
||
}
|
||
notificationMsg := fmt.Sprintf("🚄 **列車データが更新されました**\n担当: %s\n列車番号: %s\n列車名: %s\n行先: %s\n経由情報: %s\nアイコン: %s\nうわさ: %s\n優先度: %.0f", updatedBy, input.TrainID, trainName, toData, viaData, imgURL, uwasa, input.Priority)
|
||
if imgHubURL != "" {
|
||
notificationMsg += fmt.Sprintf("\n運用HUB用アイコン: %s", imgHubURL)
|
||
}
|
||
go sendDiscordWebhook(notificationMsg)
|
||
|
||
c.JSON(200, gin.H{"data": input})
|
||
}
|
||
|
||
func (handler Handler) ArchiveExpiredTrainData(c *gin.Context) {
|
||
base := handler.DB
|
||
|
||
// 権限チェック用のmachine_idを取得
|
||
type ArchiveRequest struct {
|
||
MachineID string `json:"machine_id" binding:"required"`
|
||
Force bool `json:"force"`
|
||
}
|
||
|
||
var req ArchiveRequest
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(400, gin.H{"error": "machine_id is required", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 権限チェック
|
||
isDailybot, err := model.CheckPermissionDailyReset(base, req.MachineID)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !isDailybot {
|
||
c.JSON(403, gin.H{"error": "Access denied. Dailybot permission required."})
|
||
return
|
||
}
|
||
|
||
archivedRows, reflectedRows, err := model.ArchiveExpiredTrainData(base, req.Force)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(200, gin.H{
|
||
"message": "Expired train data archived successfully",
|
||
"train_data_archived_rows": archivedRows,
|
||
"operation_log_reflected_rows": reflectedRows,
|
||
"rows_affected": reflectedRows,
|
||
})
|
||
}
|
||
|
||
func (handler Handler) GetOperationLogs(c *gin.Context) {
|
||
base := handler.DB
|
||
|
||
// クエリパラメータの取得
|
||
idStr := c.Query("id")
|
||
operationID := c.Query("operation_id")
|
||
trainID := c.Query("train_id")
|
||
unitID := c.Query("unit_id")
|
||
dateStr := c.Query("date")
|
||
weekStr := c.Query("week")
|
||
allStr := c.Query("all")
|
||
|
||
var id *int64
|
||
if idStr != "" {
|
||
var parsedID int64
|
||
if _, err := fmt.Sscanf(idStr, "%d", &parsedID); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid id format"})
|
||
return
|
||
}
|
||
id = &parsedID
|
||
}
|
||
|
||
var opID *string
|
||
if operationID != "" {
|
||
opID = &operationID
|
||
}
|
||
|
||
var tID *string
|
||
if trainID != "" {
|
||
tID = &trainID
|
||
}
|
||
|
||
var uID *string
|
||
if unitID != "" {
|
||
uID = &unitID
|
||
}
|
||
|
||
var date *string
|
||
if dateStr != "" {
|
||
date = &dateStr
|
||
}
|
||
|
||
var week *int
|
||
if weekStr != "" {
|
||
var parsedWeek int
|
||
if _, err := fmt.Sscanf(weekStr, "%d", &parsedWeek); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid week format"})
|
||
return
|
||
}
|
||
week = &parsedWeek
|
||
}
|
||
|
||
var all *bool
|
||
if allStr != "" {
|
||
allValue := true
|
||
all = &allValue
|
||
}
|
||
|
||
logs, err := model.GetOperationLogs(base, id, opID, tID, uID, date, week, all)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(200, gin.H{"data": logs})
|
||
}
|
||
|
||
func (handler Handler) PostOperationLog(c *gin.Context) {
|
||
base := handler.DB
|
||
|
||
// リクエストボディをマップで取得(machine_idの抽出が必要)
|
||
var requestData map[string]interface{}
|
||
if err := c.BindJSON(&requestData); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid JSON format", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
// machine_idを抽出
|
||
machineID, ok := requestData["machine_id"].(string)
|
||
if !ok || machineID == "" {
|
||
c.JSON(403, gin.H{"error": "machine_id is required for permission check"})
|
||
return
|
||
}
|
||
|
||
// 権限チェック: administrator または crew 権限が必須
|
||
hasPermission, err := model.CheckPermissionAdminOrCrew(base, machineID)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": "Failed to check permission", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
if !hasPermission {
|
||
c.JSON(403, gin.H{"error": "Access denied. Administrator or Crew permission required."})
|
||
return
|
||
}
|
||
|
||
// 構造体にマッピング
|
||
jsonBytes, _ := json.Marshal(requestData)
|
||
var input types.OperationLogTable
|
||
if err := json.Unmarshal(jsonBytes, &input); err != nil {
|
||
c.JSON(400, gin.H{"error": "Invalid JSON format", "details": err.Error()})
|
||
return
|
||
}
|
||
|
||
// 必須フィールドのバリデーション: OperationIDまたはTrainIdsのどちらかが必須
|
||
if (input.OperationID == nil || *input.OperationID == "") && len(input.TrainIds) == 0 {
|
||
c.JSON(400, gin.H{"error": "Either operation_id or train_ids is required"})
|
||
return
|
||
}
|
||
|
||
if err := model.PostOperationLog(base, &input); err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
// Discord通知
|
||
operationID := ""
|
||
if input.OperationID != nil {
|
||
operationID = *input.OperationID
|
||
}
|
||
imgURL := ""
|
||
if input.VehicleImg != nil {
|
||
imgURL = *input.VehicleImg
|
||
}
|
||
imgRightURL := ""
|
||
if input.VehicleImgRight != nil {
|
||
imgRightURL = *input.VehicleImgRight
|
||
}
|
||
vehicleInfoURL := ""
|
||
if input.VehicleInfoURL != nil {
|
||
vehicleInfoURL = *input.VehicleInfoURL
|
||
}
|
||
|
||
notificationMsg := fmt.Sprintf("📝 **運用ログが登録されました**\n日付: %s\n運用ID: %s\n列車番号: %v\n編成番号: %v\n車両画像: %s",
|
||
input.Date.Format("2006-01-02"), operationID, input.TrainIds, input.UnitIds, imgURL)
|
||
if imgRightURL != "" {
|
||
notificationMsg += fmt.Sprintf("\n後ろ向き車両画像: %s", imgRightURL)
|
||
}
|
||
if vehicleInfoURL != "" {
|
||
notificationMsg += fmt.Sprintf("\n車両情報URL: %s", vehicleInfoURL)
|
||
}
|
||
go sendDiscordWebhook(notificationMsg)
|
||
|
||
c.JSON(200, gin.H{"data": input})
|
||
}
|
||
|
||
func (handler Handler) CheckPermission(c *gin.Context) {
|
||
userID := c.Query("user_id") // ?user_id=値 で取得
|
||
base := handler.DB
|
||
|
||
if userID == "" {
|
||
c.JSON(400, gin.H{"error": "user_id is required"})
|
||
return
|
||
}
|
||
|
||
permissionType, err := model.GetPermissionType(base, userID)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(200, gin.H{"permission": permissionType})
|
||
}
|
||
|
||
func (handler Handler) GetDatabaseOverview(c *gin.Context) {
|
||
base := handler.DB
|
||
|
||
overview, err := model.GetDatabaseOverview(base)
|
||
if err != nil {
|
||
c.JSON(500, gin.H{"error": err.Error()})
|
||
return
|
||
}
|
||
|
||
c.JSON(200, gin.H{"data": overview})
|
||
}
|