feat: separate update profile and password

This commit is contained in:
Damillora 2025-02-22 18:07:50 +00:00
parent 6581e40f80
commit c87e76566d
8 changed files with 271 additions and 19 deletions

View File

@ -3,6 +3,8 @@ package app
import ( import (
"net/http" "net/http"
"github.com/Damillora/Shioriko/pkg/database"
"github.com/Damillora/Shioriko/pkg/middleware"
"github.com/Damillora/Shioriko/pkg/models" "github.com/Damillora/Shioriko/pkg/models"
"github.com/Damillora/Shioriko/pkg/services" "github.com/Damillora/Shioriko/pkg/services"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -11,6 +13,11 @@ import (
func InitializeAuthRoutes(g *gin.Engine) { func InitializeAuthRoutes(g *gin.Engine) {
g.POST("/api/auth/login", createToken) g.POST("/api/auth/login", createToken)
protected := g.Group("/api/auth").Use(middleware.AuthMiddleware())
{
protected.POST("/token", createTokenLoggedIn)
}
} }
func createToken(c *gin.Context) { func createToken(c *gin.Context) {
var model models.LoginFormModel var model models.LoginFormModel
@ -50,3 +57,21 @@ func createToken(c *gin.Context) {
}) })
} }
} }
func createTokenLoggedIn(c *gin.Context) {
result, ok := c.Get("user")
if ok {
user := result.(*database.User)
if user != nil {
token := services.CreateToken(user)
c.JSON(http.StatusOK, models.TokenResponse{
Token: token,
})
}
} else {
c.JSON(http.StatusUnauthorized, models.ErrorResponse{
Code: http.StatusUnauthorized,
Message: "No authorized user",
})
}
}

View File

@ -18,7 +18,8 @@ func InitializeUserRoutes(g *gin.Engine) {
protected := g.Group("/api/user").Use(middleware.AuthMiddleware()) protected := g.Group("/api/user").Use(middleware.AuthMiddleware())
{ {
protected.GET("/profile", userProfile) protected.GET("/profile", userProfile)
protected.POST("/update", userUpdate) protected.PUT("/update", userUpdate)
protected.PUT("/update-password", userUpdatePassword)
} }
} }
@ -114,7 +115,45 @@ func userUpdate(c *gin.Context) {
result, ok := c.Get("user") result, ok := c.Get("user")
if ok { if ok {
user := result.(*database.User) user := result.(*database.User)
services.UpdateUser(user.ID, model) services.UpdateUserProfile(user.ID, model)
c.JSON(http.StatusOK, nil)
} else {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: http.StatusBadRequest,
Message: "User does not exist",
})
}
}
func userUpdatePassword(c *gin.Context) {
var model models.UserUpdatePasswordModel
err := c.ShouldBindJSON(&model)
if err != nil {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: http.StatusBadRequest,
Message: err.Error(),
})
c.Abort()
return
}
validate := validator.New()
err = validate.Struct(model)
if err != nil {
c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: http.StatusBadRequest,
Message: err.Error(),
})
c.Abort()
return
}
result, ok := c.Get("user")
if ok {
user := result.(*database.User)
services.UpdateUserPassword(user.ID, model)
c.JSON(http.StatusOK, nil)
} else { } else {
c.JSON(http.StatusBadRequest, models.ErrorResponse{ c.JSON(http.StatusBadRequest, models.ErrorResponse{
Code: http.StatusBadRequest, Code: http.StatusBadRequest,

View File

@ -7,8 +7,11 @@ type UserCreateModel struct {
} }
type UserUpdateModel struct { type UserUpdateModel struct {
Email string `json:"email" validate:"required,email"` Email string `json:"email" validate:"required,email"`
Username string `json:"username" validate:"required"` Username string `json:"username" validate:"required"`
}
type UserUpdatePasswordModel struct {
OldPassword string `json:"old_password"` OldPassword string `json:"old_password"`
NewPassword string `json:"new_password"` NewPassword string `json:"new_password"`
} }

View File

@ -39,7 +39,7 @@ func GetUserFromUsername(username string) *database.User {
return &user return &user
} }
func UpdateUser(id string, model models.UserUpdateModel) (*database.User, error) { func UpdateUserProfile(id string, model models.UserUpdateModel) (*database.User, error) {
var user database.User var user database.User
result := database.DB.Where("id = ?", id).First(&user) result := database.DB.Where("id = ?", id).First(&user)
@ -49,6 +49,19 @@ func UpdateUser(id string, model models.UserUpdateModel) (*database.User, error)
user.Email = model.Email user.Email = model.Email
user.Username = model.Username user.Username = model.Username
result = database.DB.Save(&user)
if result.Error != nil {
return nil, result.Error
}
return &user, nil
}
func UpdateUserPassword(id string, model models.UserUpdatePasswordModel) (*database.User, error) {
var user database.User
result := database.DB.Where("id = ?", id).First(&user)
if user.Password != "" { if user.Password != "" {
verifyErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(model.OldPassword)) verifyErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(model.OldPassword))
if verifyErr != nil { if verifyErr != nil {

View File

@ -37,6 +37,20 @@ export async function register({ email, username, password }) {
return response.data; return response.data;
} }
export async function updateToken() {
const endpoint = url + "/api/auth/token";
const response = await axios({
url: endpoint,
method: "POST",
headers: {
'Authorization': 'Bearer ' + current_token,
},
})
token.set(response.data.token);
return response.data;
}
export async function getTags() { export async function getTags() {
const endpoint = url + "/api/tag"; const endpoint = url + "/api/tag";
const response = await axios.get(endpoint); const response = await axios.get(endpoint);
@ -200,8 +214,8 @@ export async function updateTag(id, { name, tagTypeId }) {
return response.data; return response.data;
} }
export async function updateUserProfile({ email, username, oldPassword, newPassword }) { export async function updateUserProfile({ email, username, }) {
const endpoint = url + "/api/tag/" + id; const endpoint = url + "/api/user/update";
const response = await axios({ const response = await axios({
url: endpoint, url: endpoint,
method: "PUT", method: "PUT",
@ -210,7 +224,22 @@ export async function updateUserProfile({ email, username, oldPassword, newPassw
}, },
withCredentials: true, withCredentials: true,
data: { data: {
email, username, oldPassword, newPassword email, username,
}
})
return response.data;
}
export async function updateUserPassword({ old_password, new_password }) {
const endpoint = url + "/api/user/update-password";
const response = await axios({
url: endpoint,
method: "PUT",
headers: {
'Authorization': 'Bearer ' + current_token,
},
withCredentials: true,
data: {
old_password, new_password
} }
}) })
return response.data; return response.data;

View File

@ -0,0 +1,8 @@
<script>
</script>
<div class="panel is-primary">
<div class="panel-heading">User Actions</div>
<a href="/user/profile" class="panel-block">Profile</a>
<a href="/user/password" class="panel-block">Change Password</a>
</div>

View File

@ -0,0 +1,79 @@
<script>
import { updateToken, updateUserPassword, } from "$lib/api";
import UserActionsPanel from "$lib/components/panels/UserActionsPanel.svelte";
import { onMount } from "svelte";
let loading = $state(false);
let updated = $state(false);
let form = $state({
old_password: "",
new_password: "",
});
const getData = async () => {
loading = false;
};
const submitForm = async () => {
updated = false;
await updateUserPassword({ old_password: form.old_password, new_password: form.new_password })
await updateToken();
updated = true;
};
onMount(() => {
loading = true;
getData();
});
</script>
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-one-third">
<UserActionsPanel />
</div>
<div class="column is-two-thirds">
<div class="box">
<h1 class="title">Change Password</h1>
{#if updated}
<div class="notification is-success">
Password updated!
</div>
{/if}
<form onsubmit={submitForm}>
<div class="field">
<label class="label" for="old_password">Current Password</label>
<div class="control">
<input
id="old_password"
class="input"
class:is-skeleton="{loading}"
type="password"
placeholder="Current password"
bind:value={form.old_password}
/>
</div>
</div>
<div class="field">
<label class="label" for="new_password">New Password</label>
<div class="control">
<input
id="new_password"
class="input"
class:is-skeleton="{loading}"
type="password"
placeholder="New password"
bind:value={form.new_password}
/>
</div>
</div>
<div class="field">
<button class="button is-primary is-fullwidth is-outlined" type="submit">Change Password</button>
</div>
</form>
</div>
</div>
</div>
</div>
</section>

View File

@ -1,26 +1,82 @@
<script> <script>
import { getUserProfile, updateToken, updateUserProfile } from "$lib/api";
import UserActionsPanel from "$lib/components/panels/UserActionsPanel.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import { getUserProfile } from "$lib/api";
import AuthRequired from "$lib/components/checks/AuthRequired.svelte";
let user = $state(); let loading = $state(false);
let updated = $state(false);
let form = $state({
email: "",
username: "",
});
const getData = async () => { const getData = async () => {
user = await getUserProfile(); const user = await getUserProfile();
form.email = user.email;
form.username = user.username;
loading = false;
}; };
const submitForm = async () => {
updated = false;
await updateUserProfile({ email: form.email, username: form.username })
await updateToken();
updated = true;
};
onMount(() => { onMount(() => {
loading = true;
getData(); getData();
}); });
</script> </script>
<AuthRequired />
<section class="section"> <section class="section">
<div class="container"> <div class="container">
{#if user} <div class="columns">
<h1 class="title">Welcome, {user.username}</h1> <div class="column is-one-third">
<p>Email: {user.email}</p> <UserActionsPanel />
<p>Username: {user.username}</p> </div>
{/if} <div class="column is-two-thirds">
<div class="box">
<h1 class="title">Profile</h1>
{#if updated}
<div class="notification is-success">
Profile updated!
</div>
{/if}
<form onsubmit={submitForm}>
<div class="field">
<label class="label" for="email">Email</label>
<div class="control">
<input
id="email"
class="input"
class:is-skeleton="{loading}"
type="text"
placeholder="Email"
bind:value={form.email}
/>
</div>
</div>
<div class="field">
<label class="label" for="username">Username</label>
<div class="control">
<input
id="username"
class="input"
class:is-skeleton="{loading}"
type="text"
placeholder="Username"
bind:value={form.username}
/>
</div>
</div>
<div class="field">
<button class="button is-primary is-fullwidth is-outlined" type="submit">Update</button>
</div>
</form>
</div>
</div>
</div>
</div> </div>
</section> </section>