mirror of
https://github.com/Floriansylvain/RenewCMS.git
synced 2026-08-19 11:43:22 +02:00
feat: password reset #32
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<h1>
|
||||
<img src="{{.Host}}/static/gocms-favicon-128.png" alt="G" style="width: 2.5rem"/>
|
||||
GoCMS
|
||||
</h1>
|
||||
<main>
|
||||
<p>You or someone tried to create reset your GoCMS account with this e-mail address. If you are not responsible for
|
||||
this, please do not validate the password reset.</p>
|
||||
<p><b>If you want to reset your password, please <a
|
||||
href="{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}" target="_blank">click HERE</a>.</b>
|
||||
</p>
|
||||
<p>If you can't click the link:</p>
|
||||
<p>{{.Host}}/register/reset/validate?c={{.VerificationCode}}&email={{.Email}}</p>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
@@ -10,10 +10,11 @@ type User struct {
|
||||
ID uint32 `gorm:"primaryKey;autoIncrement"`
|
||||
Username string `gorm:"unique;not null"`
|
||||
Password string `gorm:"not null"`
|
||||
PasswordResetCode string `gorm:"unique"`
|
||||
Email string `gorm:"unique;not null"`
|
||||
IsVerified bool `gorm:"default=false;not null"`
|
||||
VerificationCode string `gorm:"unique;not null"`
|
||||
VerificationExpiration time.Time `gorm:"not null"`
|
||||
VerificationCode string `gorm:"unique"`
|
||||
VerificationExpiration time.Time
|
||||
CreatedAt time.Time `gorm:"autoCreateTime"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime"`
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ func mapUserToDomain(user entity.User) domain.User {
|
||||
user.ID,
|
||||
user.Username,
|
||||
user.Password,
|
||||
user.PasswordResetCode,
|
||||
user.Email,
|
||||
user.IsVerified,
|
||||
user.VerificationCode,
|
||||
@@ -88,6 +89,16 @@ func (u *UserRepository) GetByUsername(username string) (domain.User, error) {
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) GetByEmail(email string) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).Where("email = ?", email).First(&localUser).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
@@ -104,4 +115,38 @@ func (u *UserRepository) UpdateVerificationStatus(userId uint32, isVerified bool
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdatePassword(userId uint32, password string) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
hashedPassword, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
localUser.Password = string(hashedPassword)
|
||||
err = u.db.Save(&localUser).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
func (u *UserRepository) UpdatePasswordResetCode(userId uint32, code string) (domain.User, error) {
|
||||
var localUser entity.User
|
||||
err := u.db.Model(&entity.User{}).First(&localUser, userId).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
hashedCode, _ := bcrypt.GenerateFromPassword([]byte(code), 12)
|
||||
localUser.PasswordResetCode = string(hashedCode)
|
||||
err = u.db.Save(&localUser).Error
|
||||
if err != nil {
|
||||
return domain.User{}, err
|
||||
}
|
||||
|
||||
return mapUserToDomain(localUser), nil
|
||||
}
|
||||
|
||||
var _ gateways.IUserRepository = &UserRepository{}
|
||||
|
||||
@@ -46,11 +46,19 @@
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<form action="register" method="get" class="mt-2 w-100">
|
||||
<button class="btn btn-link w-100" type="submit">
|
||||
Don't have any verified account? Register here.
|
||||
</button>
|
||||
</form>
|
||||
<div class="d-flex gap-2 my-2 w-100">
|
||||
<a href="register" class="btn btn-outline-secondary w-100" type="submit">
|
||||
First setup? Register
|
||||
</a>
|
||||
<a href="register/reset/request" class="btn btn-outline-secondary w-100" type="submit">
|
||||
Forgot password
|
||||
</a>
|
||||
</div>
|
||||
{{ if .Success }}
|
||||
<div class="alert alert-success my-4" role="alert">
|
||||
{{ .Success }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -63,8 +71,12 @@
|
||||
return Array.from(inputs).some((input) => input.value === "")
|
||||
}
|
||||
|
||||
function isFormValid() {
|
||||
return !formFieldsEmpty() && validateEmail()
|
||||
}
|
||||
|
||||
function setButtonDisabled() {
|
||||
button.disabled = formFieldsEmpty() ? "disabled" : ""
|
||||
button.disabled = isFormValid() ? "disabled" : ""
|
||||
}
|
||||
|
||||
function setButtonLoading() {
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>GoCMS | Password reset</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column m-auto form-container">
|
||||
<div>
|
||||
<h1>GoCMS</h1>
|
||||
<h2>Admin account password reset</h2>
|
||||
</div>
|
||||
<form action="request" method="POST" class="mt-5" id="registerForm">
|
||||
<div class="d-flex flex-column gap-4">
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Email }} is-invalid {{ end }}"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="E-mail"
|
||||
required
|
||||
type="email"
|
||||
value="{{.Email}}"
|
||||
onblur="validateEmail()"
|
||||
{{ if .Email }} disabled {{ end }}>
|
||||
<label for="email">E-mail</label>
|
||||
</div>
|
||||
<button id="registerFormButton" class="btn btn-primary" disabled type="submit">
|
||||
<span class="registerFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span class="registerFormButtonDefault">Send e-mail</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ if .Success }}
|
||||
<div class="alert alert-success my-4" role="alert">
|
||||
{{ .Success }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const docElems = {
|
||||
form: document.querySelector("#registerForm"),
|
||||
button: document.querySelector("#registerFormButton"),
|
||||
inputs: document.querySelectorAll("input"),
|
||||
email: document.querySelector("#email"),
|
||||
}
|
||||
|
||||
function formSomeFieldsEmpty() {
|
||||
return Array.from(docElems.inputs).some((input) => input.value === "")
|
||||
}
|
||||
|
||||
function setButtonLoading() {
|
||||
docElems.button.classList.add("disabled")
|
||||
docElems.button.querySelector(".registerFormButtonDefault").classList.add("visually-hidden")
|
||||
docElems.button.querySelector(".registerFormButtonLoading").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
function onRegisterFormSubmit(event) {
|
||||
event.preventDefault()
|
||||
setButtonLoading()
|
||||
if (formSomeFieldsEmpty()) return
|
||||
event.target.submit()
|
||||
}
|
||||
|
||||
function validateEmail() {
|
||||
const email = docElems.email.value.trim()
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
if (!emailRegex.test(email)) {
|
||||
docElems.email.classList.add("is-invalid")
|
||||
return false
|
||||
} else {
|
||||
docElems.email.classList.remove("is-invalid")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function onInput(event) {
|
||||
if (event.target.tagName === "INPUT") {
|
||||
docElems.button.disabled = !validateEmail() || formSomeFieldsEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
docElems.form.addEventListener('submit', onRegisterFormSubmit)
|
||||
window.addEventListener('input', onInput)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,140 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>GoCMS | Password reset</title>
|
||||
{{.Head}}
|
||||
|
||||
<style>
|
||||
.form-container {
|
||||
max-width: 24rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="d-flex min-vh-100 vw-100 justify-content-center align-items-center text-dark">
|
||||
<div class="container">
|
||||
<div class="d-flex flex-column m-auto form-container">
|
||||
<div>
|
||||
<h1>GoCMS</h1>
|
||||
<h2>Admin account password reset</h2>
|
||||
</div>
|
||||
<form action="validate" method="POST" class="mt-5" id="registerForm">
|
||||
<div class="d-flex flex-column gap-4">
|
||||
<div class="form-floating">
|
||||
<input class="form-control"
|
||||
id="code"
|
||||
name="code"
|
||||
placeholder="Code"
|
||||
type="text"
|
||||
value="{{.Code}}"
|
||||
readonly>
|
||||
<label for="code">Code</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="E-mail"
|
||||
type="email"
|
||||
value="{{.Email}}"
|
||||
readonly>
|
||||
<label for="email">E-mail</label>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Password }} is-invalid {{ end }}"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
type="password">
|
||||
<label for="password">Password</label>
|
||||
<div class="invalid-feedback">The password must contain at least 8 characters.</div>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control {{ if .PageError.Password }} is-invalid {{ end }}"
|
||||
id="confirmPassword"
|
||||
name="confirmPassword"
|
||||
placeholder="Password confirmation"
|
||||
required
|
||||
type="password"
|
||||
onblur="validatePasswordMatch()"
|
||||
oninput="validatePasswordMatch()">
|
||||
<label for="confirmPassword">Password confirmation</label>
|
||||
<div class="invalid-feedback">The passwords do not match.</div>
|
||||
</div>
|
||||
<button id="registerFormButton" class="btn btn-primary" disabled type="submit">
|
||||
<span class="registerFormButtonLoading visually-hidden spinner-border spinner-border-sm"
|
||||
role="status"></span>
|
||||
<span class="registerFormButtonDefault">Confirm changes</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{ if .Error.Message }}
|
||||
<div class="alert alert-danger my-4" role="alert">
|
||||
{{ .Error.Message }}
|
||||
</div>
|
||||
{{ end }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const docElems = {
|
||||
form: document.querySelector("#registerForm"),
|
||||
button: document.querySelector("#registerFormButton"),
|
||||
inputs: document.querySelectorAll("input"),
|
||||
email: document.querySelector("#email"),
|
||||
password: document.querySelector("#password"),
|
||||
confirmPassword: document.querySelector("#confirmPassword"),
|
||||
}
|
||||
|
||||
function formSomeFieldsEmpty() {
|
||||
return Array.from(docElems.inputs).some((input) => input.value === "")
|
||||
}
|
||||
|
||||
function setButtonLoading() {
|
||||
docElems.button.classList.add("disabled")
|
||||
docElems.button.querySelector(".registerFormButtonDefault").classList.add("visually-hidden")
|
||||
docElems.button.querySelector(".registerFormButtonLoading").classList.remove("visually-hidden")
|
||||
}
|
||||
|
||||
function onRegisterFormSubmit(event) {
|
||||
event.preventDefault()
|
||||
setButtonLoading()
|
||||
if (formSomeFieldsEmpty()) return
|
||||
event.target.submit()
|
||||
}
|
||||
|
||||
function validateEmail() {
|
||||
const email = docElems.email.value.trim()
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
|
||||
if (!emailRegex.test(email)) {
|
||||
docElems.email.classList.add("is-invalid")
|
||||
return false
|
||||
} else {
|
||||
docElems.email.classList.remove("is-invalid")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function onInput(event) {
|
||||
if (event.target.tagName === "INPUT") {
|
||||
docElems.button.disabled = !validateEmail() || formSomeFieldsEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
function validatePasswordMatch() {
|
||||
if (docElems.password.value !== docElems.confirmPassword.value) {
|
||||
docElems.confirmPassword.classList.add("is-invalid")
|
||||
return false
|
||||
} else {
|
||||
docElems.confirmPassword.classList.remove("is-invalid")
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
docElems.form.addEventListener('submit', onRegisterFormSubmit)
|
||||
window.addEventListener('input', onInput)
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
+17
@@ -105,30 +105,47 @@ func NewPageRouter() http.Handler {
|
||||
r.Handle("/static/*", http.StripPrefix("/static/", fileServer))
|
||||
|
||||
r.Get("/", GetLogin)
|
||||
|
||||
r.Get(LoginRoute, GetLoginPageHandler(EmptyLoginPage))
|
||||
r.Post(LoginRoute, GetLoginPageHandler(EmptyLoginPage))
|
||||
|
||||
r.Get("/register", GetRegisterPageHandler(EmptyRegisterPage))
|
||||
r.Post("/register", GetRegisterPageHandler(EmptyRegisterPage))
|
||||
|
||||
r.Get("/logout", GetLogout)
|
||||
|
||||
r.Get("/register/reset/request", GetPasswordResetRequest)
|
||||
r.Post("/register/reset/request", PostPasswordResetRequest)
|
||||
|
||||
r.Get("/register/reset/validate", GetPasswordResetValidate)
|
||||
r.Post("/register/reset/validate", PostPasswordResetValidate)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(IsLoggedInMiddleware)
|
||||
r.Use(IsNotVerifiedMiddleware)
|
||||
|
||||
r.Get("/register/pending", GetRegisterPendingPage)
|
||||
r.Post("/register/pending", PostRegisterPendingPage)
|
||||
|
||||
r.Get("/register/validate", GetRegisterValidatePage)
|
||||
})
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(IsLoggedInMiddleware)
|
||||
r.Use(IsVerifiedMiddleware)
|
||||
|
||||
r.Get("/home", GetHomePage)
|
||||
|
||||
r.Get("/post", GetPostsPage)
|
||||
|
||||
r.Get("/post/{id}/edit", GetPostEditPage)
|
||||
r.Post("/post/{id}/edit", PostPostEditPage)
|
||||
|
||||
r.Get("/post/{id}/delete", GetPostDeletePage)
|
||||
|
||||
r.Get("/post/create", GetPostCreatePage)
|
||||
r.Post("/post/create", PostPostCreatePage)
|
||||
|
||||
r.Post("/post/{id}/image/create", postImage)
|
||||
})
|
||||
|
||||
|
||||
+4
-4
@@ -2,6 +2,7 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const LoginRoute = "/login"
|
||||
@@ -30,14 +31,13 @@ func GetLoginPageHandler(loginPage *LoginPage) http.HandlerFunc {
|
||||
http.Redirect(w, r, "/register", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
bs, err := Container.GetPageUseCase.GetPage("login", map[string]interface{}{
|
||||
success, _ := url.QueryUnescape(r.URL.Query().Get("success"))
|
||||
bs, _ := Container.GetPageUseCase.GetPage("login", map[string]interface{}{
|
||||
"PageError": loginPage.PageError,
|
||||
"Username": loginPage.Username,
|
||||
"Head": headTmpl,
|
||||
"Success": success,
|
||||
})
|
||||
if err != nil {
|
||||
_, _ = w.Write([]byte(err.Error()))
|
||||
}
|
||||
_, _ = w.Write(bs)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
type PasswordResetRequest struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
var successMessage = "An email has been sent to the provided email address if it exists."
|
||||
|
||||
func GetPasswordResetRequest(w http.ResponseWriter, r *http.Request) {
|
||||
success := r.URL.Query().Get("success")
|
||||
email := r.URL.Query().Get("email")
|
||||
|
||||
bs, _ := Container.GetPageUseCase.GetPage("passwordResetRequest", map[string]interface{}{
|
||||
"Head": headTmpl,
|
||||
"Email": email,
|
||||
"Success": success,
|
||||
})
|
||||
_, _ = w.Write(bs)
|
||||
}
|
||||
|
||||
func PostPasswordResetRequest(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
passResetReq := PasswordResetRequest{
|
||||
Email: r.FormValue("email"),
|
||||
}
|
||||
var getRedirectUrl = "/register/reset/request?success=" + successMessage + "&email=" + passResetReq.Email
|
||||
|
||||
err := validate.Struct(passResetReq)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
fetchedUser, err := Container.GetUserUseCase.GetUserByEmail(passResetReq.Email)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
verificationCode := uuid.NewString()
|
||||
updatedUser, _ := Container.UpdateUserUseCase.UpdatePasswordResetCode(fetchedUser.ID, verificationCode)
|
||||
|
||||
_ = Container.SendMailUseCase.SendMail(updatedUser.Email, "passwordReset", map[string]string{
|
||||
"Host": os.Getenv("HOST"),
|
||||
"VerificationCode": verificationCode,
|
||||
"Email": updatedUser.Email,
|
||||
})
|
||||
|
||||
http.Redirect(w, r, getRedirectUrl, http.StatusSeeOther)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var PasswordLinkErrorMessage = "The reset password link you used is invalid."
|
||||
var PasswordLinkSuccessMessage = "Your account password was successfully updated."
|
||||
|
||||
func GetPasswordResetValidate(w http.ResponseWriter, r *http.Request) {
|
||||
failure := r.URL.Query().Get("failure")
|
||||
pageError := NewPageError("")
|
||||
if failure != "" {
|
||||
pageError = NewPageError(failure)
|
||||
}
|
||||
template, _ := Container.GetPageUseCase.GetPage("passwordResetValidate", map[string]interface{}{
|
||||
"Head": headTmpl,
|
||||
"Error": pageError,
|
||||
"Email": r.URL.Query().Get("email"),
|
||||
"Code": r.URL.Query().Get("c"),
|
||||
})
|
||||
_, _ = w.Write(template)
|
||||
}
|
||||
|
||||
func PostPasswordResetValidate(w http.ResponseWriter, r *http.Request) {
|
||||
_ = r.ParseForm()
|
||||
email := r.FormValue("email")
|
||||
password := r.FormValue("password")
|
||||
resetCode := r.FormValue("code")
|
||||
|
||||
var redirectionErrorLink = "/register/reset/validate?email=" + email + "&c=" + resetCode
|
||||
|
||||
if len(password) < 8 {
|
||||
http.Redirect(w, r, redirectionErrorLink+"&failure=Password length should be at least 8 characters.", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
fetchedUser, err := Container.GetUserUseCase.GetUserByEmail(email)
|
||||
if err != nil {
|
||||
http.Redirect(w, r, redirectionErrorLink+"&failure="+PasswordLinkErrorMessage, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(fetchedUser.PasswordResetCode), []byte(resetCode))
|
||||
if err != nil {
|
||||
http.Redirect(w, r, redirectionErrorLink+"&failure="+PasswordLinkErrorMessage, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = Container.UpdateUserUseCase.UpdatePassword(fetchedUser.ID, password)
|
||||
_, _ = Container.UpdateUserUseCase.UpdatePasswordResetCode(fetchedUser.ID, "")
|
||||
|
||||
http.Redirect(w, r, "/login?success="+PasswordLinkSuccessMessage, http.StatusSeeOther)
|
||||
}
|
||||
@@ -5,8 +5,11 @@ import "GoCMS/domain/user"
|
||||
type IUserRepository interface {
|
||||
Get(id uint32) (user.User, error)
|
||||
GetByUsername(username string) (user.User, error)
|
||||
GetByEmail(email string) (user.User, error)
|
||||
GetAll() []user.User
|
||||
Create(user user.User) (user.User, error)
|
||||
Delete(id uint32) error
|
||||
UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error)
|
||||
UpdatePassword(userId uint32, password string) (user.User, error)
|
||||
UpdatePasswordResetCode(userId uint32, code string) (user.User, error)
|
||||
}
|
||||
|
||||
@@ -5,11 +5,11 @@ import (
|
||||
)
|
||||
|
||||
type Image struct {
|
||||
ID uint32
|
||||
Path string
|
||||
PostID uint32
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
ID uint32 `json:"id"`
|
||||
Path string `json:"path"`
|
||||
PostID uint32 `json:"post_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func FromDB(id uint32, path string, postId uint32, createdAt time.Time, updatedAt time.Time) Image {
|
||||
|
||||
+8
-3
@@ -8,10 +8,11 @@ type User struct {
|
||||
ID uint32 `json:"id"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
PasswordResetCode string `json:"password_reset_code"`
|
||||
Email string `json:"email"`
|
||||
IsVerified bool `gorm:"default=false;not null"`
|
||||
VerificationCode string `gorm:"unique;not null"`
|
||||
VerificationExpiration time.Time `gorm:"not null"`
|
||||
IsVerified bool `json:"is_verified"`
|
||||
VerificationCode string `json:"verification_code"`
|
||||
VerificationExpiration time.Time `json:"verification_expiration"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
@@ -19,6 +20,7 @@ type User struct {
|
||||
func FromApi(
|
||||
username string,
|
||||
password string,
|
||||
passwordResetCode string,
|
||||
email string,
|
||||
verificationCode string,
|
||||
) User {
|
||||
@@ -26,6 +28,7 @@ func FromApi(
|
||||
return User{
|
||||
Username: username,
|
||||
Password: password,
|
||||
PasswordResetCode: passwordResetCode,
|
||||
Email: email,
|
||||
IsVerified: false,
|
||||
VerificationCode: verificationCode,
|
||||
@@ -37,6 +40,7 @@ func FromDb(
|
||||
id uint32,
|
||||
username string,
|
||||
password string,
|
||||
passwordResetCode string,
|
||||
email string,
|
||||
isVerified bool,
|
||||
verificationCode string,
|
||||
@@ -48,6 +52,7 @@ func FromDb(
|
||||
ID: id,
|
||||
Username: username,
|
||||
Password: password,
|
||||
PasswordResetCode: passwordResetCode,
|
||||
Email: email,
|
||||
IsVerified: isVerified,
|
||||
VerificationCode: verificationCode,
|
||||
|
||||
@@ -27,6 +27,7 @@ func (g *CreateUserUseCase) CreateUser(createUser CreateUserCommand) (user.User,
|
||||
return g.userRepository.Create(user.FromApi(
|
||||
createUser.Username,
|
||||
createUser.Password,
|
||||
"",
|
||||
createUser.Email,
|
||||
createUser.VerificationCode,
|
||||
))
|
||||
|
||||
@@ -23,3 +23,7 @@ func (g *GetUserUseCase) GetUser(id uint32) (user.User, error) {
|
||||
func (g *GetUserUseCase) GetUserByUsername(username string) (user.User, error) {
|
||||
return g.userRepository.GetByUsername(username)
|
||||
}
|
||||
|
||||
func (g *GetUserUseCase) GetUserByEmail(email string) (user.User, error) {
|
||||
return g.userRepository.GetByEmail(email)
|
||||
}
|
||||
|
||||
@@ -19,3 +19,11 @@ func NewUpdateUserUseCase(db *gorm.DB) *UpdateUserUseCase {
|
||||
func (g *UpdateUserUseCase) UpdateVerificationStatus(userId uint32, isVerified bool) (user.User, error) {
|
||||
return g.userRepository.UpdateVerificationStatus(userId, isVerified)
|
||||
}
|
||||
|
||||
func (g *UpdateUserUseCase) UpdatePasswordResetCode(userId uint32, code string) (user.User, error) {
|
||||
return g.userRepository.UpdatePasswordResetCode(userId, code)
|
||||
}
|
||||
|
||||
func (g *UpdateUserUseCase) UpdatePassword(userId uint32, password string) (user.User, error) {
|
||||
return g.userRepository.UpdatePassword(userId, password)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user