Skip to content
Snippets Groups Projects
Forked from whats-this / api
23 commits behind the upstream repository.
createuser.go 2.97 KiB
package routes

import (
	"encoding/json"
	"net/http"
	"regexp"
	"strings"

	"owo.codes/whats-this/api/lib/apierrors"
	"owo.codes/whats-this/api/lib/db"
	"owo.codes/whats-this/api/lib/middleware"

	"github.com/go-chi/render"
	"github.com/gofrs/uuid"
	"github.com/rs/zerolog/log"
)

// Username regex.
var usernameRegex = regexp.MustCompile(`(?i)^[a-z0-9][a-z0-9_-]+[a-z0-9]$`)

// Email regex. Taken from http://www.golangprograms.com/regular-expression-to-validate-email-address.html
var emailRegex = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")

// createUserBody is the request body for a create user request.
type createUserBody struct {
	Email    string `json:"email"`
	Username string `json:"username"`
}

// createUserResponse is the response body for a successful create user request.
type createUserResponse struct {
	Success    bool   `json:"success"`
	StatusCode *int   `json:"errorcode"`
	UserID     string `json:"id"`
	UserToken  string `json:"token"`
}

// CreateUser handles user creation requests.
func CreateUser(w http.ResponseWriter, r *http.Request) {
	// Only authorized admin users can use this route
	user := middleware.GetAuthorizedUser(r)
	if user.ID == "" || user.IsBlocked || !user.IsAdmin {
		panic(apierrors.Unauthorized)
	}

	// Parse request body
	decoder := json.NewDecoder(r.Body)
	var body createUserBody
	err := decoder.Decode(&body)
	if err != nil {
		panic(apierrors.InvalidJSONPayload)
	}

	// Validate request body
	if !emailRegex.MatchString(body.Email) && !usernameRegex.MatchString(body.Username) {
		panic(apierrors.InvalidEmailOrUsername)
	}

	// Check if user exists
	body.Email = strings.TrimSpace(body.Email)
	body.Username = strings.TrimSpace(body.Username)
	exists, err := db.CheckUserExistsByUsernameOrEmail(body.Username, body.Email)
	if err != nil {
		log.Error().Err(err).Msg("failed to check if user exists")
		panic(apierrors.InternalServerError)
	}
	if exists {
		panic(apierrors.UserExists)
	}
	// Insert user
	userID, err := uuid.NewV4()
	if err != nil {
		log.Error().Err(err).Msg("failed to generate new user UUID")
		panic(apierrors.InternalServerError)
	}
	userIDStr := userID.String()
	err = db.InsertUser(userIDStr, body.Username, body.Email)
	if err != nil {
		log.Error().Err(err).Msg("failed to insert user into database")
		panic(apierrors.InternalServerError)
	}

	// Insert token
	token, err := uuid.NewV4()
	if err != nil {
		log.Error().Err(err).Msg("failed to generate new token UUID")
		panic(apierrors.InternalServerError)
	}
	tokenStr := token.String()
	err = db.InsertToken(userIDStr, tokenStr)
	if err != nil {
		log.Error().Err(err).Msg("failed to insert token into database")
		panic(apierrors.InternalServerError)
	}

	// Return response
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusOK)
	render.JSON(w, r, createUserResponse{
		Success:    true,
		StatusCode: nil,
		UserID:     userIDStr,
		UserToken:  tokenStr,
	})
}