@@ -41,9 +41,31 @@ type ComplexInput struct {
4141 Category string `json:"category" validate:"required,min=1,max=100"`
4242}
4343
44- // type Goal struct { ... }
44+ // Goal represents the goal entity.
45+ type Goal struct {
46+ ID uint `gorm:"primarykey" json:"id"`
47+ UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
48+ ComplexID uint `json:"complex_id" gorm:"not null;index"` // Foreign key to Complex
49+ SurfaceGoal string `json:"surface_goal" gorm:"not null"`
50+ UnderlyingGoal string `json:"underlying_goal" gorm:"not null"`
51+ CreatedAt time.Time `json:"created_at"`
52+ UpdatedAt time.Time `json:"updated_at"`
53+ Complex Complex `gorm:"foreignKey:ComplexID"` // Belongs to Complex
54+ }
55+
56+ // GoalInput defines the expected input for creating or updating a goal.
57+ type GoalInput struct {
58+ ComplexID uint `json:"complex_id" validate:"required"`
59+ SurfaceGoal string `json:"surface_goal" validate:"required,min=1,max=255"`
60+ UnderlyingGoal string `json:"underlying_goal" validate:"required,min=1,max=255"`
61+ }
62+
4563// type Action struct { ... }
4664
65+ // --- GORM AutoMigrate Helper ---
66+ // (This can be moved to a more appropriate place like a db setup function later)
67+ var modelsToMigrate = []interface {}{& Complex {}, & Goal {}} // Add &Action{} later
68+
4769// ErrorResponse Helper
4870type ErrorResponse struct {
4971 Code int `json:"code"`
@@ -212,12 +234,144 @@ func DeleteComplexHandler(c *gin.Context) {
212234 c .Status (http .StatusNoContent )
213235}
214236
215- // Goal Handlers (スタブ)
216- // func CreateGoalHandler(c *gin.Context) { /* ... */ }
217- // func GetGoalsHandler(c *gin.Context) { /* ... */ }
218- // func GetGoalHandler(c *gin.Context) { /* ... */ }
219- // func UpdateGoalHandler(c *gin.Context) { /* ... */ }
220- // func DeleteGoalHandler(c *gin.Context) { /* ... */ }
237+ // CreateGoalHandler handles the creation of a new goal.
238+ func CreateGoalHandler (c * gin.Context ) {
239+ userID , exists := c .Get ("userID" )
240+ if ! exists {
241+ c .JSON (http .StatusUnauthorized , NewErrorResponse (http .StatusUnauthorized , "User ID not found in context" ))
242+ return
243+ }
244+
245+ var input GoalInput
246+ if err := c .ShouldBindJSON (& input ); err != nil {
247+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Invalid request body: " + err .Error ()))
248+ return
249+ }
250+
251+ if err := validate .Struct (input ); err != nil {
252+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Validation failed: " + err .Error ()))
253+ return
254+ }
255+
256+ // Check if the referenced complex exists and belongs to the user
257+ var complex Complex
258+ if err := db .Where ("id = ? AND user_id = ?" , input .ComplexID , userID .(string )).First (& complex ).Error ; err != nil {
259+ if err == gorm .ErrRecordNotFound {
260+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Referenced complex not found or does not belong to user" ))
261+ return
262+ }
263+ c .JSON (http .StatusInternalServerError , NewErrorResponse (http .StatusInternalServerError , "Error checking complex: " + err .Error ()))
264+ return
265+ }
266+
267+ goal := Goal {
268+ UserID : userID .(string ),
269+ ComplexID : input .ComplexID ,
270+ SurfaceGoal : input .SurfaceGoal ,
271+ UnderlyingGoal : input .UnderlyingGoal ,
272+ }
273+
274+ if result := db .Create (& goal ); result .Error != nil {
275+ c .JSON (http .StatusInternalServerError , NewErrorResponse (http .StatusInternalServerError , "Failed to create goal: " + result .Error .Error ()))
276+ return
277+ }
278+
279+ c .JSON (http .StatusCreated , goal )
280+ }
281+
282+ // GetGoalsHandler handles fetching all goals for the authenticated user.
283+ func GetGoalsHandler (c * gin.Context ) {
284+ userID , exists := c .Get ("userID" )
285+ if ! exists {
286+ c .JSON (http .StatusUnauthorized , NewErrorResponse (http .StatusUnauthorized , "User ID not found in context" ))
287+ return
288+ }
289+
290+ var goals []Goal
291+ if result := db .Where ("user_id = ?" , userID .(string )).Find (& goals ); result .Error != nil {
292+ c .JSON (http .StatusInternalServerError , NewErrorResponse (http .StatusInternalServerError , "Failed to fetch goals: " + result .Error .Error ()))
293+ return
294+ }
295+
296+ if goals == nil {
297+ goals = []Goal {}
298+ }
299+ c .JSON (http .StatusOK , goals )
300+ }
301+
302+ // GetGoalHandler handles fetching a specific goal by its ID.
303+ func GetGoalHandler (c * gin.Context ) {
304+ userID , exists := c .Get ("userID" )
305+ if ! exists {
306+ c .JSON (http .StatusUnauthorized , NewErrorResponse (http .StatusUnauthorized , "User ID not found in context" ))
307+ return
308+ }
309+ goalID := c .Param ("goalId" )
310+
311+ var goal Goal
312+ if err := db .Where ("id = ? AND user_id = ?" , goalID , userID .(string )).First (& goal ).Error ; err != nil {
313+ if err == gorm .ErrRecordNotFound {
314+ c .JSON (http .StatusNotFound , NewErrorResponse (http .StatusNotFound , "Goal not found" ))
315+ return
316+ }
317+ c .JSON (http .StatusInternalServerError , NewErrorResponse (http .StatusInternalServerError , "Failed to fetch goal: " + err .Error ()))
318+ return
319+ }
320+ c .JSON (http .StatusOK , goal )
321+ }
322+
323+ // UpdateGoalHandler handles updating an existing goal.
324+ func UpdateGoalHandler (c * gin.Context ) {
325+ userID , exists := c .Get ("userID" )
326+ if ! exists {
327+ c .JSON (http .StatusUnauthorized , NewErrorResponse (http .StatusUnauthorized , "User ID not found in context" ))
328+ return
329+ }
330+ goalID := c .Param ("goalId" )
331+
332+ var input GoalInput
333+ if err := c .ShouldBindJSON (& input ); err != nil {
334+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Invalid request body: " + err .Error ()))
335+ return
336+ }
337+ if err := validate .Struct (input ); err != nil {
338+ c .JSON (http .StatusBadRequest , NewErrorResponse (http .StatusBadRequest , "Validation failed: " + err .Error ()))
339+ return
340+ }
341+
342+ var goal Goal
343+ if err := db .Where ("id = ? AND user_id = ?" , goalID , userID .(string )).First (& goal ).Error ; err != nil {
344+ c .JSON (http .StatusNotFound , NewErrorResponse (http .StatusNotFound , "Goal not found to update" ))
345+ return
346+ }
347+
348+ // Note: ComplexID update is not allowed here for simplicity, but can be added if needed.
349+ // If ComplexID is updated, ensure the new ComplexID also belongs to the user.
350+ goal .SurfaceGoal = input .SurfaceGoal
351+ goal .UnderlyingGoal = input .UnderlyingGoal
352+
353+ if err := db .Save (& goal ).Error ; err != nil {
354+ c .JSON (http .StatusInternalServerError , NewErrorResponse (http .StatusInternalServerError , "Failed to update goal: " + err .Error ()))
355+ return
356+ }
357+ c .JSON (http .StatusOK , goal )
358+ }
359+
360+ // DeleteGoalHandler handles deleting a goal by its ID.
361+ func DeleteGoalHandler (c * gin.Context ) {
362+ userID , exists := c .Get ("userID" )
363+ if ! exists {
364+ c .JSON (http .StatusUnauthorized , NewErrorResponse (http .StatusUnauthorized , "User ID not found in context" ))
365+ return
366+ }
367+ goalID := c .Param ("goalId" )
368+
369+ if result := db .Where ("id = ? AND user_id = ?" , goalID , userID .(string )).Delete (& Goal {}); result .Error != nil || result .RowsAffected == 0 {
370+ c .JSON (http .StatusNotFound , NewErrorResponse (http .StatusNotFound , "Goal not found or already deleted" ))
371+ return
372+ }
373+ c .Status (http .StatusNoContent )
374+ }
221375
222376// Action Handlers (スタブ)
223377// func CreateActionHandler(c *gin.Context) { /* ... */ }
@@ -299,8 +453,8 @@ func main() {
299453 } else {
300454 log .Println ("ℹ️ MIGRATION_PATH not set, skipping automated migrations. Ensure DB schema is up to date." )
301455 // 開発初期段階ではGORMのAutoMigrateも便利です
302- // log.Println("Running GORM AutoMigrate for initial schema setup...")
303- // db.AutoMigrate(&Complex{}, &Goal{}, &Action{} ) // ここにモデルを追加
456+ log .Println ("Running GORM AutoMigrate for initial schema setup..." )
457+ db .AutoMigrate (modelsToMigrate ... ) // Use the slice for AutoMigrate
304458 }
305459
306460 // --- Ginルーターの初期化 ---
@@ -331,14 +485,14 @@ func main() {
331485 }
332486
333487 // Goals
334- // goalsGroup := apiV1.Group("/goals")
335- // {
336- // // goalsGroup.POST("", CreateGoalHandler)
337- // // goalsGroup.GET("", GetGoalsHandler)
338- // // goalsGroup.GET("/:goalId", GetGoalHandler)
339- // // goalsGroup.PUT("/:goalId", UpdateGoalHandler)
340- // // goalsGroup.DELETE("/:goalId", DeleteGoalHandler)
341- // }
488+ goalsGroup := apiV1 .Group ("/goals" )
489+ {
490+ goalsGroup .POST ("" , CreateGoalHandler )
491+ goalsGroup .GET ("" , GetGoalsHandler )
492+ goalsGroup .GET ("/:goalId" , GetGoalHandler )
493+ goalsGroup .PUT ("/:goalId" , UpdateGoalHandler )
494+ goalsGroup .DELETE ("/:goalId" , DeleteGoalHandler )
495+ }
342496
343497 // Actions
344498 // actionsGroup := apiV1.Group("/actions")
0 commit comments