feat: user_rework (todo -> GetAll)
This commit is contained in:
+35
-17
@@ -48,9 +48,10 @@ type config struct {
|
||||
}
|
||||
|
||||
type mailConfig struct {
|
||||
exp time.Duration
|
||||
fromEmail string
|
||||
sendGrid sendGridConfig
|
||||
inviteExp time.Duration
|
||||
passwordRefreshExp time.Duration
|
||||
fromEmail string
|
||||
sendGrid sendGridConfig
|
||||
}
|
||||
|
||||
type authConfig struct {
|
||||
@@ -64,10 +65,11 @@ type basicConfig struct {
|
||||
}
|
||||
|
||||
type tokenConfig struct {
|
||||
secret string
|
||||
exp time.Duration
|
||||
iss string
|
||||
aud string
|
||||
secret string
|
||||
exp time.Duration
|
||||
refreshExp time.Duration
|
||||
iss string
|
||||
aud string
|
||||
}
|
||||
|
||||
type redisConfig struct {
|
||||
@@ -89,6 +91,8 @@ type dbConfig struct {
|
||||
maxIdleTime time.Duration
|
||||
}
|
||||
|
||||
// This `mount` function is responsible for setting up the routing for your API
|
||||
// using the Chi router. Here's a breakdown of what it does:
|
||||
func (api *api) mount() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
// *CORS
|
||||
@@ -135,29 +139,37 @@ func (api *api) mount() http.Handler {
|
||||
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Put("/activate/{token}", api.activateUserHandler)
|
||||
//r.Put("/reset-password/{token}", api.resetPasswordHandler)
|
||||
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
r.Route("/me", func(r chi.Router) {
|
||||
r.Use(api.AuthTokenMiddleware)
|
||||
|
||||
r.Get("/", api.getUserHandler)
|
||||
r.Patch("/", api.checkProfileOwnership(api.updateUserHandler))
|
||||
r.Patch("/password", api.checkProfileOwnership(api.updatePasswordHandler))
|
||||
r.Put(("/follow"), api.followUserHandler)
|
||||
r.Put(("/unfollow"), api.unfollowUserHandler)
|
||||
})
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(api.AuthTokenMiddleware)
|
||||
r.Get("/feed", api.getUserFeedHandler)
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/authentication", func(r chi.Router) {
|
||||
r.Post("/user", api.registerUserHandler)
|
||||
r.Post("/register", api.registerUserHandler)
|
||||
r.Post("/token", api.createTokenHandler)
|
||||
|
||||
r.Post("/forgot-password", api.forgotPasswordHandler)
|
||||
r.Post("/refresh", api.refreshTokenHandler)
|
||||
})
|
||||
})
|
||||
|
||||
r.Route("/admin", func(r chi.Router) {
|
||||
r.Use(api.AuthTokenMiddleware)
|
||||
|
||||
r.Route("/users", func(r chi.Router) {
|
||||
r.Get("/", api.protectAdminRoutes("moderator", api.getUsersHandler))
|
||||
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
r.Get("/", api.protectAdminRoutes("moderator", api.getUserByIdHandler))
|
||||
})
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -204,6 +216,12 @@ func (api *api) run(mux http.Handler) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if userStore, ok := api.store.Users.(*store.UserStore); ok {
|
||||
if err := userStore.Close(); err != nil {
|
||||
api.logger.Errorw("error closing userstore", err)
|
||||
}
|
||||
}
|
||||
|
||||
api.logger.Infow("server has been shutdown")
|
||||
|
||||
return nil
|
||||
|
||||
+88
-1
@@ -27,6 +27,15 @@ type UserWithToken struct {
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
type ForgotPasswordPayload struct {
|
||||
Email string `json:"email" validate:"required,email,max=255"`
|
||||
}
|
||||
|
||||
type AuthResponse struct {
|
||||
Token string `json:"token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
// RegisterUser godoc
|
||||
//
|
||||
// @Summary Register a new User
|
||||
@@ -75,7 +84,7 @@ func (api *api) registerUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
hashedToken := hex.EncodeToString(hash[:])
|
||||
|
||||
// Store the user
|
||||
err := api.store.Users.CreateAndInvite(ctx, user, hashedToken, api.config.mail.exp)
|
||||
err := api.store.Users.CreateAndInvite(ctx, user, hashedToken, api.config.mail.inviteExp)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, store.ErrDuplicateEmail):
|
||||
@@ -201,3 +210,81 @@ func (api *api) createTokenHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ResetPasswordHandler godoc
|
||||
//
|
||||
// @Summary Forgot Password
|
||||
// @Description Forgot Password
|
||||
// @Tags authentication
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param payload body ForgotPasswordPayload true "User Credentials"
|
||||
// @Success 201 {string} string "Token Created"
|
||||
// @Failure 400 {object} error
|
||||
// @Failure 401 {object} error
|
||||
// @Failure 500 {object} error
|
||||
// @Router /authentication/forgot-password [post]
|
||||
func (api *api) forgotPasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var payload ForgotPasswordPayload
|
||||
if err := readJSON(w, r, &payload); err != nil {
|
||||
api.badRequestError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := Validate.Struct(payload); err != nil {
|
||||
api.badRequestError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := api.store.Users.GetByEmail(r.Context(), payload.Email)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case store.ErrNotFound:
|
||||
api.badRequestError(w, r, err)
|
||||
default:
|
||||
api.internalServerError(w, r, err)
|
||||
}
|
||||
}
|
||||
|
||||
plainToken := uuid.New().String()
|
||||
|
||||
hash := sha256.Sum256([]byte(plainToken))
|
||||
hashedToken := hex.EncodeToString(hash[:])
|
||||
|
||||
//*Store The Request
|
||||
err = api.store.Users.PasswordResetRequest(r.Context(), user.ID, hashedToken, api.config.mail.passwordRefreshExp)
|
||||
if err != nil {
|
||||
api.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
updateURL := fmt.Sprintf("%s/reset-password/%s", api.config.frontEndURL, plainToken)
|
||||
|
||||
isProdEnv := api.config.env == "production"
|
||||
vars := struct {
|
||||
Username string
|
||||
ServiceName string
|
||||
UpdateURL string
|
||||
Email string
|
||||
CurrentYear string
|
||||
}{
|
||||
Username: user.Username,
|
||||
ServiceName: "LK_API_Temp",
|
||||
UpdateURL: updateURL,
|
||||
Email: user.Email,
|
||||
CurrentYear: time.Now().Format("2006"),
|
||||
}
|
||||
|
||||
status, err := api.mailer.Send(mailer.ForgotPasswordTemplate, user.Username, user.Email, vars, !isProdEnv)
|
||||
if err != nil {
|
||||
api.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.logger.Infow("email sent", "status code", status)
|
||||
|
||||
if err := api.jsonResponse(w, http.StatusCreated, plainToken); err != nil {
|
||||
api.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/FernandoVideira/LK_API_Temp/internal/store"
|
||||
@@ -48,7 +47,6 @@ func (api *api) getUserFeedHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx := r.Context()
|
||||
user := getUserFromContext(r)
|
||||
log.Println(user)
|
||||
feed, err := api.store.Posts.GetUserFeed(ctx, user.ID, fq)
|
||||
if err != nil {
|
||||
api.internalServerError(w, r, err)
|
||||
|
||||
+7
-3
@@ -56,8 +56,9 @@ func main() {
|
||||
},
|
||||
env: env.GetString("ENV", "development"),
|
||||
mail: mailConfig{
|
||||
exp: time.Hour * 24 * 3,
|
||||
fromEmail: env.GetString("FROM_EMAIL", ""),
|
||||
inviteExp: time.Hour * 24 * 3,
|
||||
passwordRefreshExp: time.Hour * 24 * 7,
|
||||
fromEmail: env.GetString("FROM_EMAIL", ""),
|
||||
sendGrid: sendGridConfig{
|
||||
apiKey: env.GetString("SENDGRID_API_KEY", ""),
|
||||
},
|
||||
@@ -111,7 +112,10 @@ func main() {
|
||||
cfg.rateLimiter.TimeFrame,
|
||||
)
|
||||
|
||||
store := store.NewStorage(db)
|
||||
store, err := store.NewStorage(db)
|
||||
if err != nil {
|
||||
logger.Fatal(err)
|
||||
}
|
||||
cacheStorage := cache.NewRedisStorage(redisDb)
|
||||
mailer := mailer.NewSendGrid(cfg.mail.sendGrid.apiKey, cfg.mail.fromEmail)
|
||||
|
||||
|
||||
+26
-1
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -104,17 +105,20 @@ func (api *api) postsContextMiddleware(next http.Handler) http.Handler {
|
||||
api.badRequestError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
post, err := api.store.Posts.GetByID(ctx, id)
|
||||
log.Println("post", post)
|
||||
if err != nil {
|
||||
switch {
|
||||
case errors.Is(err, store.ErrNotFound):
|
||||
api.notFoundError(w, r, err)
|
||||
return
|
||||
default:
|
||||
api.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, postCtx, post)
|
||||
@@ -161,6 +165,27 @@ func (api *api) checkProfileOwnership(next http.HandlerFunc) http.HandlerFunc {
|
||||
})
|
||||
}
|
||||
|
||||
func (api *api) protectAdminRoutes(requiredRole string, next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
user := getUserFromContext(r)
|
||||
allowed, err := api.checkRolePrecedence(r.Context(), user, requiredRole)
|
||||
if err != nil {
|
||||
api.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
api.forbiddenError(w, r, fmt.Errorf("forbidden"))
|
||||
return
|
||||
}
|
||||
|
||||
ctx := r.Context()
|
||||
|
||||
ctx = context.WithValue(ctx, roleContextKey, user.Role)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func (api *api) checkRolePrecedence(ctx context.Context, user *store.User, roleName string) (bool, error) {
|
||||
role, err := api.store.Roles.GetByName(ctx, roleName)
|
||||
if err != nil {
|
||||
|
||||
+39
-3
@@ -3,7 +3,6 @@ package main
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -12,8 +11,39 @@ import (
|
||||
)
|
||||
|
||||
type userKey string
|
||||
type roleKey string
|
||||
|
||||
const userContextKey userKey = "user"
|
||||
const roleContextKey roleKey = "role"
|
||||
|
||||
// GetUser godoc
|
||||
//
|
||||
// @Summary Fetches all users
|
||||
// @Description Fetches all users
|
||||
// @Tags admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {object} store.User
|
||||
// @Failure 400 {object} error
|
||||
// @Failure 404 {object} error
|
||||
// @Failure 500 {object} error
|
||||
// @Security ApiKeyAuth
|
||||
// @Router /admin/users [get]
|
||||
func (api *api) getUsersHandler(w http.ResponseWriter, r *http.Request) {
|
||||
role := getRoleFromContext(r)
|
||||
isAdmin := false
|
||||
if role.Level == 3 {
|
||||
isAdmin = true
|
||||
}
|
||||
|
||||
users, err := api.store.Users.GetAll(r.Context(), isAdmin)
|
||||
if err != nil {
|
||||
api.internalServerError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
api.jsonResponse(w, http.StatusOK, users)
|
||||
}
|
||||
|
||||
// GetUser godoc
|
||||
//
|
||||
@@ -78,7 +108,6 @@ type updateUserPayload struct {
|
||||
// @Router /users/{id} [patch]
|
||||
func (api *api) updateUserHandler(w http.ResponseWriter, r *http.Request) {
|
||||
user := getUserFromContext(r)
|
||||
log.Println(user)
|
||||
|
||||
var payload updateUserPayload
|
||||
if err := readJSON(w, r, &payload); err != nil {
|
||||
@@ -103,7 +132,9 @@ func (api *api) updateUser(ctx context.Context, user *store.User) error {
|
||||
return err
|
||||
}
|
||||
|
||||
api.cacheStore.Users.Delete(ctx, user.ID)
|
||||
if api.config.redisCfg.enabled {
|
||||
api.cacheStore.Users.Delete(ctx, user.ID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -300,3 +331,8 @@ func getUserFromContext(r *http.Request) *store.User {
|
||||
user, _ := r.Context().Value(userContextKey).(*store.User)
|
||||
return user
|
||||
}
|
||||
|
||||
func getRoleFromContext(r *http.Request) *store.Role {
|
||||
role, _ := r.Context().Value(roleContextKey).(*store.Role)
|
||||
return role
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user