UPDATE: Fix bug
All checks were successful
Build and Release / release (push) Successful in 1m27s

This commit is contained in:
2026-04-10 15:52:09 +07:00
parent 0896fd587e
commit af76d2a26a
22 changed files with 586 additions and 129 deletions

View File

@@ -2,6 +2,7 @@ CREATE TABLE IF NOT EXISTS user_verifications (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
verify_type SMALLINT NOT NULL,
content TEXT,
is_deleted BOOLEAN NOT NULL DEFAULT false,
status SMALLINT NOT NULL DEFAULT 1,
reviewed_by UUID REFERENCES users(id),

View File

@@ -16,23 +16,23 @@ SELECT id, name, is_deleted, created_at, updated_at
FROM roles
WHERE id = ANY($1::uuid[]) AND is_deleted = false;
-- name: AddUserRole :exec
-- name: CreateUserRole :exec
INSERT INTO user_roles (user_id, role_id)
SELECT $1, unnest($2::uuid[])
ON CONFLICT DO NOTHING;
-- name: RemoveUserRole :exec
-- name: DeleteUserRole :exec
DELETE FROM user_roles ur
USING roles r
WHERE ur.role_id = r.id
AND ur.user_id = $1
AND r.name = $2;
-- name: RemoveAllRolesFromUser :exec
-- name: BulkDeleteRolesFromUser :exec
DELETE FROM user_roles
WHERE user_id = $1;
-- name: RemoveAllUsersFromRole :exec
-- name: BulkDeleteUsersFromRole :exec
DELETE FROM user_roles
WHERE role_id = $1;

View File

@@ -26,14 +26,14 @@ RETURNING *;
-- name: UpdateUserProfile :one
UPDATE user_profiles
SET
display_name = $1,
full_name = $2,
avatar_url = $3,
bio = $4,
location = $5,
website = $6,
country_code = $7,
phone = $8,
display_name = COALESCE($1, display_name),
full_name = COALESCE($2, full_name),
avatar_url = COALESCE($3, avatar_url),
bio = COALESCE($4, bio),
location = COALESCE($5, location),
website = COALESCE($6, website),
country_code = COALESCE($7, country_code),
phone = COALESCE($8, phone),
updated_at = now()
WHERE user_id = $9
RETURNING *;

View File

@@ -1,8 +1,8 @@
-- name: CreateUserVerification :one
INSERT INTO user_verifications (
user_id, verify_type
user_id, verify_type, content
) VALUES (
$1, $2
$1, $2, $3
)
RETURNING *;
@@ -11,6 +11,7 @@ SELECT
uv.id,
uv.user_id,
uv.verify_type,
uv.content,
uv.is_deleted,
uv.status,
uv.reviewed_by,
@@ -43,6 +44,7 @@ SELECT
uv.id,
uv.user_id,
uv.verify_type,
uv.content,
uv.is_deleted,
uv.status,
uv.reviewed_by,
@@ -91,14 +93,86 @@ WHERE verification_id = $1 AND media_id = $2;
-- name: CreateVerificationMedia :exec
INSERT INTO verification_medias (
verification_id, media_id
) VALUES (
$1, $2
);
)
SELECT $1, unnest($2::uuid[])
ON CONFLICT DO NOTHING;
-- name: DeleteAllVerificationMedias :exec
-- name: BulkDeleteVerificationByMediaId :exec
DELETE FROM verification_medias
WHERE verification_id = $1;
WHERE media_id = $1;
-- name: BulkDeleteVerificationMedias :exec
-- name: DeleteVerificationMedias :exec
DELETE FROM verification_medias
WHERE verification_id = $1 AND media_id = ANY($2::uuid[]);
WHERE verification_id = $1 AND media_id = ANY($2::uuid[]);
-- name: SearchUserVerifications :many
SELECT
uv.id,
uv.user_id,
uv.verify_type,
uv.content,
uv.is_deleted,
uv.status,
uv.reviewed_by,
uv.reviewed_at,
uv.created_at,
(
SELECT COALESCE(
json_agg(
json_build_object(
'id', m.id,
'storage_key', m.storage_key,
'original_name', m.original_name,
'mime_type', m.mime_type,
'size', m.size,
'file_metadata', m.file_metadata,
'created_at', m.created_at
)
),
'[]'
)::json
FROM verification_medias vm
JOIN medias m ON vm.media_id = m.id
WHERE vm.verification_id = uv.id
) AS medias
FROM user_verifications uv
WHERE
uv.is_deleted = false
AND (sqlc.narg('user_ids')::uuid[] IS NULL OR uv.user_id = ANY(sqlc.narg('user_ids')::uuid[]))
AND (sqlc.narg('verify_types')::text[] IS NULL OR uv.verify_type = ANY(sqlc.narg('verify_types')::text[]))
AND (sqlc.narg('statuses')::text[] IS NULL OR uv.status = ANY(sqlc.narg('statuses')::text[]))
AND (sqlc.narg('reviewed_by')::uuid IS NULL OR uv.reviewed_by = sqlc.narg('reviewed_by')::uuid)
AND (sqlc.narg('created_after')::timestamptz IS NULL OR uv.created_at >= sqlc.narg('created_after')::timestamptz)
AND (sqlc.narg('created_before')::timestamptz IS NULL OR uv.created_at <= sqlc.narg('created_before')::timestamptz)
AND (
sqlc.narg('search_text')::text IS NULL OR
uv.id::text ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
uv.content::text ILIKE '%' || sqlc.narg('search_text')::text || '%'
)
ORDER BY
CASE WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'asc' THEN uv.created_at END ASC,
CASE WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'desc' THEN uv.created_at END DESC,
CASE WHEN sqlc.narg('sort') = 'reviewed_at' AND sqlc.narg('order') = 'asc' THEN uv.reviewed_at END ASC,
CASE WHEN sqlc.narg('sort') = 'reviewed_at' AND sqlc.narg('order') = 'desc' THEN uv.reviewed_at END DESC,
CASE WHEN sqlc.narg('sort') = 'status' AND sqlc.narg('order') = 'asc' THEN uv.status END ASC,
CASE WHEN sqlc.narg('sort') = 'status' AND sqlc.narg('order') = 'desc' THEN uv.status END DESC,
CASE WHEN sqlc.narg('sort') IS NULL THEN uv.created_at END DESC
LIMIT sqlc.arg('limit')
OFFSET sqlc.arg('offset');
-- name: CountUserVerifications :one
SELECT count(*)
FROM user_verifications uv
WHERE
uv.is_deleted = false
AND (sqlc.narg('user_ids')::uuid[] IS NULL OR uv.user_id = ANY(sqlc.narg('user_ids')::uuid[]))
AND (sqlc.narg('verify_types')::text[] IS NULL OR uv.verify_type = ANY(sqlc.narg('verify_types')::text[]))
AND (sqlc.narg('statuses')::text[] IS NULL OR uv.status = ANY(sqlc.narg('statuses')::text[]))
AND (sqlc.narg('reviewed_by')::uuid IS NULL OR uv.reviewed_by = sqlc.narg('reviewed_by')::uuid)
AND (sqlc.narg('created_after')::timestamptz IS NULL OR uv.created_at >= sqlc.narg('created_after')::timestamptz)
AND (sqlc.narg('created_before')::timestamptz IS NULL OR uv.created_at <= sqlc.narg('created_before')::timestamptz)
AND (
sqlc.narg('search_text')::text IS NULL OR
uv.id::text ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
uv.content::text ILIKE '%' || sqlc.narg('search_text')::text || '%'
);

View File

@@ -55,6 +55,7 @@ CREATE TABLE IF NOT EXISTS user_verifications (
id UUID PRIMARY KEY DEFAULT uuidv7(),
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
verify_type SMALLINT NOT NULL,
content TEXT,
is_deleted BOOLEAN NOT NULL DEFAULT false,
status SMALLINT NOT NULL DEFAULT 1,
reviewed_by UUID REFERENCES users(id),

View File

@@ -3,16 +3,15 @@ package request
import "time"
type UpdateProfileDto struct {
DisplayName string `json:"display_name" validate:"omitempty,min=2,max=50"`
FullName string `json:"full_name" validate:"omitempty,min=2,max=100"`
AvatarUrl string `json:"avatar_url" validate:"omitempty,url,image_url"`
Bio string `json:"bio" validate:"omitempty,max=255"`
Location string `json:"location" validate:"omitempty,max=100"`
Website string `json:"website" validate:"omitempty,url"`
CountryCode string `json:"country_code" validate:"omitempty,len=2"`
Phone string `json:"phone" validate:"omitempty,min=8,max=20"`
DisplayName *string `json:"display_name" validate:"omitempty,min=2,max=50"`
FullName *string `json:"full_name" validate:"omitempty,min=2,max=100"`
AvatarUrl *string `json:"avatar_url" validate:"omitempty,url,image_url"`
Bio *string `json:"bio" validate:"omitempty,max=255"`
Location *string `json:"location" validate:"omitempty,max=100"`
Website *string `json:"website" validate:"omitempty,url"`
CountryCode *string `json:"country_code" validate:"omitempty,len=2"`
Phone *string `json:"phone" validate:"omitempty,min=8,max=20"`
}
type ChangePasswordDto struct {
OldPassword string `json:"old_password" validate:"required,min=8,max=64"`
NewPassword string `json:"new_password" validate:"required,min=8,max=64,nefield=OldPassword"`

View File

@@ -0,0 +1,25 @@
package request
import "time"
type SearchUserVerificationDto struct {
PaginationDto
Sort string `json:"sort" query:"sort" validate:"omitempty,oneof=id created_at reviewed_at status"`
Search string `json:"search" query:"search" validate:"omitempty,min=2,max=200"`
UserIDs []string `json:"user_ids" query:"user_ids" validate:"omitempty,dive,uuid"`
VerifyTypes []string `json:"verify_types" query:"verify_types" validate:"omitempty,dive,ascii"`
Statuses []string `json:"statuses" query:"statuses" validate:"omitempty,dive,ascii"`
ReviewedBy *string `json:"reviewed_by" query:"reviewed_by" validate:"omitempty,uuid"`
CreatedAfter *time.Time `json:"created_after" query:"created_after" validate:"omitempty"`
CreatedBefore *time.Time `json:"created_before" query:"created_before" validate:"omitempty,gtfield=CreatedAfter"`
}
type CreateUserVerificationDto struct {
VerifyType string `json:"verify_type" validate:"required,oneof=ID_CARD EDUCATION EXPERT OTHER"`
Content string `json:"content" validate:"required"`
MediaIDs []string `json:"media_ids" validate:"omitempty,dive,uuid"`
}
type UpdateVerificationStatusDto struct {
Status string `json:"status" validate:"required,oneof=PENDING APPROVED REJECTED"`
}

View File

@@ -20,3 +20,13 @@ type MediaResponse struct {
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
}
type MediaSimpleResponse struct {
ID string `json:"id"`
StorageKey string `json:"storage_key"`
OriginalName string `json:"original_name"`
MimeType string `json:"mime_type"`
Size int64 `json:"size"`
FileMetadata []byte `json:"file_metadata"`
CreatedAt time.Time `json:"created_at"`
}

View File

@@ -0,0 +1,15 @@
package response
import "time"
type UserVerificationResponse struct {
ID string `json:"id"`
UserID string `json:"user_id"`
VerifyType string `json:"verify_type"`
Content string `json:"content"`
Status string `json:"status"`
ReviewedBy *string `json:"reviewed_by"`
ReviewedAt *time.Time `json:"reviewed_at"`
CreatedAt time.Time `json:"created_at"`
Medias []*MediaSimpleResponse `json:"media"`
}

View File

@@ -64,6 +64,7 @@ type UserVerification struct {
ID pgtype.UUID `json:"id"`
UserID pgtype.UUID `json:"user_id"`
VerifyType int16 `json:"verify_type"`
Content pgtype.Text `json:"content"`
IsDeleted bool `json:"is_deleted"`
Status int16 `json:"status"`
ReviewedBy pgtype.UUID `json:"reviewed_by"`

View File

@@ -11,19 +11,23 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const addUserRole = `-- name: AddUserRole :exec
INSERT INTO user_roles (user_id, role_id)
SELECT $1, unnest($2::uuid[])
ON CONFLICT DO NOTHING
const bulkDeleteRolesFromUser = `-- name: BulkDeleteRolesFromUser :exec
DELETE FROM user_roles
WHERE user_id = $1
`
type AddUserRoleParams struct {
UserID pgtype.UUID `json:"user_id"`
Column2 []pgtype.UUID `json:"column_2"`
func (q *Queries) BulkDeleteRolesFromUser(ctx context.Context, userID pgtype.UUID) error {
_, err := q.db.Exec(ctx, bulkDeleteRolesFromUser, userID)
return err
}
func (q *Queries) AddUserRole(ctx context.Context, arg AddUserRoleParams) error {
_, err := q.db.Exec(ctx, addUserRole, arg.UserID, arg.Column2)
const bulkDeleteUsersFromRole = `-- name: BulkDeleteUsersFromRole :exec
DELETE FROM user_roles
WHERE role_id = $1
`
func (q *Queries) BulkDeleteUsersFromRole(ctx context.Context, roleID pgtype.UUID) error {
_, err := q.db.Exec(ctx, bulkDeleteUsersFromRole, roleID)
return err
}
@@ -46,6 +50,22 @@ func (q *Queries) CreateRole(ctx context.Context, name string) (Role, error) {
return i, err
}
const createUserRole = `-- name: CreateUserRole :exec
INSERT INTO user_roles (user_id, role_id)
SELECT $1, unnest($2::uuid[])
ON CONFLICT DO NOTHING
`
type CreateUserRoleParams struct {
UserID pgtype.UUID `json:"user_id"`
Column2 []pgtype.UUID `json:"column_2"`
}
func (q *Queries) CreateUserRole(ctx context.Context, arg CreateUserRoleParams) error {
_, err := q.db.Exec(ctx, createUserRole, arg.UserID, arg.Column2)
return err
}
const deleteRole = `-- name: DeleteRole :exec
UPDATE roles
SET
@@ -59,6 +79,24 @@ func (q *Queries) DeleteRole(ctx context.Context, id pgtype.UUID) error {
return err
}
const deleteUserRole = `-- name: DeleteUserRole :exec
DELETE FROM user_roles ur
USING roles r
WHERE ur.role_id = r.id
AND ur.user_id = $1
AND r.name = $2
`
type DeleteUserRoleParams struct {
UserID pgtype.UUID `json:"user_id"`
Name string `json:"name"`
}
func (q *Queries) DeleteUserRole(ctx context.Context, arg DeleteUserRoleParams) error {
_, err := q.db.Exec(ctx, deleteUserRole, arg.UserID, arg.Name)
return err
}
const getRoleByID = `-- name: GetRoleByID :one
SELECT id, name, is_deleted, created_at, updated_at FROM roles
WHERE id = $1 AND is_deleted = false
@@ -159,44 +197,6 @@ func (q *Queries) GetRolesByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]
return items, nil
}
const removeAllRolesFromUser = `-- name: RemoveAllRolesFromUser :exec
DELETE FROM user_roles
WHERE user_id = $1
`
func (q *Queries) RemoveAllRolesFromUser(ctx context.Context, userID pgtype.UUID) error {
_, err := q.db.Exec(ctx, removeAllRolesFromUser, userID)
return err
}
const removeAllUsersFromRole = `-- name: RemoveAllUsersFromRole :exec
DELETE FROM user_roles
WHERE role_id = $1
`
func (q *Queries) RemoveAllUsersFromRole(ctx context.Context, roleID pgtype.UUID) error {
_, err := q.db.Exec(ctx, removeAllUsersFromRole, roleID)
return err
}
const removeUserRole = `-- name: RemoveUserRole :exec
DELETE FROM user_roles ur
USING roles r
WHERE ur.role_id = r.id
AND ur.user_id = $1
AND r.name = $2
`
type RemoveUserRoleParams struct {
UserID pgtype.UUID `json:"user_id"`
Name string `json:"name"`
}
func (q *Queries) RemoveUserRole(ctx context.Context, arg RemoveUserRoleParams) error {
_, err := q.db.Exec(ctx, removeUserRole, arg.UserID, arg.Name)
return err
}
const restoreRole = `-- name: RestoreRole :exec
UPDATE roles
SET

View File

@@ -542,14 +542,14 @@ func (q *Queries) UpdateUserPassword(ctx context.Context, arg UpdateUserPassword
const updateUserProfile = `-- name: UpdateUserProfile :one
UPDATE user_profiles
SET
display_name = $1,
full_name = $2,
avatar_url = $3,
bio = $4,
location = $5,
website = $6,
country_code = $7,
phone = $8,
display_name = COALESCE($1, display_name),
full_name = COALESCE($2, full_name),
avatar_url = COALESCE($3, avatar_url),
bio = COALESCE($4, bio),
location = COALESCE($5, location),
website = COALESCE($6, website),
country_code = COALESCE($7, country_code),
phone = COALESCE($8, phone),
updated_at = now()
WHERE user_id = $9
RETURNING user_id, display_name, full_name, avatar_url, bio, location, website, country_code, phone, created_at, updated_at

View File

@@ -11,27 +11,82 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const bulkDeleteVerificationByMediaId = `-- name: BulkDeleteVerificationByMediaId :exec
DELETE FROM verification_medias
WHERE media_id = $1
`
func (q *Queries) BulkDeleteVerificationByMediaId(ctx context.Context, mediaID pgtype.UUID) error {
_, err := q.db.Exec(ctx, bulkDeleteVerificationByMediaId, mediaID)
return err
}
const countUserVerifications = `-- name: CountUserVerifications :one
SELECT count(*)
FROM user_verifications uv
WHERE
uv.is_deleted = false
AND ($1::uuid[] IS NULL OR uv.user_id = ANY($1::uuid[]))
AND ($2::text[] IS NULL OR uv.verify_type = ANY($2::text[]))
AND ($3::text[] IS NULL OR uv.status = ANY($3::text[]))
AND ($4::uuid IS NULL OR uv.reviewed_by = $4::uuid)
AND ($5::timestamptz IS NULL OR uv.created_at >= $5::timestamptz)
AND ($6::timestamptz IS NULL OR uv.created_at <= $6::timestamptz)
AND (
$7::text IS NULL OR
uv.id::text ILIKE '%' || $7::text || '%' OR
uv.content::text ILIKE '%' || $7::text || '%'
)
`
type CountUserVerificationsParams struct {
UserIds []pgtype.UUID `json:"user_ids"`
VerifyTypes []string `json:"verify_types"`
Statuses []string `json:"statuses"`
ReviewedBy pgtype.UUID `json:"reviewed_by"`
CreatedAfter pgtype.Timestamptz `json:"created_after"`
CreatedBefore pgtype.Timestamptz `json:"created_before"`
SearchText pgtype.Text `json:"search_text"`
}
func (q *Queries) CountUserVerifications(ctx context.Context, arg CountUserVerificationsParams) (int64, error) {
row := q.db.QueryRow(ctx, countUserVerifications,
arg.UserIds,
arg.VerifyTypes,
arg.Statuses,
arg.ReviewedBy,
arg.CreatedAfter,
arg.CreatedBefore,
arg.SearchText,
)
var count int64
err := row.Scan(&count)
return count, err
}
const createUserVerification = `-- name: CreateUserVerification :one
INSERT INTO user_verifications (
user_id, verify_type
user_id, verify_type, content
) VALUES (
$1, $2
$1, $2, $3
)
RETURNING id, user_id, verify_type, is_deleted, status, reviewed_by, reviewed_at, created_at
RETURNING id, user_id, verify_type, content, is_deleted, status, reviewed_by, reviewed_at, created_at
`
type CreateUserVerificationParams struct {
UserID pgtype.UUID `json:"user_id"`
VerifyType int16 `json:"verify_type"`
Content pgtype.Text `json:"content"`
}
func (q *Queries) CreateUserVerification(ctx context.Context, arg CreateUserVerificationParams) (UserVerification, error) {
row := q.db.QueryRow(ctx, createUserVerification, arg.UserID, arg.VerifyType)
row := q.db.QueryRow(ctx, createUserVerification, arg.UserID, arg.VerifyType, arg.Content)
var i UserVerification
err := row.Scan(
&i.ID,
&i.UserID,
&i.VerifyType,
&i.Content,
&i.IsDeleted,
&i.Status,
&i.ReviewedBy,
@@ -44,18 +99,18 @@ func (q *Queries) CreateUserVerification(ctx context.Context, arg CreateUserVeri
const createVerificationMedia = `-- name: CreateVerificationMedia :exec
INSERT INTO verification_medias (
verification_id, media_id
) VALUES (
$1, $2
)
SELECT $1, unnest($2::uuid[])
ON CONFLICT DO NOTHING
`
type CreateVerificationMediaParams struct {
VerificationID pgtype.UUID `json:"verification_id"`
MediaID pgtype.UUID `json:"media_id"`
VerificationID pgtype.UUID `json:"verification_id"`
Column2 []pgtype.UUID `json:"column_2"`
}
func (q *Queries) CreateVerificationMedia(ctx context.Context, arg CreateVerificationMediaParams) error {
_, err := q.db.Exec(ctx, createVerificationMedia, arg.VerificationID, arg.MediaID)
_, err := q.db.Exec(ctx, createVerificationMedia, arg.VerificationID, arg.Column2)
return err
}
@@ -85,11 +140,27 @@ func (q *Queries) DeleteVerificationMedia(ctx context.Context, arg DeleteVerific
return err
}
const deleteVerificationMedias = `-- name: DeleteVerificationMedias :exec
DELETE FROM verification_medias
WHERE verification_id = $1 AND media_id = ANY($2::uuid[])
`
type DeleteVerificationMediasParams struct {
VerificationID pgtype.UUID `json:"verification_id"`
Column2 []pgtype.UUID `json:"column_2"`
}
func (q *Queries) DeleteVerificationMedias(ctx context.Context, arg DeleteVerificationMediasParams) error {
_, err := q.db.Exec(ctx, deleteVerificationMedias, arg.VerificationID, arg.Column2)
return err
}
const getUserVerificationByID = `-- name: GetUserVerificationByID :one
SELECT
uv.id,
uv.user_id,
uv.verify_type,
uv.content,
uv.is_deleted,
uv.status,
uv.reviewed_by,
@@ -122,6 +193,7 @@ type GetUserVerificationByIDRow struct {
ID pgtype.UUID `json:"id"`
UserID pgtype.UUID `json:"user_id"`
VerifyType int16 `json:"verify_type"`
Content pgtype.Text `json:"content"`
IsDeleted bool `json:"is_deleted"`
Status int16 `json:"status"`
ReviewedBy pgtype.UUID `json:"reviewed_by"`
@@ -137,6 +209,7 @@ func (q *Queries) GetUserVerificationByID(ctx context.Context, id pgtype.UUID) (
&i.ID,
&i.UserID,
&i.VerifyType,
&i.Content,
&i.IsDeleted,
&i.Status,
&i.ReviewedBy,
@@ -152,6 +225,7 @@ SELECT
uv.id,
uv.user_id,
uv.verify_type,
uv.content,
uv.is_deleted,
uv.status,
uv.reviewed_by,
@@ -185,6 +259,7 @@ type GetUserVerificationsRow struct {
ID pgtype.UUID `json:"id"`
UserID pgtype.UUID `json:"user_id"`
VerifyType int16 `json:"verify_type"`
Content pgtype.Text `json:"content"`
IsDeleted bool `json:"is_deleted"`
Status int16 `json:"status"`
ReviewedBy pgtype.UUID `json:"reviewed_by"`
@@ -206,6 +281,133 @@ func (q *Queries) GetUserVerifications(ctx context.Context, userID pgtype.UUID)
&i.ID,
&i.UserID,
&i.VerifyType,
&i.Content,
&i.IsDeleted,
&i.Status,
&i.ReviewedBy,
&i.ReviewedAt,
&i.CreatedAt,
&i.Medias,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}
const searchUserVerifications = `-- name: SearchUserVerifications :many
SELECT
uv.id,
uv.user_id,
uv.verify_type,
uv.content,
uv.is_deleted,
uv.status,
uv.reviewed_by,
uv.reviewed_at,
uv.created_at,
(
SELECT COALESCE(
json_agg(
json_build_object(
'id', m.id,
'storage_key', m.storage_key,
'original_name', m.original_name,
'mime_type', m.mime_type,
'size', m.size,
'file_metadata', m.file_metadata,
'created_at', m.created_at
)
),
'[]'
)::json
FROM verification_medias vm
JOIN medias m ON vm.media_id = m.id
WHERE vm.verification_id = uv.id
) AS medias
FROM user_verifications uv
WHERE
uv.is_deleted = false
AND ($1::uuid[] IS NULL OR uv.user_id = ANY($1::uuid[]))
AND ($2::text[] IS NULL OR uv.verify_type = ANY($2::text[]))
AND ($3::text[] IS NULL OR uv.status = ANY($3::text[]))
AND ($4::uuid IS NULL OR uv.reviewed_by = $4::uuid)
AND ($5::timestamptz IS NULL OR uv.created_at >= $5::timestamptz)
AND ($6::timestamptz IS NULL OR uv.created_at <= $6::timestamptz)
AND (
$7::text IS NULL OR
uv.id::text ILIKE '%' || $7::text || '%' OR
uv.content::text ILIKE '%' || $7::text || '%'
)
ORDER BY
CASE WHEN $8 = 'created_at' AND $9 = 'asc' THEN uv.created_at END ASC,
CASE WHEN $8 = 'created_at' AND $9 = 'desc' THEN uv.created_at END DESC,
CASE WHEN $8 = 'reviewed_at' AND $9 = 'asc' THEN uv.reviewed_at END ASC,
CASE WHEN $8 = 'reviewed_at' AND $9 = 'desc' THEN uv.reviewed_at END DESC,
CASE WHEN $8 = 'status' AND $9 = 'asc' THEN uv.status END ASC,
CASE WHEN $8 = 'status' AND $9 = 'desc' THEN uv.status END DESC,
CASE WHEN $8 IS NULL THEN uv.created_at END DESC
LIMIT $11
OFFSET $10
`
type SearchUserVerificationsParams struct {
UserIds []pgtype.UUID `json:"user_ids"`
VerifyTypes []string `json:"verify_types"`
Statuses []string `json:"statuses"`
ReviewedBy pgtype.UUID `json:"reviewed_by"`
CreatedAfter pgtype.Timestamptz `json:"created_after"`
CreatedBefore pgtype.Timestamptz `json:"created_before"`
SearchText pgtype.Text `json:"search_text"`
Sort interface{} `json:"sort"`
Order interface{} `json:"order"`
Offset int32 `json:"offset"`
Limit int32 `json:"limit"`
}
type SearchUserVerificationsRow struct {
ID pgtype.UUID `json:"id"`
UserID pgtype.UUID `json:"user_id"`
VerifyType int16 `json:"verify_type"`
Content pgtype.Text `json:"content"`
IsDeleted bool `json:"is_deleted"`
Status int16 `json:"status"`
ReviewedBy pgtype.UUID `json:"reviewed_by"`
ReviewedAt pgtype.Timestamptz `json:"reviewed_at"`
CreatedAt pgtype.Timestamptz `json:"created_at"`
Medias []byte `json:"medias"`
}
func (q *Queries) SearchUserVerifications(ctx context.Context, arg SearchUserVerificationsParams) ([]SearchUserVerificationsRow, error) {
rows, err := q.db.Query(ctx, searchUserVerifications,
arg.UserIds,
arg.VerifyTypes,
arg.Statuses,
arg.ReviewedBy,
arg.CreatedAfter,
arg.CreatedBefore,
arg.SearchText,
arg.Sort,
arg.Order,
arg.Offset,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
items := []SearchUserVerificationsRow{}
for rows.Next() {
var i SearchUserVerificationsRow
if err := rows.Scan(
&i.ID,
&i.UserID,
&i.VerifyType,
&i.Content,
&i.IsDeleted,
&i.Status,
&i.ReviewedBy,

View File

@@ -16,6 +16,16 @@ type MediaEntity struct {
CreatedAt *time.Time `json:"created_at"`
UpdatedAt *time.Time `json:"updated_at"`
}
type MediaSimpleEntity struct {
ID string `json:"id"`
StorageKey string `json:"storage_key"`
OriginalName string `json:"original_name"`
MimeType string `json:"mime_type"`
Size int64 `json:"size"`
FileMetadata []byte `json:"file_metadata"`
CreatedAt time.Time `json:"created_at"`
}
type MediaStorageEntity struct {
ID string `json:"id"`

View File

@@ -0,0 +1,66 @@
package models
import (
"encoding/json"
"history-api/internal/dtos/response"
"history-api/pkg/constants"
"time"
)
type UserVerificationEntity struct {
ID string `json:"id"`
UserID string `json:"user_id"`
VerifyType string `json:"verify_type"`
Content string `json:"content"`
IsDeleted bool `json:"is_deleted"`
Status constants.VerifyType `json:"status"`
ReviewedBy *string `json:"reviewed_by"`
ReviewedAt *time.Time `json:"reviewed_at"`
CreatedAt time.Time `json:"created_at"`
Media []*MediaSimpleEntity `json:"media"`
}
func (u *UserVerificationEntity) ParseMedia(data []byte) error {
if len(data) == 0 {
u.Media = []*MediaSimpleEntity{}
return nil
}
return json.Unmarshal(data, &u.Media)
}
func (u *UserVerificationEntity) ToResponse() *response.UserVerificationResponse {
mediaResponses := make([]*response.MediaSimpleResponse, 0)
for _, m := range u.Media {
if m != nil {
mediaResponses = append(mediaResponses, &response.MediaSimpleResponse{
ID: m.ID,
StorageKey: m.StorageKey,
OriginalName: m.OriginalName,
MimeType: m.MimeType,
Size: m.Size,
FileMetadata: m.FileMetadata,
CreatedAt: m.CreatedAt,
})
}
}
res := &response.UserVerificationResponse{
ID: u.ID,
UserID: u.UserID,
VerifyType: u.VerifyType,
Content: u.Content,
Status: u.Status.String(),
CreatedAt: u.CreatedAt,
Medias: mediaResponses,
}
if u.ReviewedBy != nil {
res.ReviewedBy = u.ReviewedBy
}
if u.ReviewedAt != nil {
res.ReviewedAt = u.ReviewedAt
}
return res
}

View File

@@ -24,10 +24,10 @@ type RoleRepository interface {
Update(ctx context.Context, params sqlc.UpdateRoleParams) (*models.RoleEntity, error)
Delete(ctx context.Context, id pgtype.UUID) error
Restore(ctx context.Context, id pgtype.UUID) error
AddUserRole(ctx context.Context, params sqlc.AddUserRoleParams) error
RemoveUserRole(ctx context.Context, params sqlc.RemoveUserRoleParams) error
RemoveAllRolesFromUser(ctx context.Context, userId pgtype.UUID) error
RemoveAllUsersFromRole(ctx context.Context, roleId pgtype.UUID) error
CreateUserRole(ctx context.Context, params sqlc.CreateUserRoleParams) error
DeleteUserRole(ctx context.Context, params sqlc.DeleteUserRoleParams) error
BulkDeleteRolesFromUser(ctx context.Context, userId pgtype.UUID) error
BulkDeleteUsersFromRole(ctx context.Context, roleId pgtype.UUID) error
}
type roleRepository struct {
@@ -261,22 +261,22 @@ func (r *roleRepository) Restore(ctx context.Context, id pgtype.UUID) error {
return nil
}
func (r *roleRepository) AddUserRole(ctx context.Context, params sqlc.AddUserRoleParams) error {
err := r.q.AddUserRole(ctx, params)
func (r *roleRepository) CreateUserRole(ctx context.Context, params sqlc.CreateUserRoleParams) error {
err := r.q.CreateUserRole(ctx, params)
return err
}
func (r *roleRepository) RemoveUserRole(ctx context.Context, params sqlc.RemoveUserRoleParams) error {
err := r.q.RemoveUserRole(ctx, params)
func (r *roleRepository) DeleteUserRole(ctx context.Context, params sqlc.DeleteUserRoleParams) error {
err := r.q.DeleteUserRole(ctx, params)
return err
}
func (r *roleRepository) RemoveAllUsersFromRole(ctx context.Context, roleId pgtype.UUID) error {
err := r.q.RemoveAllUsersFromRole(ctx, roleId)
func (r *roleRepository) BulkDeleteUsersFromRole(ctx context.Context, roleId pgtype.UUID) error {
err := r.q.BulkDeleteUsersFromRole(ctx, roleId)
return err
}
func (r *roleRepository) RemoveAllRolesFromUser(ctx context.Context, roleId pgtype.UUID) error {
err := r.q.RemoveAllRolesFromUser(ctx, roleId)
func (r *roleRepository) BulkDeleteRolesFromUser(ctx context.Context, roleId pgtype.UUID) error {
err := r.q.BulkDeleteRolesFromUser(ctx, roleId)
return err
}

View File

@@ -0,0 +1,32 @@
package repositories
import (
"context"
"history-api/internal/gen/sqlc"
"history-api/internal/models"
"history-api/pkg/cache"
"github.com/jackc/pgx/v5/pgtype"
)
type VerificationRepository interface {
GetByID(ctx context.Context, id pgtype.UUID) (*models.UserVerificationEntity, error)
GetByUserID(ctx context.Context, id pgtype.UUID) ([]*models.UserVerificationEntity, error)
Count(ctx context.Context, params sqlc.CountUserVerificationsParams) (int64, error)
Search(ctx context.Context, params sqlc.SearchUserVerificationsParams) ([]*models.UserVerificationEntity, error)
Delete(ctx context.Context, id pgtype.UUID) error
CreateVerificationMedia(ctx context.Context, params sqlc.CreateVerificationMediaParams) error
DeleteVerificationMedia(ctx context.Context, params sqlc.DeleteVerificationMediasParams) error
}
type verificationRepository struct {
q *sqlc.Queries
c cache.Cache
}
// func NewVerificationRepository(db sqlc.DBTX, c cache.Cache) VerificationRepository {
// return &verificationRepository{
// q: sqlc.New(db),
// c: c,
// }
// }

View File

@@ -318,9 +318,9 @@ func (a *authService) Signup(ctx context.Context, dto *request.SignUpDto) (*resp
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
err = a.roleRepo.AddUserRole(
err = a.roleRepo.CreateUserRole(
ctx,
sqlc.AddUserRoleParams{
sqlc.CreateUserRoleParams{
UserID: userId,
Column2: []pgtype.UUID{roleId},
},
@@ -464,9 +464,9 @@ func (a *authService) SigninWithGoogle(ctx context.Context, dto *request.SigninW
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
err = a.roleRepo.AddUserRole(
err = a.roleRepo.CreateUserRole(
ctx,
sqlc.AddUserRoleParams{
sqlc.CreateUserRoleParams{
UserID: userId,
Column2: []pgtype.UUID{roleId},
},

View File

@@ -172,12 +172,12 @@ func (u *userService) ChangeRoleUser(ctx context.Context, claims *response.JWTCl
user.Roles = append(user.Roles, role.ToRoleSimple())
}
err = u.roleRepo.RemoveAllRolesFromUser(ctx, userId)
err = u.roleRepo.BulkDeleteRolesFromUser(ctx, userId)
if err != nil {
return nil, fiber.NewError(fiber.StatusInternalServerError, err.Error())
}
err = u.roleRepo.AddUserRole(ctx, sqlc.AddUserRoleParams{
err = u.roleRepo.CreateUserRole(ctx, sqlc.CreateUserRoleParams{
UserID: userId,
Column2: roleIdList,
})
@@ -239,14 +239,14 @@ func (u *userService) UpdateProfile(ctx context.Context, userId string, dto *req
newUser, err := u.userRepo.UpdateProfile(
ctx,
sqlc.UpdateUserProfileParams{
DisplayName: pgtype.Text{String: dto.DisplayName, Valid: len(dto.DisplayName) > 0},
FullName: pgtype.Text{String: dto.FullName, Valid: len(dto.FullName) > 0},
AvatarUrl: pgtype.Text{String: dto.AvatarUrl, Valid: len(dto.AvatarUrl) > 0},
Bio: pgtype.Text{String: dto.Bio, Valid: len(dto.Bio) > 0},
Location: pgtype.Text{String: dto.Location, Valid: len(dto.Location) > 0},
Website: pgtype.Text{String: dto.Website, Valid: len(dto.Website) > 0},
CountryCode: pgtype.Text{String: dto.CountryCode, Valid: len(dto.CountryCode) > 0},
Phone: pgtype.Text{String: dto.Phone, Valid: len(dto.Phone) > 0},
DisplayName: convert.PtrToText(dto.DisplayName),
FullName: convert.PtrToText(dto.FullName),
AvatarUrl: convert.PtrToText(dto.AvatarUrl),
Bio: convert.PtrToText(dto.Bio),
Location: convert.PtrToText(dto.Location),
Website: convert.PtrToText(dto.Website),
CountryCode: convert.PtrToText(dto.CountryCode),
Phone: convert.PtrToText(dto.Phone),
UserID: pgID,
},
)

View File

@@ -3,9 +3,11 @@ package constants
type VerifyType int16
const (
VerifyUnknown VerifyType = 0
VerifyIdCard VerifyType = 1
VerifyEducation VerifyType = 2
VerifyExpert VerifyType = 3
VerifyOther VerifyType = 4
)
func (t VerifyType) String() string {
@@ -16,20 +18,24 @@ func (t VerifyType) String() string {
return "EDUCATION"
case VerifyExpert:
return "EXPERT"
case VerifyOther:
return "OTHER"
default:
return "UNKNOWN"
}
}
func ParseVerifyType(v int16) VerifyType {
func ParseVerifyType(v string) VerifyType {
switch v {
case 1:
case "ID_CARD":
return VerifyIdCard
case 2:
case "EDUCATION":
return VerifyEducation
case 3:
case "EXPERT":
return VerifyExpert
case "OTHER":
return VerifyOther
default:
return 0
return VerifyUnknown
}
}

View File

@@ -43,3 +43,13 @@ func TimeToPtr(v pgtype.Timestamptz) *time.Time {
t := v.Time
return &t
}
func PtrToText(s *string) pgtype.Text {
if s == nil {
return pgtype.Text{Valid: false}
}
return pgtype.Text{
String: *s,
Valid: true,
}
}

View File

@@ -70,16 +70,21 @@ func SeedSuperAdmin(pool *pgxpool.Pool) error {
return err
}
role, err := q.GetRoleByName(ctx, constants.ADMIN.String())
adminRole, err := q.GetRoleByName(ctx, constants.ADMIN.String())
if err != nil {
return err
}
err = q.AddUserRole(
useRole, err := q.GetRoleByName(ctx, constants.USER.String())
if err != nil {
return err
}
err = q.CreateUserRole(
ctx,
sqlc.AddUserRoleParams{
sqlc.CreateUserRoleParams{
UserID: user.ID,
Column2: []pgtype.UUID{role.ID},
Column2: []pgtype.UUID{adminRole.ID, useRole.ID},
},
)
if err != nil {