package app import ( "clapclap/internal/auth" "clapclap/internal/controllers" "clapclap/internal/dtos" "clapclap/internal/email" "clapclap/internal/env" "clapclap/internal/ftype" "clapclap/internal/ginctx" "clapclap/internal/upload" "time" "github.com/gin-gonic/gin" ) func (app *App) RegisterApi(group string) { api := app.Router.Group(group) // Set up the middlewares chain common for all api endpoint ginctx.UseAtomic(api, "db", &app.Database) ginctx.UseRepo(api) ginctx.UseS3(api, app.S3, app.S3Internal) ginctx.UseTimestamp(api) api.Use(auth.EnvironmentHmacSigningKey) api.Use(controllers.UpdateLastActivity) api.Use(app.JwtRefreshAuth.Optional) if env.C.ENV == "dev" { test := api.Group("/test") { test.GET("/mail", controllers.TestMail) } } status := api.Group("/status") { status.GET("/ping", controllers.Pong) } uploads := api.Group("/upload") { uploads.POST("/minio-webhook", upload.ErrorHandlingWebhook, ginctx.ShouldBindJSON[dtos.MinioUploadEvent], upload.GetEventInfos, ftype.DownloadObjFromS3AndCheck) protected := uploads.Group("/") auth.Use(protected, app.JwtAccessAuth) protected.Use(email.RequireVerification) { protected.POST("/upload-url/:type", ginctx.ShouldBindJSON[dtos.UploadFile], upload.GenerateObjectKey, upload.GetPresignedUploadUrl, controllers.GetUploadUrl) } } friendRouter := api.Group("/friends") auth.Use(friendRouter, app.JwtAccessAuth) friendRouter.Use(email.RequireVerification) { friendRouter.GET("", controllers.GetFriends) friendRouter.GET("/:id", controllers.IsFriend) friendRouter.GET("pending", controllers.GetPendingRequests) friendRouter.GET("pending/:id", controllers.GetPendingRequestsWith) friendRouter.POST("", ginctx.ShouldBindJSON[dtos.SendFriendRequest], controllers.SendFriendRequest) friendRouter.DELETE("", ginctx.ShouldBindJSON[dtos.RemoveFriendRequest], controllers.RemoveFriend) friendRouter.POST("/withdraw", ginctx.ShouldBindJSON[dtos.WithdrawFriendRequest], controllers.WithdrawFriendRequest) friendRouter.POST("/accept", ginctx.ShouldBindJSON[dtos.AcceptFriendRequest], controllers.AcceptFriendRequest) friendRouter.POST("/refuse", ginctx.ShouldBindJSON[dtos.RefuseFriendRequest], controllers.RefuseFriendRequest) } authRouter := api.Group("/auth") authRouter.Use(auth.HmacSigningMethod) { ft := authRouter.Group("/ft") { ft.GET("/log-in", app.FtAuth.Redirect("log-in")) ft.GET("/sign-up", app.FtAuth.Redirect("sign-up")) ft.GET("/callback", app.FtAuth.GetToken, app.FtAuth.Authenticate, auth.EnvironmentRefreshTokenDuration, app.JwtRefreshIssuer.Issue, func(c *gin.Context) { c.Redirect(302, env.C.CLIENT_URL) }) } authRouter.GET("/verify-email", app.EmailTokenAuth.Authenticate, controllers.VerifyEmail) authRouter.POST("/sign-up", ginctx.ShouldBindJSON[dtos.SignUpRequest], controllers.CreateUser) authRouter.POST("/log-in", ginctx.ShouldBindJSON[dtos.LoginRequest], app.PasswordAuth.Authenticate, email.RequireVerification, auth.EnvironmentRefreshTokenDuration, app.JwtRefreshIssuer.Issue) authRouter.POST("/logout", app.JwtAccessAuth.Authenticate, auth.SetTokenDuration(0*time.Minute), app.JwtRefreshRemover.Issue) authRouter.POST("/refresh", app.JwtRefreshAuth.Authenticate, auth.EnvironmentRefreshTokenDuration, app.JwtRefreshIssuer.Issue, auth.EnvironmentAccessTokenDuration, app.JwtAccessIssuer.Issue) authRouter.POST("/reset-password", ginctx.ShouldBindJSON[dtos.ResetPasswordRequest], controllers.RequestPasswordReset) authRouter.PUT("/change-password", app.PasswordResetAuth.Authenticate, ginctx.ShouldBindJSON[dtos.ChangePasswordRequest], controllers.ChangePassword) } projects := api.Group("/projects") { protected := projects.Group("") auth.Use(protected, app.JwtAccessAuth) protected.Use(email.RequireVerification) { protected.GET("/user/:userID", controllers.GetProjectsByOwner) protected.GET("/:id", controllers.GetProject) protected.POST("", ginctx.ShouldBindJSON[dtos.CreateProjectRequest], controllers.CreateProject) protected.PUT("/:id", ginctx.ShouldBindJSON[dtos.UpdateProjectRequest], controllers.UpdateProject) protected.DELETE("/:id", controllers.DeleteProject) } } users := api.Group("users") { // TODO (pipo) : are all user able to call this endpoint ? users.GET("", controllers.GetAllUsers) users.GET("/id/:id", controllers.GetUserByID) users.GET("/:username", controllers.GetUserByName) users.GET("/is-logged-in", app.JwtAccessAuth.Authenticate, func(c *gin.Context) { c.Status(200) }) // TODO (pipo): // Find better auth for this endpoint and make it harder to delete an // account. The user is not supposed to delete its account this easily protected := users.Group("") auth.Use(protected, app.JwtAccessAuth) protected.Use(email.RequireVerification) { protected.DELETE("/id/:id", controllers.DeleteUserByID) protected.DELETE("/:username", controllers.DeleteUserByName) protected.PUT("", ginctx.ShouldBindJSON[dtos.UpdateUserRequest], controllers.UpdateUser) protected.DELETE("/avatar", controllers.ResetAvatar) } } comments := api.Group("/comments") { comments.GET("/track/:id", controllers.GetCommentByTrack) comments.GET("/:id", controllers.GetCommentByID) protected := comments.Group("") auth.Use(protected, app.JwtAccessAuth) protected.Use(email.RequireVerification) { protected.POST("", ginctx.ShouldBindJSON[dtos.CreateCommentRequest], controllers.CreateComment) protected.PUT("/:id", ginctx.ShouldBindJSON[dtos.UpdateCommentRequest], controllers.UpdateComment) // ValidateTwoFactorDeadline, // ResetTwoFactorDeadline protected.DELETE("/:id", controllers.DeleteCommentByID) } } genres := api.Group("/genres") { genres.GET("", controllers.GetAllGenres) genres.GET("/:id", controllers.GetGenreByID) } tracks := api.Group("/tracks") { /* CORE */ // NOTE: QUICK EXAMPLES: // curl "http://api.localhost:8080/api/tracks?q=%234&sort_by=name&order=ASC&limit=20&page=1" // curl "http://api.localhost:8080/api/tracks?genre_id=123e4567-e89b-12d3-a456-426614174000&q=%234&sort_by=name&order=ASC&limit=20&page=1" tracks.GET("", app.JwtAccessAuth.Optional2, controllers.GetAllTracks) tracks.GET("/max-pages", app.JwtAccessAuth.Optional2, controllers.GetMaxTrackPages) tracks.GET("/:id", app.JwtAccessAuth.Optional2, controllers.GetTrackByID) /* FILTER */ tracks.GET("/genre/:genreID", controllers.GetTracksByGenre) tracks.GET("/user/:userID", controllers.GetTracksByOwner) /* WEBHOOK */ protected := tracks.Group("") auth.Use(protected, app.JwtAccessAuth) protected.Use(email.RequireVerification) { protected.POST("", upload.ErrorHandling, ginctx.ShouldBindJSON[dtos.CreateTrackRequest], ftype.GetUploadRecord, ftype.CheckFileVallidity, controllers.CreateTrack) protected.PUT("/:id", upload.ErrorHandling, ginctx.ShouldBindJSON[dtos.UpdateTrackRequest], ftype.GetUploadRecord, ftype.CheckFileVallidity, controllers.UpdateTrack) protected.DELETE("/:id", controllers.DeleteTrack) protected.POST("/finalize-upload", upload.ErrorHandling, ginctx.ShouldBindJSON[dtos.CreateTrackRequest], ftype.GetUploadRecord, ftype.CheckFileVallidity, controllers.CreateTrack) protected.POST("/url", ginctx.ShouldBindJSON[dtos.GetPrivateTrackUploadUrl], controllers.EnsureUserOwnsTrack, upload.GeneratePresignedGetUrl, ) } } ratings := api.Group("/ratings") { ratings.GET("/track/:trackID", controllers.GetRatingsByTrack) protected := ratings.Group("") auth.Use(protected, app.JwtAccessAuth) protected.Use(email.RequireVerification) { protected.POST("", ginctx.ShouldBindJSON[dtos.UpsertRatingRequest], controllers.UpsertRating) protected.DELETE("/:id", controllers.DeleteRating) } } }