@@ -60,11 +60,28 @@ type GoalInput struct {
6060 UnderlyingGoal string `json:"underlying_goal" validate:"required,min=1,max=255"`
6161}
6262
63- // type Action struct { ... }
63+ // Action represents the action entity.
64+ type Action struct {
65+ ID uint `gorm:"primarykey" json:"id"`
66+ UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
67+ GoalID uint `json:"goal_id" gorm:"not null;index"` // Foreign key to Goal
68+ Content string `json:"content" gorm:"not null"`
69+ CompletedAt * time.Time `json:"completed_at,omitempty"` // Pointer to allow null
70+ CreatedAt time.Time `json:"created_at"`
71+ UpdatedAt time.Time `json:"updated_at"`
72+ Goal Goal `gorm:"foreignKey:GoalID"` // Belongs to Goal
73+ }
74+
75+ // ActionInput defines the expected input for creating a new action.
76+ type ActionInput struct {
77+ GoalID uint `json:"goal_id" validate:"required"`
78+ Content string `json:"content" validate:"required,min=1,max=1000"`
79+ CompletedAt string `json:"completed_at,omitempty" validate:"omitempty,datetime=2006-01-02T15:04:05Z07:00"` // ISO8601 format
80+ }
6481
6582// --- GORM AutoMigrate Helper ---
6683// (This can be moved to a more appropriate place like a db setup function later)
67- var modelsToMigrate = []interface {}{& Complex {}, & Goal {}} // Add &Action{} later
84+ var modelsToMigrate = []interface {}{& Complex {}, & Goal {}, & Action {}}
6885
6986// ErrorResponse Helper
7087type ErrorResponse struct {
@@ -373,8 +390,59 @@ func DeleteGoalHandler(c *gin.Context) {
373390 c .Status (http .StatusNoContent )
374391}
375392
376- // Action Handlers (スタブ)
377- // func CreateActionHandler(c *gin.Context) { /* ... */ }
393+ // CreateActionHandler handles the creation of a new action.
394+ func CreateActionHandler (c * gin.Context ) {
395+ userID , exists := c .Get ("userID" )
396+ if ! exists {
397+ c .JSON (http .StatusUnauthorized , NewErrorResponse (http .StatusUnauthorized , "User ID not found in context" ))
398+ return
399+ }
400+
401+ var input ActionInput
402+ if err := c .ShouldBindJSON (& input ); err != nil {
403+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Invalid request body: " + err .Error ()))
404+ return
405+ }
406+
407+ if err := validate .Struct (input ); err != nil {
408+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Validation failed: " + err .Error ()))
409+ return
410+ }
411+
412+ // Check if the referenced goal exists and belongs to the user
413+ var goal Goal
414+ if err := db .Where ("id = ? AND user_id = ?" , input .GoalID , userID .(string )).First (& goal ).Error ; err != nil {
415+ if err == gorm .ErrRecordNotFound {
416+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Referenced goal not found or does not belong to user" ))
417+ return
418+ }
419+ c .JSON (http .StatusInternalServerError , NewErrorResponse (http .StatusInternalServerError , "Error checking goal: " + err .Error ()))
420+ return
421+ }
422+
423+ var completedAtParsed * time.Time
424+ if input .CompletedAt != "" {
425+ t , err := time .Parse (time .RFC3339 , input .CompletedAt )
426+ if err != nil {
427+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Invalid completed_at format. Use ISO8601 (RFC3339)." ))
428+ return
429+ }
430+ completedAtParsed = & t
431+ }
432+
433+ action := Action {
434+ UserID : userID .(string ),
435+ GoalID : input .GoalID ,
436+ Content : input .Content ,
437+ CompletedAt : completedAtParsed ,
438+ }
439+
440+ if result := db .Create (& action ); result .Error != nil {
441+ c .JSON (http .StatusInternalServerError , NewErrorResponse (http .StatusInternalServerError , "Failed to create action: " + result .Error .Error ()))
442+ return
443+ }
444+ c .JSON (http .StatusCreated , action )
445+ }
378446
379447func main () {
380448 // --- 環境変数からの設定読み込み (例) ---
@@ -495,10 +563,10 @@ func main() {
495563 }
496564
497565 // Actions
498- // actionsGroup := apiV1.Group("/actions")
499- // {
500- // // actionsGroup.POST("", CreateActionHandler)
501- // }
566+ actionsGroup := apiV1 .Group ("/actions" )
567+ {
568+ actionsGroup .POST ("" , CreateActionHandler )
569+ }
502570 }
503571
504572 // --- サーバー起動 ---
0 commit comments