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:
2025-08-04 20:26:51 +03:00
parent 3bcd8af100
commit b1125d3f6a
5 changed files with 400 additions and 53 deletions

View File

@@ -32,7 +32,9 @@ WHERE id = $1;
;-- name: UpdateUserByUsername :exec
UPDATE users
SET verified = $2, deleted = $3
SET
verified = COALESCE($2, verified),
deleted = COALESCE($3, deleted)
WHERE username = $1;
;-- name: DeleteUser :exec
@@ -56,29 +58,6 @@ SELECT users.* FROM users
JOIN login_informations linfo ON linfo.user_id = users.id
WHERE linfo.email = @email::text;
;-- name: CheckUserRegistrationAvailability :one
-- SELECT
-- COUNT(users.username = @username::text) > 0 AS username_busy,
-- COUNT(linfo.email = @email::text) > 0 AS email_busy
-- FROM users
-- JOIN login_informations AS linfo on linfo.user_id = users.id
-- WHERE
-- (
-- users.username = @username::text OR
-- linfo.email = @email::text
-- )
-- AND
-- (
-- users.verified IS TRUE OR
-- COUNT(
-- SELECT confirmation_codes as codes
-- JOIN users on users.id = codes.user_id
-- WHERE codes.code_type = 0 AND
-- codes.deleted IS FALSE AND
-- codes.expires_at < CURRENT_TIMESTAMP
-- ) = 0;
-- )
;-- name: GetValidUserByLoginCredentials :one
SELECT
users.*,
@@ -86,12 +65,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(@password::text, linfo.password_hash); -- Password hash matches
;-- name: CheckUserRegistrationAvailability :one
@@ -320,7 +304,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)
)
);
@@ -371,3 +357,82 @@ JOIN users ON users.id = profiles.user_id
WHERE users.username = $1;
--: }}}
--: Wish List Object {{{
;-- 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 = @username::text),
@hidden::boolean,
@name::text,
@icon_name::text,
@color::text,
@color_grad::boolean
)
RETURNING *;
;-- name: UpdateWishListByGuid :exec
UPDATE wish_lists wl
SET
hidden = COALESCE(@hidden::boolean, wl.hidden),
name = COALESCE(@name::text, wl.name),
icon_name = COALESCE(@icon_name::text, wl.icon_name),
color = COALESCE(@color::text, wl.color),
color_grad = COALESCE(@color_grad::text, wl.color_grad),
deleted = COALESCE(@deleted::boolean, wl.deleted)
WHERE wl.guid = (@guid::text)::uuid;
;-- name: GetWishlistByGuid :one
SELECT * FROM wish_lists wl
WHERE wl.guid = (@guid::text)::uuid;
-- name: GetWishlistsByUsernameWithPrivacy :many
SELECT
wl.*,
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 = @username::text AND
(
u.username = @requester::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)
))
);
--: }}}
--: Wish Object {{{
;-- name: UpdateWishByGuid :exec
UPDATE wishes w
SET
name = COALESCE(@name::text, w.name),
description = COALESCE(@description::text, w.description),
picture_url = COALESCE(@picture_url::text, w.picture_url),
stars = COALESCE(@stars::smallint, w.stars),
fulfilled = COALESCE(@fulfilled::boolean, w.fulfilled),
fulfilled_date = COALESCE(@fulfilled_date::timestamp, w.fulfilled_date),
deleted = COALESCE(@deleted::boolean, w.deleted)
WHERE w.guid = (@guid::text)::uuid;
--: }}}

View File

@@ -22,7 +22,7 @@ CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE TABLE IF NOT EXISTS "users" (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(20) UNIQUE NOT NULL,
verified BOOLEAN DEFAULT FALSE,
verified BOOLEAN NOT NULL DEFAULT FALSE,
registration_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
role INTEGER NOT NULL DEFAULT 1, -- enum user
deleted BOOLEAN DEFAULT FALSE
@@ -35,7 +35,7 @@ CREATE TABLE IF NOT EXISTS "banned_users" (
reason VARCHAR(512),
expires_at TIMESTAMP,
banned_by VARCHAR(20) DEFAULT 'system',
pardoned BOOLEAN DEFAULT FALSE,
pardoned BOOLEAN NOT NULL DEFAULT FALSE,
pardoned_by VARCHAR(20)
);
@@ -55,14 +55,14 @@ CREATE TABLE IF NOT EXISTS "confirmation_codes" (
code_type INTEGER NOT NULL CHECK (code_type IN (0, 1)),
code_hash VARCHAR(512) NOT NULL,
expires_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP + INTERVAL '10 minutes',
used BOOLEAN DEFAULT FALSE,
deleted BOOLEAN DEFAULT FALSE
used BOOLEAN NOT NULL DEFAULT FALSE,
deleted BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS "sessions" (
id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
guid UUID NOT NULL DEFAULT gen_random_uuid(),
guid UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(),
name VARCHAR(175),
platform VARCHAR(175),
latest_ip VARCHAR(16),
@@ -77,7 +77,7 @@ CREATE TABLE IF NOT EXISTS "profiles" (
user_id BIGINT UNIQUE NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(75) NOT NULL,
bio VARCHAR(512) NOT NULL DEFAULT '',
avatar_url VARCHAR(255) NOT NULL DEFAULT '',
avatar_url VARCHAR(512) NOT NULL DEFAULT '',
birthday TIMESTAMP,
color VARCHAR(7) NOT NULL DEFAULT '#254333',
color_grad VARCHAR(7) NOT NULL DEFAULT '#691E4D'
@@ -93,4 +93,30 @@ CREATE TABLE IF NOT EXISTS "profile_settings" (
hide_dates BOOLEAN NOT NULL DEFAULT FALSE,
captcha BOOLEAN NOT NULL DEFAULT FALSE,
followers_only_interaction BOOLEAN NOT NULL DEFAULT FALSE
)
);
CREATE TABLE IF NOT EXISTS "wish_lists" (
id BIGSERIAL PRIMARY KEY,
guid UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(),
profile_id BIGINT UNIQUE NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
hidden BOOLEAN NOT NULL DEFAULT FALSE,
name VARCHAR(32) NOT NULL DEFAULT 'Wishes',
icon_name VARCHAR(64),
color VARCHAR(7),
color_grad VARCHAR(7),
deleted BOOLEAN NOT NULL DEFAULT FALSE
);
CREATE TABLE IF NOT EXISTS "wishes" (
id BIGSERIAL PRIMARY KEY,
guid UUID UNIQUE NOT NULL DEFAULT gen_random_uuid(),
wish_list_id BIGINT UNIQUE NOT NULL REFERENCES wish_lists(id) ON DELETE CASCADE,
name VARCHAR(32) NOT NULL DEFAULT 'New wish',
description VARCHAR(1000) NOT NULL DEFAULT '',
picture_url VARCHAR(512) NOT NULL DEFAULT '',
stars SMALLINT NOT NULL DEFAULT 3 CHECK (stars BETWEEN 1 AND 5),
creation_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
fulfilled BOOLEAN NOT NULL DEFAULT FALSE,
fulfilled_date TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted BOOLEAN NOT NULL DEFAULT FALSE
);