Line data Source code
1 : // Package router はアプリケーションのHTTPルーティングを定義します。
2 : package router
3 :
4 : import (
5 : "log/slog"
6 : "net/http"
7 :
8 : "github.com/gin-gonic/gin"
9 :
10 : fba "resume/internal/adapter/gateway/firebase"
11 : "resume/internal/adapter/http/controller"
12 : "resume/internal/adapter/http/middleware"
13 : "resume/internal/domain/repository"
14 : uci18n "resume/internal/usecase/i18n"
15 : )
16 :
17 : // New はアプリケーションのルーティングを初期化し、*gin.Engine を返します。
18 : func New(
19 : fb fba.Auth,
20 : appLog *slog.Logger,
21 : ucI18n uci18n.Usecase,
22 : userRepo repository.UserRepository,
23 : hSample *controller.SampleHandler,
24 : hTest *controller.TestHandler,
25 : hUser *controller.UserHandler,
26 : hAuth *controller.AuthHandler,
27 : hProfile *controller.ProfileHandler,
28 : hMeta *controller.MetaHandler,
29 : hI18n *controller.I18nHandler,
30 : hEducation *controller.EducationHandler,
31 : hSkill *controller.SkillHandler,
32 : hContact *controller.ContactHandler,
33 : hLicense *controller.LicenseHandler,
34 1 : ) *gin.Engine {
35 1 : r := gin.New()
36 1 : r.RedirectTrailingSlash = false // 末尾スラッシュ補正をやめる
37 1 : r.RedirectFixedPath = false // 大文字小文字などの補正もやめる
38 1 : r.Use(middleware.CamelSnakeCodec())
39 1 : r.Use(gin.Recovery())
40 1 : r.Use(middleware.Cors(middleware.DefaultCORSConfig()))
41 1 : r.Use(middleware.InjectAuth(fb))
42 1 :
43 1 : r.Use(middleware.RequestLogger(appLog, &middleware.RequestLoggerOptions{
44 2 : Skipper: func(c *gin.Context) bool { return c.Request.URL.Path == "/health" },
45 : IncludeHeaders: []string{"X-Request-ID", "X-Trace-Id"},
46 : }))
47 :
48 : // region 全ルーター対策
49 : // 404が返らなかった対策
50 1 : r.NoRoute(func(c *gin.Context) {
51 0 : c.JSON(http.StatusNotFound, gin.H{
52 0 : "error": "not found",
53 0 : })
54 0 : })
55 : // メソッド違いに対応出来てなかった対策
56 1 : r.HandleMethodNotAllowed = true
57 1 : r.NoMethod(func(c *gin.Context) {
58 0 : c.JSON(http.StatusMethodNotAllowed, gin.H{
59 0 : "error": "method not allowed",
60 0 : })
61 0 : })
62 : // cors対策 任意:OPTIONS を一括ハンドル(プリフライトを確実に 204 で返す)
63 : //r.OPTIONS("/*path", func(c *gin.Context) {
64 : // c.Status(204)
65 : //})
66 : // endregion 全ルーター対策
67 :
68 : // region i18n
69 :
70 : // (b) ロケール交渉ミドルウェア(?lang, cookie=lang, Accept-Language)
71 1 : src := NewUsecaseLocaleSource(ucI18n)
72 1 : r.Use(middleware.Middleware(middleware.Opts{
73 1 : Default: "ja",
74 1 : Allow: nil, // 増やしたらここに追記
75 1 : Query: "lang",
76 1 : Header: "Accept-Language",
77 1 : Cookie: "", // Cookieを使わないなら空
78 1 : SetHeader: true,
79 1 : Store: src,
80 1 : }))
81 1 :
82 1 : i18nG := r.Group("/i18n")
83 2 : {
84 1 : // (c) バンドル配信用ハンドラ(ETag/304 対応)
85 1 : i18nG.GET("/bundle", hI18n.GetBundle)
86 1 : i18nG.POST("/reload", hI18n.Reload)
87 1 : i18nG.GET("/list", hI18n.ListLocales)
88 1 : }
89 :
90 : // endregion i18n
91 :
92 : // region テスト
93 : // ヘルスチェック
94 2 : r.GET("/health", func(c *gin.Context) { c.String(http.StatusOK, "ok") })
95 :
96 : // テスト
97 1 : r.GET("ping", hSample.Ping)
98 1 : testG := r.Group("test")
99 1 : testG.Use(middleware.RequireAuth(fb))
100 2 : {
101 1 : testG.GET("1", hTest.Ping)
102 1 : }
103 : // endregion テスト
104 :
105 : // region 認証
106 1 : authG := r.Group("/auth")
107 2 : {
108 1 : authG.POST("/fl/login", hAuth.UserLogin)
109 1 : }
110 : // endregion 認証
111 :
112 : // region firebase認可
113 1 : resolveUserMw := middleware.NewResolveUser(userRepo)
114 1 : // フリーランサー情報
115 1 : freelanceG := r.Group("/fl")
116 1 : freelanceG.Use(middleware.RequireAuth(fb))
117 1 : freelanceG.Use(resolveUserMw.RequireResolveUser())
118 2 : {
119 1 : // ログインしている自身の情報
120 1 : profileG := freelanceG.Group("/profile")
121 1 : profileG.GET("/", hProfile.Me)
122 1 : profileG.GET("/fba", hProfile.Fba)
123 1 : profileG.GET("/identities", hProfile.Identities)
124 1 :
125 1 : // 履歴書掲載情報の氏名など
126 1 : personalG := freelanceG.Group("/personal")
127 1 : personalG.GET("/", hProfile.GetPersonalInfo)
128 1 : personalG.PATCH("/", hProfile.PatchPersonalInfo)
129 1 : personalG.GET("/exists", hProfile.HasPersonalInfo)
130 1 : //PATCH /profile/account # メールやパスワードなどアカウント設定(任意)
131 1 :
132 1 : addressG := freelanceG.Group("/address")
133 1 : addressG.GET("/exists", hProfile.HasUserAddress)
134 1 : addressG.GET("/", hProfile.ListUserAddress)
135 1 : addressG.GET("/:address_id", hProfile.DetailUserAddress)
136 1 : addressG.POST("/", hProfile.CreateUserAddress)
137 1 : addressG.PATCH("/:address_id", hProfile.UpdateUserAddress)
138 1 : addressG.DELETE("/:address_id", hProfile.DeleteUserAddress)
139 1 :
140 1 : // 学歴関連情報
141 1 : educationG := freelanceG.Group("/education")
142 1 : educationG.GET("/", hEducation.ListEducation)
143 1 : educationG.GET("/exists", hEducation.ExistsEducation)
144 1 : educationG.POST("/", hEducation.AddEducation)
145 1 : educationG.PATCH("/:education_id", hEducation.UpdateEducation)
146 1 : educationG.DELETE("/:education_id", hEducation.DeleteEducation)
147 1 : educationG.PATCH("/order", hEducation.OrderEducation)
148 1 :
149 1 : // スキル関連
150 1 : skillG := freelanceG.Group("/skill")
151 1 : skillG.GET("/", hSkill.ListSkill)
152 1 : skillG.GET("/exists", hSkill.ExistsSkill)
153 1 : skillG.POST("/suggest", hSkill.SuggestSkill)
154 1 : skillG.POST("/", hSkill.AddSkill)
155 1 : skillG.PATCH("/:skill_id", hSkill.EditSkill)
156 1 : skillG.DELETE("/:skill_id", hSkill.DeleteSkill)
157 1 : skillG.PATCH("/order", hSkill.OrderSkill)
158 1 :
159 1 : // 連絡先関連
160 1 : contactG := freelanceG.Group("/contact")
161 1 : contactG.GET("/", hContact.ListContact)
162 1 : contactG.GET("/exists", hContact.ExistsContact)
163 1 : contactG.POST("/", hContact.AddContact)
164 1 : contactG.PATCH("/:contact_id", hContact.EditContact)
165 1 : contactG.DELETE("/:contact_id", hContact.DeleteContact)
166 1 : contactG.PATCH("/reorder", hContact.ReorderContact)
167 1 :
168 1 : // 資格・免許関連
169 1 : licenseG := freelanceG.Group("/license")
170 1 : licenseG.GET("/", hLicense.ListLicense)
171 1 : licenseG.GET("/exists", hLicense.ExistsLicense)
172 1 : licenseG.POST("/", hLicense.AddLicense)
173 1 : licenseG.GET("/:license_id", hLicense.DetailLicense)
174 1 : licenseG.PATCH("/:license_id", hLicense.EditLicense)
175 1 : licenseG.DELETE("/:license_id", hLicense.DeleteLicense)
176 1 : licenseG.PATCH("/reorder", hLicense.ReorderLicense)
177 1 :
178 1 : }
179 : // endregion firebase認可
180 :
181 : // region agent、corporate、adminが使うユーザー情報
182 1 : userGroup := r.Group("/users")
183 2 : {
184 1 : userGroup.GET("/:id", hUser.GetByID)
185 1 : }
186 : // endregion agent、corporate、adminが使うユーザー情報
187 :
188 : // region 認証とかロールとか無関係に誰でも使うやつを置く
189 1 : optionG := r.Group("/option")
190 2 : {
191 1 : optionAddressG := optionG.Group("/address")
192 2 : {
193 1 : // 目的
194 1 : optionAddressG.GET("/purpose", hMeta.ListAddressPurpose)
195 1 : // 国コード
196 1 : optionAddressG.GET("/countries", hMeta.ListCountry)
197 1 : }
198 : // 性別
199 1 : optionG.GET("/gender", hMeta.ListGender)
200 1 :
201 1 : optionEducationG := optionG.Group("/education")
202 2 : {
203 1 : // 学歴状態
204 1 : optionEducationG.GET("/status", hMeta.ListEducationStatus)
205 1 : // 学位種別
206 1 : optionEducationG.GET("/degree", hMeta.ListDegreeType)
207 1 : }
208 :
209 : // スキルに関するオプション取得
210 1 : optionSkillG := optionG.Group("/skill")
211 2 : {
212 1 : // スキルカテゴリ一覧
213 1 : optionSkillG.GET("/category", hMeta.ListSkillCategory)
214 1 : // スキルレベル一覧
215 1 : optionSkillG.GET("/level", hMeta.ListSkillLevel)
216 1 : }
217 :
218 : // 職域に関するオプション値取得
219 1 : occupationG := optionG.Group("/occupation")
220 2 : {
221 1 : occupationG.GET("/category", hMeta.ListOccupation)
222 1 : }
223 :
224 : // 経歴に関するオプション値取得
225 1 : experienceG := optionG.Group("/experience")
226 2 : {
227 1 : experienceG.GET("/", hMeta.ListExperience)
228 1 : }
229 :
230 : // 続柄に関するオプション値取得
231 1 : relationshipG := optionG.Group("/relationship")
232 2 : {
233 1 : relationshipG.GET("/", hMeta.ListRelationshipType)
234 1 : }
235 : }
236 : // endregion 認証とかロールとか無関係に誰でも使うやつを置く
237 :
238 1 : return r
239 : }
|