Files

1018 lines
26 KiB
Go
Raw Permalink 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 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})
}