feat: implement wish list and wish features including creation, retrieval, and updates;
fix: modify ban logic to respect expiration timestamps and pardon flags; refactor: change boolean fields to non-nullable in models and use COALESCE for optional updates in SQL
This commit is contained in:
@@ -15,7 +15,7 @@ type BannedUser struct {
|
||||
Reason *string
|
||||
ExpiresAt pgtype.Timestamp
|
||||
BannedBy *string
|
||||
Pardoned *bool
|
||||
Pardoned bool
|
||||
PardonedBy *string
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ type ConfirmationCode struct {
|
||||
CodeType int32
|
||||
CodeHash string
|
||||
ExpiresAt pgtype.Timestamp
|
||||
Used *bool
|
||||
Deleted *bool
|
||||
Used bool
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
type LoginInformation struct {
|
||||
@@ -78,8 +78,34 @@ type Session struct {
|
||||
type User struct {
|
||||
ID int64
|
||||
Username string
|
||||
Verified *bool
|
||||
Verified bool
|
||||
RegistrationDate pgtype.Timestamp
|
||||
Role int32
|
||||
Deleted *bool
|
||||
}
|
||||
|
||||
type Wish struct {
|
||||
ID int64
|
||||
Guid pgtype.UUID
|
||||
WishListID int64
|
||||
Name string
|
||||
Description string
|
||||
PictureUrl string
|
||||
Stars int16
|
||||
CreationDate pgtype.Timestamp
|
||||
Fulfilled bool
|
||||
FulfilledDate pgtype.Timestamp
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
type WishList struct {
|
||||
ID int64
|
||||
Guid pgtype.UUID
|
||||
ProfileID int64
|
||||
Hidden bool
|
||||
Name string
|
||||
IconName *string
|
||||
Color *string
|
||||
ColorGrad *string
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
@@ -253,6 +253,54 @@ func (q *Queries) CreateUser(ctx context.Context, username string) (User, error)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createWishList = `-- name: CreateWishList :one
|
||||
INSERT INTO wish_lists(profile_id, hidden, name, icon_name, color, color_grad)
|
||||
VALUES (
|
||||
(SELECT p.id FROM profiles AS p
|
||||
JOIN users AS u ON u.id = p.user_id
|
||||
WHERE u.username = $1::text),
|
||||
$2::boolean,
|
||||
$3::text,
|
||||
$4::text,
|
||||
$5::text,
|
||||
$6::boolean
|
||||
)
|
||||
RETURNING id, guid, profile_id, hidden, name, icon_name, color, color_grad, deleted
|
||||
`
|
||||
|
||||
type CreateWishListParams struct {
|
||||
Username string
|
||||
Hidden bool
|
||||
Name string
|
||||
IconName string
|
||||
Color string
|
||||
ColorGrad bool
|
||||
}
|
||||
|
||||
func (q *Queries) CreateWishList(ctx context.Context, arg CreateWishListParams) (WishList, error) {
|
||||
row := q.db.QueryRow(ctx, createWishList,
|
||||
arg.Username,
|
||||
arg.Hidden,
|
||||
arg.Name,
|
||||
arg.IconName,
|
||||
arg.Color,
|
||||
arg.ColorGrad,
|
||||
)
|
||||
var i WishList
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Guid,
|
||||
&i.ProfileID,
|
||||
&i.Hidden,
|
||||
&i.Name,
|
||||
&i.IconName,
|
||||
&i.Color,
|
||||
&i.ColorGrad,
|
||||
&i.Deleted,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteUnverifiedAccountsHavingUsernameOrEmail = `-- name: DeleteUnverifiedAccountsHavingUsernameOrEmail :one
|
||||
WITH deleted_rows AS (
|
||||
DELETE FROM users
|
||||
@@ -369,7 +417,9 @@ WHERE
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM banned_users
|
||||
WHERE user_id = u.id
|
||||
WHERE user_id = u.id AND
|
||||
pardoned IS FALSE AND
|
||||
(expires_at IS NULL OR expires_at < CURRENT_TIMESTAMP)
|
||||
)
|
||||
)
|
||||
`
|
||||
@@ -726,11 +776,11 @@ type GetValidConfirmationCodesByUsernameRow struct {
|
||||
CodeType int32
|
||||
CodeHash string
|
||||
ExpiresAt pgtype.Timestamp
|
||||
Used *bool
|
||||
Deleted *bool
|
||||
Used bool
|
||||
Deleted bool
|
||||
ID_2 int64
|
||||
Username string
|
||||
Verified *bool
|
||||
Verified bool
|
||||
RegistrationDate pgtype.Timestamp
|
||||
Role int32
|
||||
Deleted_2 *bool
|
||||
@@ -777,12 +827,17 @@ SELECT
|
||||
linfo.totp_encrypted
|
||||
FROM users
|
||||
JOIN login_informations AS linfo ON users.id = linfo.user_id
|
||||
LEFT JOIN banned_users AS banned ON users.id = banned.user_id
|
||||
WHERE
|
||||
users.username = $1 AND
|
||||
users.verified IS TRUE AND -- Verified
|
||||
users.deleted IS FALSE AND -- Not deleted
|
||||
banned.user_id IS NULL AND -- Not banned
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM banned_users
|
||||
WHERE user_id = users.id AND
|
||||
pardoned IS FALSE AND
|
||||
(expires_at IS NULL OR expires_at < CURRENT_TIMESTAMP)
|
||||
) AND -- Not banned
|
||||
linfo.password_hash = crypt($2::text, linfo.password_hash)
|
||||
`
|
||||
|
||||
@@ -794,7 +849,7 @@ type GetValidUserByLoginCredentialsParams struct {
|
||||
type GetValidUserByLoginCredentialsRow struct {
|
||||
ID int64
|
||||
Username string
|
||||
Verified *bool
|
||||
Verified bool
|
||||
RegistrationDate pgtype.Timestamp
|
||||
Role int32
|
||||
Deleted *bool
|
||||
@@ -856,6 +911,108 @@ func (q *Queries) GetValidUserSessions(ctx context.Context, userID int64) ([]Ses
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWishlistByGuid = `-- name: GetWishlistByGuid :one
|
||||
SELECT id, guid, profile_id, hidden, name, icon_name, color, color_grad, deleted FROM wish_lists wl
|
||||
WHERE wl.guid = ($1::text)::uuid
|
||||
`
|
||||
|
||||
func (q *Queries) GetWishlistByGuid(ctx context.Context, guid string) (WishList, error) {
|
||||
row := q.db.QueryRow(ctx, getWishlistByGuid, guid)
|
||||
var i WishList
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Guid,
|
||||
&i.ProfileID,
|
||||
&i.Hidden,
|
||||
&i.Name,
|
||||
&i.IconName,
|
||||
&i.Color,
|
||||
&i.ColorGrad,
|
||||
&i.Deleted,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWishlistsByUsernameWithPrivacy = `-- name: GetWishlistsByUsernameWithPrivacy :many
|
||||
SELECT
|
||||
wl.id, wl.guid, wl.profile_id, wl.hidden, wl.name, wl.icon_name, wl.color, wl.color_grad, wl.deleted,
|
||||
CASE
|
||||
WHEN (ps.hide_profile_details OR ps.hide_for_unauthenticated) THEN FALSE
|
||||
ELSE TRUE
|
||||
END AS access_allowed
|
||||
FROM
|
||||
wish_lists wl
|
||||
JOIN
|
||||
profiles AS p ON wl.profile_id = p.id
|
||||
JOIN
|
||||
profile_settings AS ps ON ps.profile_id = p.id
|
||||
JOIN
|
||||
users AS u ON p.user_id = u.id
|
||||
WHERE
|
||||
wl.deleted IS FALSE AND
|
||||
u.username = $1::text AND
|
||||
(
|
||||
u.username = $2::text OR
|
||||
(u.verified IS TRUE AND
|
||||
NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM banned_users
|
||||
WHERE user_id = u.id AND
|
||||
pardoned IS FALSE AND
|
||||
(expires_at IS NULL OR expires_at < CURRENT_TIMESTAMP)
|
||||
))
|
||||
)
|
||||
`
|
||||
|
||||
type GetWishlistsByUsernameWithPrivacyParams struct {
|
||||
Username string
|
||||
Requester string
|
||||
}
|
||||
|
||||
type GetWishlistsByUsernameWithPrivacyRow struct {
|
||||
ID int64
|
||||
Guid pgtype.UUID
|
||||
ProfileID int64
|
||||
Hidden bool
|
||||
Name string
|
||||
IconName *string
|
||||
Color *string
|
||||
ColorGrad *string
|
||||
Deleted bool
|
||||
AccessAllowed bool
|
||||
}
|
||||
|
||||
func (q *Queries) GetWishlistsByUsernameWithPrivacy(ctx context.Context, arg GetWishlistsByUsernameWithPrivacyParams) ([]GetWishlistsByUsernameWithPrivacyRow, error) {
|
||||
rows, err := q.db.Query(ctx, getWishlistsByUsernameWithPrivacy, arg.Username, arg.Requester)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []GetWishlistsByUsernameWithPrivacyRow
|
||||
for rows.Next() {
|
||||
var i GetWishlistsByUsernameWithPrivacyRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Guid,
|
||||
&i.ProfileID,
|
||||
&i.Hidden,
|
||||
&i.Name,
|
||||
&i.IconName,
|
||||
&i.Color,
|
||||
&i.ColorGrad,
|
||||
&i.Deleted,
|
||||
&i.AccessAllowed,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const pruneExpiredConfirmationCodes = `-- name: PruneExpiredConfirmationCodes :exec
|
||||
DELETE FROM confirmation_codes
|
||||
WHERE expires_at < CURRENT_TIMESTAMP
|
||||
@@ -920,7 +1077,7 @@ type UpdateBannedUserParams struct {
|
||||
Reason *string
|
||||
ExpiresAt pgtype.Timestamp
|
||||
BannedBy *string
|
||||
Pardoned *bool
|
||||
Pardoned bool
|
||||
PardonedBy *string
|
||||
}
|
||||
|
||||
@@ -946,8 +1103,8 @@ WHERE id = $1
|
||||
|
||||
type UpdateConfirmationCodeParams struct {
|
||||
ID int64
|
||||
Used *bool
|
||||
Deleted *bool
|
||||
Used bool
|
||||
Deleted bool
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateConfirmationCode(ctx context.Context, arg UpdateConfirmationCodeParams) error {
|
||||
@@ -1112,7 +1269,7 @@ WHERE id = $1
|
||||
|
||||
type UpdateUserParams struct {
|
||||
ID int64
|
||||
Verified *bool
|
||||
Verified bool
|
||||
Deleted *bool
|
||||
}
|
||||
|
||||
@@ -1129,7 +1286,7 @@ WHERE username = $1
|
||||
|
||||
type UpdateUserByUsernameParams struct {
|
||||
Username string
|
||||
Verified *bool
|
||||
Verified bool
|
||||
Deleted *bool
|
||||
}
|
||||
|
||||
@@ -1137,3 +1294,76 @@ func (q *Queries) UpdateUserByUsername(ctx context.Context, arg UpdateUserByUser
|
||||
_, err := q.db.Exec(ctx, updateUserByUsername, arg.Username, arg.Verified, arg.Deleted)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateWishByGuid = `-- name: UpdateWishByGuid :exec
|
||||
UPDATE wishes w
|
||||
SET
|
||||
name = COALESCE($1::text, w.name),
|
||||
description = COALESCE($2::text, w.description),
|
||||
picture_url = COALESCE($3::text, w.picture_url),
|
||||
stars = COALESCE($4::smallint, w.stars),
|
||||
fulfilled = COALESCE($5::boolean, w.fulfilled),
|
||||
fulfilled_date = COALESCE($6::timestamp, w.fulfilled_date),
|
||||
deleted = COALESCE($7::boolean, w.deleted)
|
||||
WHERE w.guid = ($8::text)::uuid
|
||||
`
|
||||
|
||||
type UpdateWishByGuidParams struct {
|
||||
Name string
|
||||
Description string
|
||||
PictureUrl string
|
||||
Stars int16
|
||||
Fulfilled bool
|
||||
FulfilledDate pgtype.Timestamp
|
||||
Deleted bool
|
||||
Guid string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateWishByGuid(ctx context.Context, arg UpdateWishByGuidParams) error {
|
||||
_, err := q.db.Exec(ctx, updateWishByGuid,
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.PictureUrl,
|
||||
arg.Stars,
|
||||
arg.Fulfilled,
|
||||
arg.FulfilledDate,
|
||||
arg.Deleted,
|
||||
arg.Guid,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateWishListByGuid = `-- name: UpdateWishListByGuid :exec
|
||||
UPDATE wish_lists wl
|
||||
SET
|
||||
hidden = COALESCE($1::boolean, wl.hidden),
|
||||
name = COALESCE($2::text, wl.name),
|
||||
icon_name = COALESCE($3::text, wl.icon_name),
|
||||
color = COALESCE($4::text, wl.color),
|
||||
color_grad = COALESCE($5::text, wl.color_grad),
|
||||
deleted = COALESCE($6::boolean, wl.deleted)
|
||||
WHERE wl.guid = ($7::text)::uuid
|
||||
`
|
||||
|
||||
type UpdateWishListByGuidParams struct {
|
||||
Hidden bool
|
||||
Name string
|
||||
IconName string
|
||||
Color string
|
||||
ColorGrad string
|
||||
Deleted bool
|
||||
Guid string
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateWishListByGuid(ctx context.Context, arg UpdateWishListByGuidParams) error {
|
||||
_, err := q.db.Exec(ctx, updateWishListByGuid,
|
||||
arg.Hidden,
|
||||
arg.Name,
|
||||
arg.IconName,
|
||||
arg.Color,
|
||||
arg.ColorGrad,
|
||||
arg.Deleted,
|
||||
arg.Guid,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -414,7 +414,7 @@ func (a *authServiceImpl) RegistrationComplete(cinfo dto.ClientInfo, request mod
|
||||
|
||||
err = db.TXQueries.UpdateConfirmationCode(db.CTX, database.UpdateConfirmationCodeParams{
|
||||
ID: confirmationCode.ID,
|
||||
Used: utils.NewPointer(true),
|
||||
Used: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -428,7 +428,7 @@ func (a *authServiceImpl) RegistrationComplete(cinfo dto.ClientInfo, request mod
|
||||
|
||||
err = db.TXQueries.UpdateUser(db.CTX, database.UpdateUserParams{
|
||||
ID: user.ID,
|
||||
Verified: utils.NewPointer(true),
|
||||
Verified: true,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
@@ -864,7 +864,7 @@ func (a *authServiceImpl) PasswordResetComplete(request models.PasswordResetComp
|
||||
|
||||
if err = db.TXQueries.UpdateConfirmationCode(db.CTX, database.UpdateConfirmationCodeParams{
|
||||
ID: resetCode.ID,
|
||||
Used: utils.NewPointer(true),
|
||||
Used: true,
|
||||
}); err != nil {
|
||||
a.log.Error(
|
||||
"Failed to invalidate password reset code upon use",
|
||||
|
||||
Reference in New Issue
Block a user