package v1 import ( "context" "database/sql" "errors" "github.com/golang-jwt/jwt/v5" echojwt "github.com/labstack/echo-jwt/v4" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" swagger "github.com/swaggo/echo-swagger" _ "git.jamestombleson.com/jtom38/newsbot-api/docs" "git.jamestombleson.com/jtom38/newsbot-api/internal/services" ) type Handler struct { Router *echo.Echo //Db *database.Queries config services.Configs repo services.RepositoryService } const ( ErrParameterIdMissing = "The requested parameter ID was not found." ErrParameterMissing = "The requested parameter was not found found:" ErrUnableToParseId = "Unable to parse the requested ID" ErrRecordMissing = "The requested record was not found" ErrFailedToCreateRecord = "The record was not created due to a database problem" ErrFailedToUpdateRecord = "The requested record was not updated due to a database problem" ErrUserUnknown = "User is unknown" ErrYouDontOwnTheRecord = "The record requested does not belong to you" ResponseMessageSuccess = "Success" ) var ( ErrIdValueMissing string = "id value is missing" ErrValueNotUuid string = "a value given was expected to be a uuid but was not correct." ErrNoRecordFound string = "no record was found." ErrUnableToConvertToJson string = "Unable to convert to json" ) func NewServer(ctx context.Context, configs services.Configs, conn *sql.DB) *Handler { s := &Handler{ config: configs, repo: services.NewRepositoryService(conn), } jwtConfig := echojwt.Config{ NewClaimsFunc: func(c echo.Context) jwt.Claims { return new(JwtToken) }, SigningKey: []byte(configs.JwtSecret), } router := echo.New() router.Pre(middleware.RemoveTrailingSlash()) router.Pre(middleware.Logger()) router.Pre(middleware.Recover()) router.GET("/swagger/*", swagger.WrapHandler) v1 := router.Group("/api/v1") articles := v1.Group("/articles") articles.Use(echojwt.WithConfig(jwtConfig)) articles.GET("", s.listArticles) articles.GET(":id", s.getArticle) articles.GET(":id/details", s.getArticleDetails) articles.GET("by/source/:id", s.ListArticlesBySourceId) //dwh := v1.Group("/discord/webhooks") //dwh.GET("/", s.ListDiscordWebHooks) //dwh.POST("/new", s.NewDiscordWebHook) //dwh.GET("/by/serverAndChannel", s.GetDiscordWebHooksByServerAndChannel) //dwh.GET("/:ID", s.GetDiscordWebHooksById) //dwh.DELETE("/:ID", s.deleteDiscordWebHook) //dwh.POST("/:ID/disable", s.disableDiscordWebHook) //dwh.POST("/:ID/enable", s.enableDiscordWebHook) //queue := v1.Group("/queue") //queue.GET("/discord/webhooks", s.ListDiscordWebhookQueue) // TODO this needs to be reworked //settings := v1.Group("/settings") //settings.GET("/", s.getSettings) sources := v1.Group("/sources") sources.Use(echojwt.WithConfig(jwtConfig)) sources.GET("", s.listSources) sources.GET("/by/source", s.listSourcesBySource) sources.GET("/by/sourceAndName", s.GetSourceBySourceAndName) //sources.POST("/new/reddit", s.newRedditSource) //sources.POST("/new/youtube", s.newYoutubeSource) //sources.POST("/new/twitch", s.newTwitchSource) sources.POST("/new/rss", s.newRssSource) sources.GET("/:id", s.getSource) sources.DELETE("/:id", s.deleteSources) sources.POST("/:id/disable", s.disableSource) sources.POST("/:id/enable", s.enableSource) users := v1.Group("/users") users.POST("/login", s.AuthLogin) users.POST("/register", s.AuthRegister) users.Use(echojwt.WithConfig(jwtConfig)) users.POST("/scopes/add", s.AddScopes) users.POST("/scopes/remove", s.RemoveScopes) users.POST("/refresh/token", s.RefreshJwtToken) users.POST("/refresh/sessionToken", s.NewSessionToken) s.Router = router return s } //type ApiStatusModel struct { // StatusCode int `json:"status"` // Message string `json:"message"` //} //type ApiError struct { // *ApiStatusModel //} //func (s *Handler) WriteError(c echo.Context, errMessage error, HttpStatusCode int) error { // return c.JSON(HttpStatusCode, domain.BaseResponse{ // Message: errMessage.Error(), // IsError: true, // }) //} //func (s *Handler) WriteMessage(c echo.Context, msg string, HttpStatusCode int) error { // return c.JSON(HttpStatusCode, domain.BaseResponse{ // Message: msg, // }) //} //func (s *Handler) InternalServerErrorResponse(c echo.Context, msg string) error { // return c.JSON(http.StatusInternalServerError, domain.BaseResponse{ // Message: msg, // }) //} //func (s *Handler) UnauthorizedResponse(c echo.Context, msg string) error { // return c.JSON(http.StatusUnauthorized, domain.BaseResponse{ // Message: msg, // }) //} // If the token is not valid then an json error will be returned. // If the token has the wrong scope, a json error will be returned. // If the token passes all the checks, it is valid and is returned back to the caller. func (s *Handler) ValidateJwtToken(c echo.Context, requiredScope string) (JwtToken, error) { token, err := s.getJwtTokenFromContext(c) if err != nil { return JwtToken{}, errors.New(ErrJwtMissing) } err = token.hasExpired() if err != nil { return JwtToken{}, errors.New(ErrJwtExpired) } err = token.hasScope(requiredScope) if err != nil { return JwtToken{}, errors.New(ErrJwtScopeMissing) } if token.Iss != s.config.ServerAddress { return JwtToken{}, errors.New(ErrJwtInvalidIssuer) } // If you are the built in admin account, skip the username and session token check if token.UserName == "admin" { return token, nil } user, err := s.repo.Users.GetUser(c.Request().Context(), token.UserName) if err != nil { return JwtToken{}, errors.New("user record not found") } if user.SessionToken != token.SessionToken { return JwtToken{}, errors.New("invalid session token") } return token, nil } func (s *Handler) GetUserIdFromJwtToken(c echo.Context) int64 { token, err := s.getJwtTokenFromContext(c) if err != nil { return -1 } return token.GetUserId() }