diff --git a/backend/api/api.gen.go b/backend/api/api.gen.go index 90b892c..d0e6b77 100644 --- a/backend/api/api.gen.go +++ b/backend/api/api.gen.go @@ -59,6 +59,35 @@ type LineUser struct { UserId string `json:"user_id"` } +// MemberCreate defines model for MemberCreate. +type MemberCreate struct { + Accounts struct { + Discord bool `json:"discord"` + Github bool `json:"github"` + Line bool `json:"line"` + } `json:"accounts"` + Avatar *string `json:"avatar,omitempty"` + + // Bio Markdown形式の自己紹介 + Bio string `json:"bio"` + Department string `json:"department"` + Links *[]struct { + Title string `json:"title"` + Url string `json:"url"` + } `json:"links,omitempty"` + Name string `json:"name"` + Nickname string `json:"nickname"` + Roles []string `json:"roles"` + Year string `json:"year"` +} + +// MemberCreateResponse defines model for MemberCreateResponse. +type MemberCreateResponse struct { + // Id 登録されたメンバーのID + Id string `json:"id"` + Message string `json:"message"` +} + // MemberDetail defines model for MemberDetail. type MemberDetail struct { Accounts struct { @@ -122,6 +151,9 @@ type GetApiLineOauthParams struct { State *string `form:"state,omitempty" json:"state,omitempty"` } +// PostApiMembersJSONRequestBody defines body for PostApiMembers for application/json ContentType. +type PostApiMembersJSONRequestBody = MemberCreate + // PutApiProfileBasicInfoJSONRequestBody defines body for PutApiProfileBasicInfo for application/json ContentType. type PutApiProfileBasicInfoJSONRequestBody = BasicInfo @@ -133,6 +165,9 @@ type ServerInterface interface { // メンバー一覧を取得する // (GET /api/members) GetApiMembers(c *gin.Context) + // メンバーを登録する + // (POST /api/members) + PostApiMembers(c *gin.Context) // メンバー詳細を取得する // (GET /api/members/{id}) GetApiMembersId(c *gin.Context, id string) @@ -207,6 +242,19 @@ func (siw *ServerInterfaceWrapper) GetApiMembers(c *gin.Context) { siw.Handler.GetApiMembers(c) } +// PostApiMembers operation middleware +func (siw *ServerInterfaceWrapper) PostApiMembers(c *gin.Context) { + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.PostApiMembers(c) +} + // GetApiMembersId operation middleware func (siw *ServerInterfaceWrapper) GetApiMembersId(c *gin.Context) { @@ -286,6 +334,7 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options router.GET(options.BaseURL+"/api/line-oauth", wrapper.GetApiLineOauth) router.GET(options.BaseURL+"/api/members", wrapper.GetApiMembers) + router.POST(options.BaseURL+"/api/members", wrapper.PostApiMembers) router.GET(options.BaseURL+"/api/members/:id", wrapper.GetApiMembersId) router.GET(options.BaseURL+"/api/profile/basic-info", wrapper.GetApiProfileBasicInfo) router.PUT(options.BaseURL+"/api/profile/basic-info", wrapper.PutApiProfileBasicInfo) diff --git a/backend/main.go b/backend/main.go index c3b2681..0fd58e3 100644 --- a/backend/main.go +++ b/backend/main.go @@ -14,6 +14,7 @@ import ( "github.com/Lumos-Programming/profile-system-backend/api" "github.com/Lumos-Programming/profile-system-backend/pkg/config" "github.com/Lumos-Programming/profile-system-backend/pkg/handler" + "github.com/Lumos-Programming/profile-system-backend/pkg/service" "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "google.golang.org/api/option" @@ -65,7 +66,8 @@ func main() { } func setupAPIServer(client *firestore.Client, cfg *config.Config) *gin.Engine { - h := handler.NewHandler(client, cfg.LINE) + membersSvc := service.NewMembersService(client) + h := handler.NewHandler(client, cfg.LINE, membersSvc) router := gin.Default() router.Use(func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "http://localhost:3000") diff --git a/backend/pkg/handler/handler.go b/backend/pkg/handler/handler.go index 0496d11..59efd93 100644 --- a/backend/pkg/handler/handler.go +++ b/backend/pkg/handler/handler.go @@ -12,6 +12,7 @@ import ( "cloud.google.com/go/firestore" "github.com/Lumos-Programming/profile-system-backend/api" "github.com/Lumos-Programming/profile-system-backend/pkg/config" + "github.com/Lumos-Programming/profile-system-backend/pkg/service" "github.com/gin-gonic/gin" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -26,15 +27,17 @@ type Handler struct { fs *firestore.Client httpClient *http.Client lineCfg config.LINE + membersSvc *service.MembersService } -func NewHandler(f *firestore.Client, lineCfg config.LINE) *Handler { +func NewHandler(f *firestore.Client, lineCfg config.LINE, membersSvc *service.MembersService) *Handler { return &Handler{ fs: f, httpClient: &http.Client{ Timeout: 10 * time.Second, }, - lineCfg: lineCfg, + lineCfg: lineCfg, + membersSvc: membersSvc, } } diff --git a/backend/pkg/handler/members.go b/backend/pkg/handler/members.go index 551924e..a323e92 100644 --- a/backend/pkg/handler/members.go +++ b/backend/pkg/handler/members.go @@ -5,6 +5,7 @@ import ( "net/http" api "github.com/Lumos-Programming/profile-system-backend/api" + "github.com/Lumos-Programming/profile-system-backend/pkg/service" "github.com/gin-gonic/gin" "google.golang.org/api/iterator" "google.golang.org/grpc/codes" @@ -171,6 +172,35 @@ func (h *Handler) GetApiMembersId(c *gin.Context, id string) { // if v == nil { // return []string{} // } + +// PostApiMembers は新しいメンバーを登録する。 +func (h *Handler) PostApiMembers(c *gin.Context) { + ctx := c.Request.Context() + + // --- ① リクエストボディをパース --- + var req api.MemberCreate + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // --- ② api.MemberCreate を service.Member に変換 --- + member := service.MemberFromAPICreate(req) + + // --- ③ Service層に登録を依頼 --- + id, err := h.membersSvc.Register(ctx, member) + if err != nil { + slog.Error("failed to register member", "error", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // --- ④ 成功レスポンス --- + c.JSON(http.StatusCreated, api.MemberCreateResponse{ + Id: id, + Message: "メンバーを登録しました", + }) +} // if a, ok := v.([]any); ok { // out := make([]string, 0, len(a)) // for _, x := range a { diff --git a/backend/pkg/service/members.go b/backend/pkg/service/members.go new file mode 100644 index 0000000..1ee563c --- /dev/null +++ b/backend/pkg/service/members.go @@ -0,0 +1,29 @@ +package service + +import ( + "context" + + "cloud.google.com/go/firestore" +) + +// MembersService は Firestore の "members" コレクションに対する操作を提供する。 +type MembersService struct { + fs *firestore.Client +} + +// NewMembersService は MembersService を生成する。 +func NewMembersService(fs *firestore.Client) *MembersService { + return &MembersService{fs: fs} +} + +// Register は Member を Firestore の "members" コレクションに登録する。 +// 戻り値はドキュメントIDとエラー。 +func (s *MembersService) Register(ctx context.Context, m Member) (string, error) { + doc := s.fs.Collection("members").NewDoc() + m.Id = doc.ID // Firestore のドキュメント ID を id フィールドにも保持 + _, err := doc.Set(ctx, m) + if err != nil { + return "", err + } + return doc.ID, nil +} diff --git a/backend/pkg/service/model.go b/backend/pkg/service/model.go new file mode 100644 index 0000000..5798a5b --- /dev/null +++ b/backend/pkg/service/model.go @@ -0,0 +1,88 @@ +package service + +import api "github.com/Lumos-Programming/profile-system-backend/api" + +// Member は Firestore に保存するメンバー情報の構造体。 +// firestore タグで Firestore フィールド名を明示する。 +type Member struct { + Accounts struct { + Discord bool `firestore:"discord"` + Github bool `firestore:"github"` + Line bool `firestore:"line"` + } `firestore:"accounts"` + Avatar *string `firestore:"avatar,omitempty"` + Bio string `firestore:"bio"` + Department string `firestore:"department"` + Id string `firestore:"id"` + Links []struct { + Title string `firestore:"title"` + Url string `firestore:"url"` + } `firestore:"links"` + Name string `firestore:"name"` + Nickname string `firestore:"nickname"` + Roles []string `firestore:"roles"` + Year string `firestore:"year"` +} + +// ToSummary は Member を API レスポンス用の MemberSummary に変換する。 +func (m *Member) ToSummary() api.MemberSummary { + return api.MemberSummary{ + Id: m.Id, + Name: m.Name, + Nickname: m.Nickname, + Roles: m.Roles, + Avatar: m.Avatar, + } +} + +// ToDetail は Member を API レスポンス用の MemberDetail に変換する。 +func (m *Member) ToDetail() api.MemberDetail { + detail := api.MemberDetail{ + Id: m.Id, + Name: m.Name, + Nickname: m.Nickname, + Department: m.Department, + Year: m.Year, + Bio: m.Bio, + Avatar: m.Avatar, + Roles: m.Roles, + } + detail.Accounts.Discord = m.Accounts.Discord + detail.Accounts.Github = m.Accounts.Github + detail.Accounts.Line = m.Accounts.Line + + for _, l := range m.Links { + detail.Links = append(detail.Links, struct { + Title string `json:"title"` + Url string `json:"url"` + }{Title: l.Title, Url: l.Url}) + } + + return detail +} + +// MemberFromAPICreate は api.MemberCreate を Member に変換する。 +func MemberFromAPICreate(req api.MemberCreate) Member { + m := Member{ + Name: req.Name, + Nickname: req.Nickname, + Department: req.Department, + Year: req.Year, + Bio: req.Bio, + Roles: req.Roles, + Avatar: req.Avatar, + } + m.Accounts.Discord = req.Accounts.Discord + m.Accounts.Github = req.Accounts.Github + m.Accounts.Line = req.Accounts.Line + + if req.Links != nil { + for _, l := range *req.Links { + m.Links = append(m.Links, struct { + Title string `firestore:"title"` + Url string `firestore:"url"` + }{Title: l.Title, Url: l.Url}) + } + } + return m +}