mirror of
https://github.com/Damillora/phoebe.git
synced 2025-04-15 19:33:12 +00:00
Compare commits
9 Commits
6bde5a6af6
...
f0a606e561
Author | SHA1 | Date | |
---|---|---|---|
f0a606e561 | |||
bf064f0e4d | |||
bf5114e000 | |||
c87e76566d | |||
6581e40f80 | |||
c887a9e49c | |||
0fab0b7f48 | |||
374ee82c76 | |||
560006aaa8 |
@ -3,6 +3,8 @@ package app
|
||||
import (
|
||||
"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/services"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -11,6 +13,11 @@ import (
|
||||
|
||||
func InitializeAuthRoutes(g *gin.Engine) {
|
||||
g.POST("/api/auth/login", createToken)
|
||||
|
||||
protected := g.Group("/api/auth").Use(middleware.AuthMiddleware())
|
||||
{
|
||||
protected.POST("/token", createTokenLoggedIn)
|
||||
}
|
||||
}
|
||||
func createToken(c *gin.Context) {
|
||||
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",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ func InitializeUserRoutes(g *gin.Engine) {
|
||||
protected := g.Group("/api/user").Use(middleware.AuthMiddleware())
|
||||
{
|
||||
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")
|
||||
if ok {
|
||||
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 {
|
||||
c.JSON(http.StatusBadRequest, models.ErrorResponse{
|
||||
Code: http.StatusBadRequest,
|
||||
|
@ -7,10 +7,13 @@ type UserCreateModel struct {
|
||||
}
|
||||
|
||||
type UserUpdateModel struct {
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
OldPassword string `json:"oldPassword"`
|
||||
NewPassword string `json:"newPassword"`
|
||||
Email string `json:"email" validate:"required,email"`
|
||||
Username string `json:"username" validate:"required"`
|
||||
}
|
||||
|
||||
type UserUpdatePasswordModel struct {
|
||||
OldPassword string `json:"old_password"`
|
||||
NewPassword string `json:"new_password"`
|
||||
}
|
||||
|
||||
type TagTypeCreateModel struct {
|
||||
|
@ -39,7 +39,7 @@ func GetUserFromUsername(username string) *database.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
|
||||
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.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 != "" {
|
||||
verifyErr := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(model.OldPassword))
|
||||
if verifyErr != nil {
|
||||
|
@ -62,7 +62,7 @@
|
||||
@extend .dropdown-item;
|
||||
background-color: hsl(var(--bulma-scheme-h), var(--bulma-scheme-s), calc(var(--bulma-scheme-main-l) + var(--bulma-dropdown-item-background-l-delta)));
|
||||
--bulma-dropdown-item-background-l-delta: 0%;
|
||||
&:hover {
|
||||
&:hover, &.focus, &:focus {
|
||||
--bulma-dropdown-item-background-l-delta: var(--bulma-hover-background-l-delta);
|
||||
--bulma-dropdown-item-border-l-delta: var(--bulma-hover-border-l-delta);
|
||||
}
|
||||
|
@ -37,6 +37,20 @@ export async function register({ email, username, password }) {
|
||||
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() {
|
||||
const endpoint = url + "/api/tag";
|
||||
const response = await axios.get(endpoint);
|
||||
@ -200,6 +214,36 @@ export async function updateTag(id, { name, tagTypeId }) {
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export async function updateUserProfile({ email, username, }) {
|
||||
const endpoint = url + "/api/user/update";
|
||||
const response = await axios({
|
||||
url: endpoint,
|
||||
method: "PUT",
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + current_token,
|
||||
},
|
||||
withCredentials: true,
|
||||
data: {
|
||||
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;
|
||||
}
|
||||
|
||||
export async function getTagTypes() {
|
||||
const endpoint = url + "/api/tagtype";
|
||||
|
@ -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>
|
@ -4,6 +4,7 @@
|
||||
import { getRelatedTags } from "$lib/api";
|
||||
import AuthCheck from "$lib/components/checks/AuthCheck.svelte";
|
||||
import TagLinkNumbered from "$lib/components/ui/TagLinkNumbered.svelte";
|
||||
import TagTypeIndicator from "../ui/TagTypeIndicator.svelte";
|
||||
|
||||
let { tag, data, toggleRenameMenu } = $props();
|
||||
let related_tags = $state([]);
|
||||
@ -30,7 +31,7 @@
|
||||
<div class="row">
|
||||
<strong>Category:</strong>
|
||||
</div>
|
||||
<div class="row">{data.tagType}</div>
|
||||
<div class="row"><TagTypeIndicator tagType={data.tagType} /></div>
|
||||
</div>
|
||||
<div class="panel-block column">
|
||||
<div class="row">
|
||||
|
@ -33,6 +33,7 @@
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
<span aria-hidden="true"></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@ -46,20 +47,27 @@
|
||||
</div>
|
||||
|
||||
<div class="navbar-end">
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<div
|
||||
class="navbar-item has-dropdown is-hoverable"
|
||||
>
|
||||
<a
|
||||
href={"#"}
|
||||
class="navbar-link"
|
||||
>
|
||||
{loggedIn ? username : "Guest"}
|
||||
</a>
|
||||
{#if loggedIn}
|
||||
<div class="navbar-link">{username}</div>
|
||||
|
||||
<div class="navbar-dropdown">
|
||||
<div class="navbar-dropdown is-right">
|
||||
<a href="/user/profile" class="navbar-item">
|
||||
Profile
|
||||
</a>
|
||||
|
||||
<a href="/user/password" class="navbar-item">
|
||||
Change Password
|
||||
</a>
|
||||
<hr class="navbar-divider" />
|
||||
<a href="/auth/logout" class="navbar-item">Log out</a>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="navbar-link">logged out</div>
|
||||
|
||||
<div class="navbar-dropdown">
|
||||
<a href="/auth/register" class="navbar-item">
|
||||
Register
|
||||
|
@ -13,7 +13,7 @@ const getUsernameFromToken = (token) => {
|
||||
const tokenData = (JSON.parse(atob(token.split('.')[1])));
|
||||
return tokenData.name;
|
||||
}
|
||||
return "logged out";
|
||||
return "Guest";
|
||||
|
||||
}
|
||||
|
||||
|
@ -54,8 +54,8 @@
|
||||
let imagePercentage = $state("0%");
|
||||
</script>
|
||||
|
||||
<div class="container">
|
||||
<section class="section">
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-one-third">
|
||||
{#if post}
|
||||
@ -90,9 +90,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="skeleton-block">
|
||||
|
||||
</div>
|
||||
<div class="skeleton-block"></div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="column box">
|
||||
@ -100,27 +98,33 @@
|
||||
{#if post.width > 1000 && isOriginal == false}
|
||||
<div class="notification is-info">
|
||||
Resized to {imagePercentage} of the original image.
|
||||
<a onclick="{() => { isOriginal = true; }}"
|
||||
>View original</a
|
||||
<a
|
||||
onclick={() => {
|
||||
isOriginal = true;
|
||||
}}>View original</a
|
||||
>
|
||||
</div>
|
||||
<figure class="image">
|
||||
<ShiorikoImage alt={post.id} src={post.preview_path} />
|
||||
<ShiorikoImage
|
||||
alt={post.id}
|
||||
src={post.preview_path}
|
||||
/>
|
||||
</figure>
|
||||
{:else}
|
||||
<div class="notification is-primary">
|
||||
Currently viewing original image.
|
||||
</div>
|
||||
<figure class="image">
|
||||
<ShiorikoImage alt={post.id} src={post.image_path} />
|
||||
<ShiorikoImage
|
||||
alt={post.id}
|
||||
src={post.image_path}
|
||||
/>
|
||||
</figure>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="skeleton-block">
|
||||
|
||||
</div>
|
||||
<div class="skeleton-block"></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -97,9 +97,6 @@
|
||||
<div class="container">
|
||||
<div class="block">
|
||||
<div class="columns is-multiline">
|
||||
<div class="column is-full">
|
||||
<div class="block"></div>
|
||||
</div>
|
||||
<div class="column is-one-third">
|
||||
<div class="panel is-primary">
|
||||
<div class="panel-heading">Menu</div>
|
||||
|
@ -184,9 +184,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
<h1 class="title">Upload Image</h1>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
79
web/app/src/routes/user/password/+page.svelte
Normal file
79
web/app/src/routes/user/password/+page.svelte
Normal 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>
|
@ -1,26 +1,82 @@
|
||||
<script>
|
||||
import { getUserProfile, updateToken, updateUserProfile } from "$lib/api";
|
||||
import UserActionsPanel from "$lib/components/panels/UserActionsPanel.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 () => {
|
||||
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(() => {
|
||||
loading = true;
|
||||
getData();
|
||||
});
|
||||
</script>
|
||||
|
||||
<AuthRequired />
|
||||
|
||||
<section class="section">
|
||||
<div class="container">
|
||||
{#if user}
|
||||
<h1 class="title">Welcome, {user.username}</h1>
|
||||
<p>Email: {user.email}</p>
|
||||
<p>Username: {user.username}</p>
|
||||
{/if}
|
||||
<div class="columns">
|
||||
<div class="column is-one-third">
|
||||
<UserActionsPanel />
|
||||
</div>
|
||||
<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>
|
||||
</section>
|
||||
|
Loading…
x
Reference in New Issue
Block a user