UPDATE: Change cursor to offset, bc FE dk implement
All checks were successful
Build and Release / release (push) Successful in 1m3s
All checks were successful
Build and Release / release (push) Successful in 1m3s
This commit is contained in:
@@ -15,14 +15,18 @@ CREATE TABLE IF NOT EXISTS users (
|
|||||||
updated_at TIMESTAMPTZ DEFAULT now()
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_users_active_created_at
|
ALTER TABLE users ADD CONSTRAINT check_auth_provider
|
||||||
ON users (created_at DESC)
|
CHECK (auth_provider IN ('local', 'google', 'facebook', 'github'));
|
||||||
WHERE is_deleted = false;
|
|
||||||
|
CREATE INDEX idx_users_provider_created_at ON users (auth_provider, created_at DESC);
|
||||||
|
|
||||||
CREATE INDEX idx_users_email_active
|
CREATE INDEX idx_users_email_active
|
||||||
ON users (email)
|
ON users (email)
|
||||||
WHERE is_deleted = false;
|
WHERE is_deleted = false;
|
||||||
|
|
||||||
|
CREATE INDEX idx_users_email_trgm ON users USING gin (email gin_trgm_ops);
|
||||||
|
CREATE INDEX idx_users_id_trgm ON users USING gin ((id::text) gin_trgm_ops);
|
||||||
|
|
||||||
CREATE OR REPLACE FUNCTION update_updated_at()
|
CREATE OR REPLACE FUNCTION update_updated_at()
|
||||||
RETURNS TRIGGER AS $$
|
RETURNS TRIGGER AS $$
|
||||||
BEGIN
|
BEGIN
|
||||||
|
|||||||
@@ -12,5 +12,9 @@ CREATE TABLE medias (
|
|||||||
updated_at TIMESTAMPTZ DEFAULT now()
|
updated_at TIMESTAMPTZ DEFAULT now()
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_medias_user_created ON medias (user_id, created_at DESC);
|
|
||||||
CREATE INDEX idx_medias_original_name_trgm ON medias USING GIN (original_name gin_trgm_ops);
|
CREATE INDEX idx_medias_original_name_trgm ON medias USING GIN (original_name gin_trgm_ops);
|
||||||
|
CREATE INDEX idx_medias_storage_key_trgm ON medias USING GIN (storage_key gin_trgm_ops);
|
||||||
|
CREATE INDEX idx_medias_size ON medias (size);
|
||||||
|
CREATE INDEX idx_medias_mime_type ON medias (mime_type);
|
||||||
|
CREATE INDEX idx_medias_user_created ON medias (user_id, created_at DESC);
|
||||||
|
CREATE INDEX idx_medias_created_at ON medias (created_at DESC);
|
||||||
@@ -11,46 +11,61 @@ DELETE FROM medias
|
|||||||
WHERE id = $1;
|
WHERE id = $1;
|
||||||
|
|
||||||
-- name: SearchMedias :many
|
-- name: SearchMedias :many
|
||||||
SELECT *
|
SELECT
|
||||||
|
id, user_id, storage_key, original_name, mime_type, size, file_metadata, created_at, updated_at
|
||||||
FROM medias
|
FROM medias
|
||||||
WHERE
|
WHERE
|
||||||
(sqlc.narg('cursor')::uuid IS NULL OR id > sqlc.narg('cursor')::uuid)
|
(sqlc.narg('user_ids')::uuid[] IS NULL OR user_id = ANY(sqlc.narg('user_ids')::uuid[]))
|
||||||
|
AND (sqlc.narg('mime_type')::text IS NULL OR mime_type ILIKE sqlc.narg('mime_type')::text || '%')
|
||||||
|
AND (sqlc.narg('min_size')::bigint IS NULL OR size >= sqlc.narg('min_size')::bigint)
|
||||||
|
AND (sqlc.narg('max_size')::bigint IS NULL OR size <= sqlc.narg('max_size')::bigint)
|
||||||
AND (
|
AND (
|
||||||
sqlc.narg('search_text')::text IS NULL OR
|
sqlc.narg('search_text')::text IS NULL OR
|
||||||
|
id::text ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
original_name ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
original_name ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
storage_key ILIKE '%' || sqlc.narg('search_text')::text || '%'
|
storage_key ILIKE '%' || sqlc.narg('search_text')::text || '%'
|
||||||
)
|
)
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
-- id
|
CASE WHEN sqlc.narg('sort') = 'id' AND sqlc.narg('order') = 'asc' THEN id END ASC,
|
||||||
CASE
|
CASE WHEN sqlc.narg('sort') = 'id' AND sqlc.narg('order') = 'desc' THEN id END DESC,
|
||||||
WHEN sqlc.narg('sort') = 'id' AND sqlc.narg('order') = 'asc' THEN id
|
|
||||||
END ASC,
|
|
||||||
CASE
|
|
||||||
WHEN sqlc.narg('sort') = 'id' AND sqlc.narg('order') = 'desc' THEN id
|
|
||||||
END DESC,
|
|
||||||
|
|
||||||
-- created_at
|
CASE WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'asc' THEN created_at END ASC,
|
||||||
CASE
|
CASE WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'desc' THEN created_at END DESC,
|
||||||
WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'asc' THEN created_at
|
|
||||||
END ASC,
|
|
||||||
CASE
|
|
||||||
WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'desc' THEN created_at
|
|
||||||
END DESC,
|
|
||||||
|
|
||||||
-- updated_at
|
CASE WHEN sqlc.narg('sort') = 'updated_at' AND sqlc.narg('order') = 'asc' THEN updated_at END ASC,
|
||||||
CASE
|
CASE WHEN sqlc.narg('sort') = 'updated_at' AND sqlc.narg('order') = 'desc' THEN updated_at END DESC,
|
||||||
WHEN sqlc.narg('sort') = 'updated_at' AND sqlc.narg('order') = 'asc' THEN updated_at
|
|
||||||
END ASC,
|
CASE WHEN sqlc.narg('sort') = 'size' AND sqlc.narg('order') = 'asc' THEN size END ASC,
|
||||||
CASE
|
CASE WHEN sqlc.narg('sort') = 'size' AND sqlc.narg('order') = 'desc' THEN size END DESC,
|
||||||
WHEN sqlc.narg('sort') = 'updated_at' AND sqlc.narg('order') = 'desc' THEN updated_at
|
|
||||||
END DESC,
|
CASE WHEN sqlc.narg('sort') = 'original_name' AND sqlc.narg('order') = 'asc' THEN original_name END ASC,
|
||||||
|
CASE WHEN sqlc.narg('sort') = 'original_name' AND sqlc.narg('order') = 'desc' THEN original_name END DESC,
|
||||||
|
|
||||||
|
CASE WHEN sqlc.narg('sort') = 'storage_key' AND sqlc.narg('order') = 'asc' THEN storage_key END ASC,
|
||||||
|
CASE WHEN sqlc.narg('sort') = 'storage_key' AND sqlc.narg('order') = 'desc' THEN storage_key END DESC,
|
||||||
|
|
||||||
|
CASE WHEN sqlc.narg('sort') = 'mime_type' AND sqlc.narg('order') = 'asc' THEN mime_type END ASC,
|
||||||
|
CASE WHEN sqlc.narg('sort') = 'mime_type' AND sqlc.narg('order') = 'desc' THEN mime_type END DESC,
|
||||||
|
|
||||||
-- fallback
|
|
||||||
id ASC
|
id ASC
|
||||||
|
LIMIT sqlc.arg('limit')
|
||||||
|
OFFSET sqlc.arg('offset');
|
||||||
|
|
||||||
LIMIT sqlc.arg('limit');
|
|
||||||
|
-- name: CountMedias :one
|
||||||
|
SELECT count(*)
|
||||||
|
FROM medias
|
||||||
|
WHERE
|
||||||
|
(sqlc.narg('user_ids')::uuid[] IS NULL OR user_id = ANY(sqlc.narg('user_ids')::uuid[]))
|
||||||
|
AND (sqlc.narg('mime_type')::text IS NULL OR mime_type ILIKE sqlc.narg('mime_type')::text || '%')
|
||||||
|
AND (sqlc.narg('min_size')::bigint IS NULL OR size >= sqlc.narg('min_size')::bigint)
|
||||||
|
AND (sqlc.narg('max_size')::bigint IS NULL OR size <= sqlc.narg('max_size')::bigint)
|
||||||
|
AND (
|
||||||
|
sqlc.narg('search_text')::text IS NULL OR
|
||||||
|
id::text ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
|
original_name ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
|
storage_key ILIKE '%' || sqlc.narg('search_text')::text || '%'
|
||||||
|
);
|
||||||
|
|
||||||
-- name: GetMediasByUserID :many
|
-- name: GetMediasByUserID :many
|
||||||
SELECT * FROM medias
|
SELECT * FROM medias
|
||||||
|
|||||||
@@ -201,11 +201,12 @@ SELECT
|
|||||||
u.email,
|
u.email,
|
||||||
u.password_hash,
|
u.password_hash,
|
||||||
u.token_version,
|
u.token_version,
|
||||||
|
u.google_id,
|
||||||
|
u.auth_provider,
|
||||||
u.refresh_token,
|
u.refresh_token,
|
||||||
u.is_deleted,
|
u.is_deleted,
|
||||||
u.created_at,
|
u.created_at,
|
||||||
u.updated_at,
|
u.updated_at,
|
||||||
|
|
||||||
(
|
(
|
||||||
SELECT json_build_object(
|
SELECT json_build_object(
|
||||||
'display_name', p.display_name,
|
'display_name', p.display_name,
|
||||||
@@ -220,7 +221,6 @@ SELECT
|
|||||||
FROM user_profiles p
|
FROM user_profiles p
|
||||||
WHERE p.user_id = u.id
|
WHERE p.user_id = u.id
|
||||||
) AS profile,
|
) AS profile,
|
||||||
|
|
||||||
(
|
(
|
||||||
SELECT COALESCE(
|
SELECT COALESCE(
|
||||||
json_agg(json_build_object('id', r.id, 'name', r.name)),
|
json_agg(json_build_object('id', r.id, 'name', r.name)),
|
||||||
@@ -230,14 +230,9 @@ SELECT
|
|||||||
JOIN roles r ON ur.role_id = r.id
|
JOIN roles r ON ur.role_id = r.id
|
||||||
WHERE ur.user_id = u.id
|
WHERE ur.user_id = u.id
|
||||||
) AS roles
|
) AS roles
|
||||||
|
|
||||||
FROM users u
|
FROM users u
|
||||||
|
|
||||||
WHERE
|
WHERE
|
||||||
(sqlc.narg('cursor')::uuid IS NULL OR u.id > sqlc.narg('cursor')::uuid)
|
(sqlc.narg('is_deleted')::boolean IS NULL OR u.is_deleted = sqlc.narg('is_deleted')::boolean)
|
||||||
|
|
||||||
AND (sqlc.narg('is_deleted')::boolean IS NULL OR u.is_deleted = sqlc.narg('is_deleted')::boolean)
|
|
||||||
|
|
||||||
AND (
|
AND (
|
||||||
sqlc.narg('role_ids')::uuid[] IS NULL OR
|
sqlc.narg('role_ids')::uuid[] IS NULL OR
|
||||||
EXISTS (
|
EXISTS (
|
||||||
@@ -246,37 +241,65 @@ WHERE
|
|||||||
AND ur2.role_id = ANY(sqlc.narg('role_ids')::uuid[])
|
AND ur2.role_id = ANY(sqlc.narg('role_ids')::uuid[])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
AND (sqlc.narg('auth_provider')::text IS NULL OR u.auth_provider = sqlc.narg('auth_provider')::text)
|
||||||
AND (sqlc.narg('search_id')::uuid IS NULL OR u.id = sqlc.narg('search_id')::uuid)
|
AND (sqlc.narg('created_from')::timestamp IS NULL OR u.created_at >= sqlc.narg('created_from')::timestamp)
|
||||||
|
AND (sqlc.narg('created_to')::timestamp IS NULL OR u.created_at <= sqlc.narg('created_to')::timestamp)
|
||||||
AND (
|
AND (
|
||||||
sqlc.narg('search_text')::text IS NULL OR
|
sqlc.narg('search_text')::text IS NULL OR
|
||||||
u.email ILIKE '%' || sqlc.narg('search_text')::text || '%'
|
u.id::text ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
|
u.email ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM user_profiles p
|
||||||
|
WHERE p.user_id = u.id
|
||||||
|
AND (
|
||||||
|
p.full_name ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
|
p.phone ILIKE '%' || sqlc.narg('search_text')::text || '%'
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
-- id
|
CASE WHEN sqlc.narg('sort') = 'id' AND sqlc.narg('order') = 'asc' THEN u.id END ASC,
|
||||||
CASE
|
CASE WHEN sqlc.narg('sort') = 'id' AND sqlc.narg('order') = 'desc' THEN u.id END DESC,
|
||||||
WHEN sqlc.narg('sort') = 'id' AND sqlc.narg('order') = 'asc' THEN id
|
CASE WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'asc' THEN u.created_at END ASC,
|
||||||
END ASC,
|
CASE WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'desc' THEN u.created_at END DESC,
|
||||||
CASE
|
CASE WHEN sqlc.narg('sort') = 'updated_at' AND sqlc.narg('order') = 'asc' THEN u.updated_at END ASC,
|
||||||
WHEN sqlc.narg('sort') = 'id' AND sqlc.narg('order') = 'desc' THEN id
|
CASE WHEN sqlc.narg('sort') = 'updated_at' AND sqlc.narg('order') = 'desc' THEN u.updated_at END DESC,
|
||||||
END DESC,
|
CASE WHEN sqlc.narg('sort') = 'email' AND sqlc.narg('order') = 'asc' THEN u.email END ASC,
|
||||||
-- created_at
|
CASE WHEN sqlc.narg('sort') = 'email' AND sqlc.narg('order') = 'desc' THEN u.email END DESC,
|
||||||
CASE
|
CASE WHEN sqlc.narg('sort') = 'is_deleted' AND sqlc.narg('order') = 'asc' THEN u.is_deleted END ASC,
|
||||||
WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'asc' THEN u.created_at
|
CASE WHEN sqlc.narg('sort') = 'is_deleted' AND sqlc.narg('order') = 'desc' THEN u.is_deleted END DESC,
|
||||||
END ASC,
|
CASE WHEN sqlc.narg('sort') = 'auth_provider' AND sqlc.narg('order') = 'asc' THEN u.auth_provider END ASC,
|
||||||
CASE
|
CASE WHEN sqlc.narg('sort') = 'auth_provider' AND sqlc.narg('order') = 'desc' THEN u.auth_provider END DESC,
|
||||||
WHEN sqlc.narg('sort') = 'created_at' AND sqlc.narg('order') = 'desc' THEN u.created_at
|
|
||||||
END DESC,
|
|
||||||
-- updated_at
|
|
||||||
CASE
|
|
||||||
WHEN sqlc.narg('sort') = 'updated_at' AND sqlc.narg('order') = 'asc' THEN u.updated_at
|
|
||||||
END ASC,
|
|
||||||
CASE
|
|
||||||
WHEN sqlc.narg('sort') = 'updated_at' AND sqlc.narg('order') = 'desc' THEN u.updated_at
|
|
||||||
END DESC,
|
|
||||||
-- fallback
|
|
||||||
u.id ASC
|
u.id ASC
|
||||||
|
LIMIT sqlc.arg('limit')
|
||||||
|
OFFSET sqlc.arg('offset');
|
||||||
|
|
||||||
LIMIT sqlc.arg('limit');
|
-- name: CountUsers :one
|
||||||
|
SELECT count(*)
|
||||||
|
FROM users u
|
||||||
|
WHERE
|
||||||
|
(sqlc.narg('is_deleted')::boolean IS NULL OR u.is_deleted = sqlc.narg('is_deleted')::boolean)
|
||||||
|
AND (
|
||||||
|
sqlc.narg('role_ids')::uuid[] IS NULL OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM user_roles ur2
|
||||||
|
WHERE ur2.user_id = u.id
|
||||||
|
AND ur2.role_id = ANY(sqlc.narg('role_ids')::uuid[])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND (sqlc.narg('auth_provider')::text IS NULL OR u.auth_provider = sqlc.narg('auth_provider')::text)
|
||||||
|
AND (sqlc.narg('created_from')::timestamp IS NULL OR u.created_at >= sqlc.narg('created_from')::timestamp)
|
||||||
|
AND (sqlc.narg('created_to')::timestamp IS NULL OR u.created_at <= sqlc.narg('created_to')::timestamp)
|
||||||
|
AND (
|
||||||
|
sqlc.narg('search_text')::text IS NULL OR
|
||||||
|
u.id::text ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
|
u.email ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM user_profiles p
|
||||||
|
WHERE p.user_id = u.id
|
||||||
|
AND (
|
||||||
|
p.full_name ILIKE '%' || sqlc.narg('search_text')::text || '%' OR
|
||||||
|
p.phone ILIKE '%' || sqlc.narg('search_text')::text || '%'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
50
docs/docs.go
50
docs/docs.go
@@ -869,7 +869,17 @@ const docTemplate = `{
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "cursor",
|
"name": "auth_provider",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "created_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "created_to",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -894,6 +904,12 @@ const docTemplate = `{
|
|||||||
"name": "order",
|
"name": "order",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -914,7 +930,10 @@ const docTemplate = `{
|
|||||||
"enum": [
|
"enum": [
|
||||||
"id",
|
"id",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at"
|
"updated_at",
|
||||||
|
"email",
|
||||||
|
"is_deleted",
|
||||||
|
"auth_provider"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "sort",
|
"name": "sort",
|
||||||
@@ -1570,21 +1589,30 @@ const docTemplate = `{
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/history-api_internal_dtos_response.PaginationMeta"
|
||||||
"properties": {
|
|
||||||
"has_more": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"next_cursor": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"history-api_internal_dtos_response.PaginationMeta": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"current_page": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"page_size": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_pages": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_records": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"history-api_pkg_constants.TokenType": {
|
"history-api_pkg_constants.TokenType": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
|
|||||||
@@ -862,7 +862,17 @@
|
|||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "cursor",
|
"name": "auth_provider",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "created_from",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "created_to",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -887,6 +897,12 @@
|
|||||||
"name": "order",
|
"name": "order",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "integer",
|
||||||
|
"name": "page",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
@@ -907,7 +923,10 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"id",
|
"id",
|
||||||
"created_at",
|
"created_at",
|
||||||
"updated_at"
|
"updated_at",
|
||||||
|
"email",
|
||||||
|
"is_deleted",
|
||||||
|
"auth_provider"
|
||||||
],
|
],
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"name": "sort",
|
"name": "sort",
|
||||||
@@ -1563,21 +1582,30 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"pagination": {
|
"pagination": {
|
||||||
"type": "object",
|
"$ref": "#/definitions/history-api_internal_dtos_response.PaginationMeta"
|
||||||
"properties": {
|
|
||||||
"has_more": {
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
"next_cursor": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"history-api_internal_dtos_response.PaginationMeta": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"current_page": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"page_size": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_pages": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"total_records": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"history-api_pkg_constants.TokenType": {
|
"history-api_pkg_constants.TokenType": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"format": "int32",
|
"format": "int32",
|
||||||
|
|||||||
@@ -156,15 +156,21 @@ definitions:
|
|||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
pagination:
|
pagination:
|
||||||
properties:
|
$ref: '#/definitions/history-api_internal_dtos_response.PaginationMeta'
|
||||||
has_more:
|
|
||||||
type: boolean
|
|
||||||
next_cursor:
|
|
||||||
type: string
|
|
||||||
type: object
|
|
||||||
status:
|
status:
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
|
history-api_internal_dtos_response.PaginationMeta:
|
||||||
|
properties:
|
||||||
|
current_page:
|
||||||
|
type: integer
|
||||||
|
page_size:
|
||||||
|
type: integer
|
||||||
|
total_pages:
|
||||||
|
type: integer
|
||||||
|
total_records:
|
||||||
|
type: integer
|
||||||
|
type: object
|
||||||
history-api_pkg_constants.TokenType:
|
history-api_pkg_constants.TokenType:
|
||||||
enum:
|
enum:
|
||||||
- 1
|
- 1
|
||||||
@@ -732,7 +738,13 @@ paths:
|
|||||||
description: Search and filter users with pagination (Admin/Mod only)
|
description: Search and filter users with pagination (Admin/Mod only)
|
||||||
parameters:
|
parameters:
|
||||||
- in: query
|
- in: query
|
||||||
name: cursor
|
name: auth_provider
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: created_from
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: created_to
|
||||||
type: string
|
type: string
|
||||||
- in: query
|
- in: query
|
||||||
name: is_deleted
|
name: is_deleted
|
||||||
@@ -749,6 +761,10 @@ paths:
|
|||||||
in: query
|
in: query
|
||||||
name: order
|
name: order
|
||||||
type: string
|
type: string
|
||||||
|
- in: query
|
||||||
|
minimum: 1
|
||||||
|
name: page
|
||||||
|
type: integer
|
||||||
- collectionFormat: csv
|
- collectionFormat: csv
|
||||||
in: query
|
in: query
|
||||||
items:
|
items:
|
||||||
@@ -764,6 +780,9 @@ paths:
|
|||||||
- id
|
- id
|
||||||
- created_at
|
- created_at
|
||||||
- updated_at
|
- updated_at
|
||||||
|
- email
|
||||||
|
- is_deleted
|
||||||
|
- auth_provider
|
||||||
in: query
|
in: query
|
||||||
name: sort
|
name: sort
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
@@ -11,6 +11,11 @@ type PreSignedCompleteDto struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SearchMediaDto struct {
|
type SearchMediaDto struct {
|
||||||
CursorPaginationDto
|
PaginationDto
|
||||||
|
Sort string `json:"sort" query:"sort" validate:"omitempty,oneof=id created_at updated_at size original_name storage_key mime_type"`
|
||||||
Search string `json:"search" query:"search" validate:"omitempty,min=2,max=200"`
|
Search string `json:"search" query:"search" validate:"omitempty,min=2,max=200"`
|
||||||
|
UserIDs []string `json:"user_ids" query:"user_ids" validate:"omitempty,dive,uuid"`
|
||||||
|
MimeType string `json:"mime_type" query:"mime_type" validate:"omitempty,max=100"`
|
||||||
|
MinSize *int64 `json:"min_size" query:"min_size" validate:"omitempty,min=0"`
|
||||||
|
MaxSize *int64 `json:"max_size" query:"max_size" validate:"omitempty,min=0,gtefield=MinSize"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package request
|
package request
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
type UpdateProfileDto struct {
|
type UpdateProfileDto struct {
|
||||||
DisplayName string `json:"display_name" validate:"omitempty,min=2,max=50"`
|
DisplayName string `json:"display_name" validate:"omitempty,min=2,max=50"`
|
||||||
FullName string `json:"full_name" validate:"omitempty,min=2,max=100"`
|
FullName string `json:"full_name" validate:"omitempty,min=2,max=100"`
|
||||||
@@ -21,21 +23,18 @@ type ChangeRoleDto struct {
|
|||||||
Roles []string `json:"role_ids" validate:"required,min=1,dive,required,uuid"`
|
Roles []string `json:"role_ids" validate:"required,min=1,dive,required,uuid"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetAllUserDto struct {
|
type PaginationDto struct {
|
||||||
CursorPaginationDto
|
Page int `json:"page" query:"page" validate:"omitempty,min=1"`
|
||||||
IsDeleted *bool `json:"is_deleted" query:"is_deleted" validate:"omitempty"`
|
|
||||||
RoleIDs []string `json:"role_ids" query:"role_ids" validate:"omitempty,dive,uuid"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CursorPaginationDto struct {
|
|
||||||
Cursor string `json:"cursor" query:"cursor" validate:"omitempty,uuid"`
|
|
||||||
Limit int `json:"limit" query:"limit" validate:"required,min=1,max=100"`
|
Limit int `json:"limit" query:"limit" validate:"required,min=1,max=100"`
|
||||||
Sort string `json:"sort" query:"sort" validate:"omitempty,oneof=id created_at updated_at"`
|
|
||||||
Order string `json:"order" query:"order" validate:"omitempty,oneof=asc desc"`
|
Order string `json:"order" query:"order" validate:"omitempty,oneof=asc desc"`
|
||||||
}
|
}
|
||||||
type SearchUserDto struct {
|
type SearchUserDto struct {
|
||||||
CursorPaginationDto
|
PaginationDto
|
||||||
|
Sort string `json:"sort" query:"sort" validate:"omitempty,oneof=id created_at updated_at email is_deleted auth_provider"`
|
||||||
Search string `json:"search" query:"search" validate:"omitempty,min=2,max=200"`
|
Search string `json:"search" query:"search" validate:"omitempty,min=2,max=200"`
|
||||||
IsDeleted *bool `json:"is_deleted" query:"is_deleted" validate:"omitempty"`
|
IsDeleted *bool `json:"is_deleted" query:"is_deleted" validate:"omitempty"`
|
||||||
RoleIDs []string `json:"role_ids" query:"role_ids" validate:"omitempty,dive,uuid"`
|
RoleIDs []string `json:"role_ids" query:"role_ids" validate:"omitempty,dive,uuid"`
|
||||||
|
AuthProvider string `json:"auth_provider" query:"auth_provider" validate:"omitempty"`
|
||||||
|
CreatedFrom *time.Time `json:"created_from" query:"created_from" validate:"omitempty"`
|
||||||
|
CreatedTo *time.Time `json:"created_to" query:"created_to" validate:"omitempty"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,12 +19,39 @@ type JWTClaims struct {
|
|||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PaginationMeta struct {
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
TotalRecords int64 `json:"total_records"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
|
|
||||||
type PaginatedResponse struct {
|
type PaginatedResponse struct {
|
||||||
Data any `json:"data"`
|
|
||||||
Status bool `json:"status"`
|
Status bool `json:"status"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Pagination struct {
|
Data any `json:"data"`
|
||||||
NextCursor string `json:"next_cursor"`
|
Pagination *PaginationMeta `json:"pagination"`
|
||||||
HasMore bool `json:"has_more"`
|
}
|
||||||
} `json:"pagination"`
|
|
||||||
|
func BuildPaginatedResponse(data any, totalRecords int64, page int, limit int) *PaginatedResponse {
|
||||||
|
if page < 1 {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
if limit < 1 {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
totalPages := int((totalRecords + int64(limit) - 1) / int64(limit))
|
||||||
|
|
||||||
|
return &PaginatedResponse{
|
||||||
|
Status: true,
|
||||||
|
Message: "Success",
|
||||||
|
Data: data,
|
||||||
|
Pagination: &PaginationMeta{
|
||||||
|
CurrentPage: page,
|
||||||
|
PageSize: limit,
|
||||||
|
TotalRecords: totalRecords,
|
||||||
|
TotalPages: totalPages,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,43 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const countMedias = `-- name: CountMedias :one
|
||||||
|
SELECT count(*)
|
||||||
|
FROM medias
|
||||||
|
WHERE
|
||||||
|
($1::uuid[] IS NULL OR user_id = ANY($1::uuid[]))
|
||||||
|
AND ($2::text IS NULL OR mime_type ILIKE $2::text || '%')
|
||||||
|
AND ($3::bigint IS NULL OR size >= $3::bigint)
|
||||||
|
AND ($4::bigint IS NULL OR size <= $4::bigint)
|
||||||
|
AND (
|
||||||
|
$5::text IS NULL OR
|
||||||
|
id::text ILIKE '%' || $5::text || '%' OR
|
||||||
|
original_name ILIKE '%' || $5::text || '%' OR
|
||||||
|
storage_key ILIKE '%' || $5::text || '%'
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CountMediasParams struct {
|
||||||
|
UserIds []pgtype.UUID `json:"user_ids"`
|
||||||
|
MimeType pgtype.Text `json:"mime_type"`
|
||||||
|
MinSize pgtype.Int8 `json:"min_size"`
|
||||||
|
MaxSize pgtype.Int8 `json:"max_size"`
|
||||||
|
SearchText pgtype.Text `json:"search_text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CountMedias(ctx context.Context, arg CountMediasParams) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, countMedias,
|
||||||
|
arg.UserIds,
|
||||||
|
arg.MimeType,
|
||||||
|
arg.MinSize,
|
||||||
|
arg.MaxSize,
|
||||||
|
arg.SearchText,
|
||||||
|
)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
const createMedia = `-- name: CreateMedia :one
|
const createMedia = `-- name: CreateMedia :one
|
||||||
INSERT INTO medias (
|
INSERT INTO medias (
|
||||||
user_id, storage_key, original_name, mime_type, size, file_metadata
|
user_id, storage_key, original_name, mime_type, size, file_metadata
|
||||||
@@ -122,62 +159,69 @@ func (q *Queries) GetMediasByUserID(ctx context.Context, userID pgtype.UUID) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
const searchMedias = `-- name: SearchMedias :many
|
const searchMedias = `-- name: SearchMedias :many
|
||||||
SELECT id, user_id, storage_key, original_name, mime_type, size, file_metadata, created_at, updated_at
|
SELECT
|
||||||
|
id, user_id, storage_key, original_name, mime_type, size, file_metadata, created_at, updated_at
|
||||||
FROM medias
|
FROM medias
|
||||||
WHERE
|
WHERE
|
||||||
($1::uuid IS NULL OR id > $1::uuid)
|
($1::uuid[] IS NULL OR user_id = ANY($1::uuid[]))
|
||||||
|
AND ($2::text IS NULL OR mime_type ILIKE $2::text || '%')
|
||||||
|
AND ($3::bigint IS NULL OR size >= $3::bigint)
|
||||||
|
AND ($4::bigint IS NULL OR size <= $4::bigint)
|
||||||
AND (
|
AND (
|
||||||
$2::text IS NULL OR
|
$5::text IS NULL OR
|
||||||
original_name ILIKE '%' || $2::text || '%' OR
|
id::text ILIKE '%' || $5::text || '%' OR
|
||||||
storage_key ILIKE '%' || $2::text || '%'
|
original_name ILIKE '%' || $5::text || '%' OR
|
||||||
|
storage_key ILIKE '%' || $5::text || '%'
|
||||||
)
|
)
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
-- id
|
CASE WHEN $6 = 'id' AND $7 = 'asc' THEN id END ASC,
|
||||||
CASE
|
CASE WHEN $6 = 'id' AND $7 = 'desc' THEN id END DESC,
|
||||||
WHEN $3 = 'id' AND $4 = 'asc' THEN id
|
|
||||||
END ASC,
|
|
||||||
CASE
|
|
||||||
WHEN $3 = 'id' AND $4 = 'desc' THEN id
|
|
||||||
END DESC,
|
|
||||||
|
|
||||||
-- created_at
|
CASE WHEN $6 = 'created_at' AND $7 = 'asc' THEN created_at END ASC,
|
||||||
CASE
|
CASE WHEN $6 = 'created_at' AND $7 = 'desc' THEN created_at END DESC,
|
||||||
WHEN $3 = 'created_at' AND $4 = 'asc' THEN created_at
|
|
||||||
END ASC,
|
|
||||||
CASE
|
|
||||||
WHEN $3 = 'created_at' AND $4 = 'desc' THEN created_at
|
|
||||||
END DESC,
|
|
||||||
|
|
||||||
-- updated_at
|
CASE WHEN $6 = 'updated_at' AND $7 = 'asc' THEN updated_at END ASC,
|
||||||
CASE
|
CASE WHEN $6 = 'updated_at' AND $7 = 'desc' THEN updated_at END DESC,
|
||||||
WHEN $3 = 'updated_at' AND $4 = 'asc' THEN updated_at
|
|
||||||
END ASC,
|
CASE WHEN $6 = 'size' AND $7 = 'asc' THEN size END ASC,
|
||||||
CASE
|
CASE WHEN $6 = 'size' AND $7 = 'desc' THEN size END DESC,
|
||||||
WHEN $3 = 'updated_at' AND $4 = 'desc' THEN updated_at
|
|
||||||
END DESC,
|
CASE WHEN $6 = 'original_name' AND $7 = 'asc' THEN original_name END ASC,
|
||||||
|
CASE WHEN $6 = 'original_name' AND $7 = 'desc' THEN original_name END DESC,
|
||||||
|
|
||||||
|
CASE WHEN $6 = 'storage_key' AND $7 = 'asc' THEN storage_key END ASC,
|
||||||
|
CASE WHEN $6 = 'storage_key' AND $7 = 'desc' THEN storage_key END DESC,
|
||||||
|
|
||||||
|
CASE WHEN $6 = 'mime_type' AND $7 = 'asc' THEN mime_type END ASC,
|
||||||
|
CASE WHEN $6 = 'mime_type' AND $7 = 'desc' THEN mime_type END DESC,
|
||||||
|
|
||||||
-- fallback
|
|
||||||
id ASC
|
id ASC
|
||||||
|
LIMIT $9
|
||||||
LIMIT $5
|
OFFSET $8
|
||||||
`
|
`
|
||||||
|
|
||||||
type SearchMediasParams struct {
|
type SearchMediasParams struct {
|
||||||
Cursor pgtype.UUID `json:"cursor"`
|
UserIds []pgtype.UUID `json:"user_ids"`
|
||||||
|
MimeType pgtype.Text `json:"mime_type"`
|
||||||
|
MinSize pgtype.Int8 `json:"min_size"`
|
||||||
|
MaxSize pgtype.Int8 `json:"max_size"`
|
||||||
SearchText pgtype.Text `json:"search_text"`
|
SearchText pgtype.Text `json:"search_text"`
|
||||||
Sort interface{} `json:"sort"`
|
Sort interface{} `json:"sort"`
|
||||||
Order interface{} `json:"order"`
|
Order interface{} `json:"order"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
Limit int32 `json:"limit"`
|
Limit int32 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (q *Queries) SearchMedias(ctx context.Context, arg SearchMediasParams) ([]Media, error) {
|
func (q *Queries) SearchMedias(ctx context.Context, arg SearchMediasParams) ([]Media, error) {
|
||||||
rows, err := q.db.Query(ctx, searchMedias,
|
rows, err := q.db.Query(ctx, searchMedias,
|
||||||
arg.Cursor,
|
arg.UserIds,
|
||||||
|
arg.MimeType,
|
||||||
|
arg.MinSize,
|
||||||
|
arg.MaxSize,
|
||||||
arg.SearchText,
|
arg.SearchText,
|
||||||
arg.Sort,
|
arg.Sort,
|
||||||
arg.Order,
|
arg.Order,
|
||||||
|
arg.Offset,
|
||||||
arg.Limit,
|
arg.Limit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -11,6 +11,60 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const countUsers = `-- name: CountUsers :one
|
||||||
|
SELECT count(*)
|
||||||
|
FROM users u
|
||||||
|
WHERE
|
||||||
|
($1::boolean IS NULL OR u.is_deleted = $1::boolean)
|
||||||
|
AND (
|
||||||
|
$2::uuid[] IS NULL OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM user_roles ur2
|
||||||
|
WHERE ur2.user_id = u.id
|
||||||
|
AND ur2.role_id = ANY($2::uuid[])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
AND ($3::text IS NULL OR u.auth_provider = $3::text)
|
||||||
|
AND ($4::timestamp IS NULL OR u.created_at >= $4::timestamp)
|
||||||
|
AND ($5::timestamp IS NULL OR u.created_at <= $5::timestamp)
|
||||||
|
AND (
|
||||||
|
$6::text IS NULL OR
|
||||||
|
u.id::text ILIKE '%' || $6::text || '%' OR
|
||||||
|
u.email ILIKE '%' || $6::text || '%' OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM user_profiles p
|
||||||
|
WHERE p.user_id = u.id
|
||||||
|
AND (
|
||||||
|
p.full_name ILIKE '%' || $6::text || '%' OR
|
||||||
|
p.phone ILIKE '%' || $6::text || '%'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
`
|
||||||
|
|
||||||
|
type CountUsersParams struct {
|
||||||
|
IsDeleted pgtype.Bool `json:"is_deleted"`
|
||||||
|
RoleIds []pgtype.UUID `json:"role_ids"`
|
||||||
|
AuthProvider pgtype.Text `json:"auth_provider"`
|
||||||
|
CreatedFrom pgtype.Timestamp `json:"created_from"`
|
||||||
|
CreatedTo pgtype.Timestamp `json:"created_to"`
|
||||||
|
SearchText pgtype.Text `json:"search_text"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CountUsers(ctx context.Context, arg CountUsersParams) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, countUsers,
|
||||||
|
arg.IsDeleted,
|
||||||
|
arg.RoleIds,
|
||||||
|
arg.AuthProvider,
|
||||||
|
arg.CreatedFrom,
|
||||||
|
arg.CreatedTo,
|
||||||
|
arg.SearchText,
|
||||||
|
)
|
||||||
|
var count int64
|
||||||
|
err := row.Scan(&count)
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
const createUserProfile = `-- name: CreateUserProfile :one
|
const createUserProfile = `-- name: CreateUserProfile :one
|
||||||
INSERT INTO user_profiles (
|
INSERT INTO user_profiles (
|
||||||
user_id,
|
user_id,
|
||||||
@@ -304,11 +358,12 @@ SELECT
|
|||||||
u.email,
|
u.email,
|
||||||
u.password_hash,
|
u.password_hash,
|
||||||
u.token_version,
|
u.token_version,
|
||||||
|
u.google_id,
|
||||||
|
u.auth_provider,
|
||||||
u.refresh_token,
|
u.refresh_token,
|
||||||
u.is_deleted,
|
u.is_deleted,
|
||||||
u.created_at,
|
u.created_at,
|
||||||
u.updated_at,
|
u.updated_at,
|
||||||
|
|
||||||
(
|
(
|
||||||
SELECT json_build_object(
|
SELECT json_build_object(
|
||||||
'display_name', p.display_name,
|
'display_name', p.display_name,
|
||||||
@@ -323,7 +378,6 @@ SELECT
|
|||||||
FROM user_profiles p
|
FROM user_profiles p
|
||||||
WHERE p.user_id = u.id
|
WHERE p.user_id = u.id
|
||||||
) AS profile,
|
) AS profile,
|
||||||
|
|
||||||
(
|
(
|
||||||
SELECT COALESCE(
|
SELECT COALESCE(
|
||||||
json_agg(json_build_object('id', r.id, 'name', r.name)),
|
json_agg(json_build_object('id', r.id, 'name', r.name)),
|
||||||
@@ -333,66 +387,61 @@ SELECT
|
|||||||
JOIN roles r ON ur.role_id = r.id
|
JOIN roles r ON ur.role_id = r.id
|
||||||
WHERE ur.user_id = u.id
|
WHERE ur.user_id = u.id
|
||||||
) AS roles
|
) AS roles
|
||||||
|
|
||||||
FROM users u
|
FROM users u
|
||||||
|
|
||||||
WHERE
|
WHERE
|
||||||
($1::uuid IS NULL OR u.id > $1::uuid)
|
($1::boolean IS NULL OR u.is_deleted = $1::boolean)
|
||||||
|
|
||||||
AND ($2::boolean IS NULL OR u.is_deleted = $2::boolean)
|
|
||||||
|
|
||||||
AND (
|
AND (
|
||||||
$3::uuid[] IS NULL OR
|
$2::uuid[] IS NULL OR
|
||||||
EXISTS (
|
EXISTS (
|
||||||
SELECT 1 FROM user_roles ur2
|
SELECT 1 FROM user_roles ur2
|
||||||
WHERE ur2.user_id = u.id
|
WHERE ur2.user_id = u.id
|
||||||
AND ur2.role_id = ANY($3::uuid[])
|
AND ur2.role_id = ANY($2::uuid[])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
AND ($3::text IS NULL OR u.auth_provider = $3::text)
|
||||||
AND ($4::uuid IS NULL OR u.id = $4::uuid)
|
AND ($4::timestamp IS NULL OR u.created_at >= $4::timestamp)
|
||||||
|
AND ($5::timestamp IS NULL OR u.created_at <= $5::timestamp)
|
||||||
AND (
|
AND (
|
||||||
$5::text IS NULL OR
|
$6::text IS NULL OR
|
||||||
u.email ILIKE '%' || $5::text || '%'
|
u.id::text ILIKE '%' || $6::text || '%' OR
|
||||||
|
u.email ILIKE '%' || $6::text || '%' OR
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1 FROM user_profiles p
|
||||||
|
WHERE p.user_id = u.id
|
||||||
|
AND (
|
||||||
|
p.full_name ILIKE '%' || $6::text || '%' OR
|
||||||
|
p.phone ILIKE '%' || $6::text || '%'
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
-- id
|
CASE WHEN $7 = 'id' AND $8 = 'asc' THEN u.id END ASC,
|
||||||
CASE
|
CASE WHEN $7 = 'id' AND $8 = 'desc' THEN u.id END DESC,
|
||||||
WHEN $6 = 'id' AND $7 = 'asc' THEN id
|
CASE WHEN $7 = 'created_at' AND $8 = 'asc' THEN u.created_at END ASC,
|
||||||
END ASC,
|
CASE WHEN $7 = 'created_at' AND $8 = 'desc' THEN u.created_at END DESC,
|
||||||
CASE
|
CASE WHEN $7 = 'updated_at' AND $8 = 'asc' THEN u.updated_at END ASC,
|
||||||
WHEN $6 = 'id' AND $7 = 'desc' THEN id
|
CASE WHEN $7 = 'updated_at' AND $8 = 'desc' THEN u.updated_at END DESC,
|
||||||
END DESC,
|
CASE WHEN $7 = 'email' AND $8 = 'asc' THEN u.email END ASC,
|
||||||
-- created_at
|
CASE WHEN $7 = 'email' AND $8 = 'desc' THEN u.email END DESC,
|
||||||
CASE
|
CASE WHEN $7 = 'is_deleted' AND $8 = 'asc' THEN u.is_deleted END ASC,
|
||||||
WHEN $6 = 'created_at' AND $7 = 'asc' THEN u.created_at
|
CASE WHEN $7 = 'is_deleted' AND $8 = 'desc' THEN u.is_deleted END DESC,
|
||||||
END ASC,
|
CASE WHEN $7 = 'auth_provider' AND $8 = 'asc' THEN u.auth_provider END ASC,
|
||||||
CASE
|
CASE WHEN $7 = 'auth_provider' AND $8 = 'desc' THEN u.auth_provider END DESC,
|
||||||
WHEN $6 = 'created_at' AND $7 = 'desc' THEN u.created_at
|
|
||||||
END DESC,
|
|
||||||
-- updated_at
|
|
||||||
CASE
|
|
||||||
WHEN $6 = 'updated_at' AND $7 = 'asc' THEN u.updated_at
|
|
||||||
END ASC,
|
|
||||||
CASE
|
|
||||||
WHEN $6 = 'updated_at' AND $7 = 'desc' THEN u.updated_at
|
|
||||||
END DESC,
|
|
||||||
-- fallback
|
|
||||||
u.id ASC
|
u.id ASC
|
||||||
|
LIMIT $10
|
||||||
LIMIT $8
|
OFFSET $9
|
||||||
`
|
`
|
||||||
|
|
||||||
type SearchUsersParams struct {
|
type SearchUsersParams struct {
|
||||||
Cursor pgtype.UUID `json:"cursor"`
|
|
||||||
IsDeleted pgtype.Bool `json:"is_deleted"`
|
IsDeleted pgtype.Bool `json:"is_deleted"`
|
||||||
RoleIds []pgtype.UUID `json:"role_ids"`
|
RoleIds []pgtype.UUID `json:"role_ids"`
|
||||||
SearchID pgtype.UUID `json:"search_id"`
|
AuthProvider pgtype.Text `json:"auth_provider"`
|
||||||
|
CreatedFrom pgtype.Timestamp `json:"created_from"`
|
||||||
|
CreatedTo pgtype.Timestamp `json:"created_to"`
|
||||||
SearchText pgtype.Text `json:"search_text"`
|
SearchText pgtype.Text `json:"search_text"`
|
||||||
Sort interface{} `json:"sort"`
|
Sort interface{} `json:"sort"`
|
||||||
Order interface{} `json:"order"`
|
Order interface{} `json:"order"`
|
||||||
|
Offset int32 `json:"offset"`
|
||||||
Limit int32 `json:"limit"`
|
Limit int32 `json:"limit"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,6 +450,8 @@ type SearchUsersRow struct {
|
|||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
PasswordHash pgtype.Text `json:"password_hash"`
|
PasswordHash pgtype.Text `json:"password_hash"`
|
||||||
TokenVersion int32 `json:"token_version"`
|
TokenVersion int32 `json:"token_version"`
|
||||||
|
GoogleID pgtype.Text `json:"google_id"`
|
||||||
|
AuthProvider string `json:"auth_provider"`
|
||||||
RefreshToken pgtype.Text `json:"refresh_token"`
|
RefreshToken pgtype.Text `json:"refresh_token"`
|
||||||
IsDeleted bool `json:"is_deleted"`
|
IsDeleted bool `json:"is_deleted"`
|
||||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||||
@@ -411,13 +462,15 @@ type SearchUsersRow struct {
|
|||||||
|
|
||||||
func (q *Queries) SearchUsers(ctx context.Context, arg SearchUsersParams) ([]SearchUsersRow, error) {
|
func (q *Queries) SearchUsers(ctx context.Context, arg SearchUsersParams) ([]SearchUsersRow, error) {
|
||||||
rows, err := q.db.Query(ctx, searchUsers,
|
rows, err := q.db.Query(ctx, searchUsers,
|
||||||
arg.Cursor,
|
|
||||||
arg.IsDeleted,
|
arg.IsDeleted,
|
||||||
arg.RoleIds,
|
arg.RoleIds,
|
||||||
arg.SearchID,
|
arg.AuthProvider,
|
||||||
|
arg.CreatedFrom,
|
||||||
|
arg.CreatedTo,
|
||||||
arg.SearchText,
|
arg.SearchText,
|
||||||
arg.Sort,
|
arg.Sort,
|
||||||
arg.Order,
|
arg.Order,
|
||||||
|
arg.Offset,
|
||||||
arg.Limit,
|
arg.Limit,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -432,6 +485,8 @@ func (q *Queries) SearchUsers(ctx context.Context, arg SearchUsersParams) ([]Sea
|
|||||||
&i.Email,
|
&i.Email,
|
||||||
&i.PasswordHash,
|
&i.PasswordHash,
|
||||||
&i.TokenVersion,
|
&i.TokenVersion,
|
||||||
|
&i.GoogleID,
|
||||||
|
&i.AuthProvider,
|
||||||
&i.RefreshToken,
|
&i.RefreshToken,
|
||||||
&i.IsDeleted,
|
&i.IsDeleted,
|
||||||
&i.CreatedAt,
|
&i.CreatedAt,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ type MediaRepository interface {
|
|||||||
GetByID(ctx context.Context, id pgtype.UUID) (*models.MediaEntity, error)
|
GetByID(ctx context.Context, id pgtype.UUID) (*models.MediaEntity, error)
|
||||||
GetByUserID(ctx context.Context, userId pgtype.UUID) ([]*models.MediaEntity, error)
|
GetByUserID(ctx context.Context, userId pgtype.UUID) ([]*models.MediaEntity, error)
|
||||||
Search(ctx context.Context, params sqlc.SearchMediasParams) ([]*models.MediaEntity, error)
|
Search(ctx context.Context, params sqlc.SearchMediasParams) ([]*models.MediaEntity, error)
|
||||||
|
Count(ctx context.Context, params sqlc.CountMediasParams) (int64, error)
|
||||||
Delete(ctx context.Context, id pgtype.UUID) error
|
Delete(ctx context.Context, id pgtype.UUID) error
|
||||||
Create(ctx context.Context, params sqlc.CreateMediaParams) (*models.MediaEntity, error)
|
Create(ctx context.Context, params sqlc.CreateMediaParams) (*models.MediaEntity, error)
|
||||||
}
|
}
|
||||||
@@ -85,6 +86,7 @@ func (r *mediaRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.
|
|||||||
var media models.MediaEntity
|
var media models.MediaEntity
|
||||||
err := r.c.Get(ctx, cacheId, &media)
|
err := r.c.Get(ctx, cacheId, &media)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
_ = r.c.Set(ctx, cacheId, media, constants.NormalCacheDuration)
|
||||||
return &media, nil
|
return &media, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,11 +120,10 @@ func (r *mediaRepository) Create(ctx context.Context, params sqlc.CreateMediaPar
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
_ = r.c.DelByPattern(bgCtx, "media:target*")
|
|
||||||
_ = r.c.DelByPattern(bgCtx, "media:userId:*")
|
|
||||||
_ = r.c.DelByPattern(bgCtx, "media:search*")
|
_ = r.c.DelByPattern(bgCtx, "media:search*")
|
||||||
|
_ = r.c.DelByPattern(bgCtx, "media:count*")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
media := models.MediaEntity{
|
media := models.MediaEntity{
|
||||||
ID: convert.UUIDToString(row.ID),
|
ID: convert.UUIDToString(row.ID),
|
||||||
UserID: convert.UUIDToString(row.UserID),
|
UserID: convert.UUIDToString(row.UserID),
|
||||||
@@ -155,7 +156,16 @@ func (r *mediaRepository) Search(ctx context.Context, params sqlc.SearchMediasPa
|
|||||||
queryKey := r.generateQueryKey("media:search", params)
|
queryKey := r.generateQueryKey("media:search", params)
|
||||||
var cachedIDs []string
|
var cachedIDs []string
|
||||||
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
||||||
return r.getByIDsWithFallback(ctx, cachedIDs)
|
listItem, err := r.getByIDsWithFallback(ctx, cachedIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newCachedIDs := make([]string, len(listItem))
|
||||||
|
for i, media := range listItem {
|
||||||
|
newCachedIDs[i] = media.ID
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, queryKey, newCachedIDs, constants.ListCacheDuration)
|
||||||
|
return listItem, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := r.q.SearchMedias(ctx, params)
|
rows, err := r.q.SearchMedias(ctx, params)
|
||||||
@@ -195,11 +205,35 @@ func (r *mediaRepository) Search(ctx context.Context, params sqlc.SearchMediasPa
|
|||||||
return medias, nil
|
return medias, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mediaRepository) Count(ctx context.Context, params sqlc.CountMediasParams) (int64, error) {
|
||||||
|
queryKey := r.generateQueryKey("media:count", params)
|
||||||
|
var count int64
|
||||||
|
if err := r.c.Get(ctx, queryKey, &count); err == nil {
|
||||||
|
_ = r.c.Set(ctx, queryKey, count, constants.ListCacheDuration)
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
count, err := r.q.CountMedias(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, queryKey, count, constants.ListCacheDuration)
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *mediaRepository) GetByUserID(ctx context.Context, userId pgtype.UUID) ([]*models.MediaEntity, error) {
|
func (r *mediaRepository) GetByUserID(ctx context.Context, userId pgtype.UUID) ([]*models.MediaEntity, error) {
|
||||||
queryKey := fmt.Sprintf("media:userId:%s", convert.UUIDToString(userId))
|
queryKey := fmt.Sprintf("media:userId:%s", convert.UUIDToString(userId))
|
||||||
var cachedIDs []string
|
var cachedIDs []string
|
||||||
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
||||||
return r.getByIDsWithFallback(ctx, cachedIDs)
|
listItem, err := r.getByIDsWithFallback(ctx, cachedIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newCachedIDs := make([]string, len(listItem))
|
||||||
|
for i, media := range listItem {
|
||||||
|
newCachedIDs[i] = media.ID
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, queryKey, newCachedIDs, constants.ListCacheDuration)
|
||||||
|
return listItem, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := r.q.GetMediasByUserID(ctx, userId)
|
rows, err := r.q.GetMediasByUserID(ctx, userId)
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ func (r *roleRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.R
|
|||||||
var role models.RoleEntity
|
var role models.RoleEntity
|
||||||
err := r.c.Get(ctx, cacheId, &role)
|
err := r.c.Get(ctx, cacheId, &role)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
_ = r.c.Set(ctx, cacheId, role, constants.NormalCacheDuration)
|
||||||
return &role, nil
|
return &role, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +123,7 @@ func (r *roleRepository) GetByname(ctx context.Context, name string) (*models.Ro
|
|||||||
var role models.RoleEntity
|
var role models.RoleEntity
|
||||||
err := r.c.Get(ctx, cacheId, &role)
|
err := r.c.Get(ctx, cacheId, &role)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
_ = r.c.Set(ctx, cacheId, role, constants.NormalCacheDuration)
|
||||||
return &role, nil
|
return &role, nil
|
||||||
}
|
}
|
||||||
row, err := r.q.GetRoleByName(ctx, name)
|
row, err := r.q.GetRoleByName(ctx, name)
|
||||||
@@ -146,6 +148,11 @@ func (r *roleRepository) Create(ctx context.Context, name string) (*models.RoleE
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
go func() {
|
||||||
|
bgCtx := context.Background()
|
||||||
|
_ = r.c.DelByPattern(bgCtx, "role:all*")
|
||||||
|
}()
|
||||||
|
|
||||||
role := models.RoleEntity{
|
role := models.RoleEntity{
|
||||||
ID: convert.UUIDToString(row.ID),
|
ID: convert.UUIDToString(row.ID),
|
||||||
Name: row.Name,
|
Name: row.Name,
|
||||||
@@ -183,24 +190,52 @@ func (r *roleRepository) Update(ctx context.Context, params sqlc.UpdateRoleParam
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *roleRepository) All(ctx context.Context) ([]*models.RoleEntity, error) {
|
func (r *roleRepository) All(ctx context.Context) ([]*models.RoleEntity, error) {
|
||||||
|
queryKey := "role:all"
|
||||||
|
var cachedIDs []string
|
||||||
|
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
|
||||||
|
listItem, err := r.getByIDsWithFallback(ctx, cachedIDs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
newCachedIDs := make([]string, len(listItem))
|
||||||
|
for i, media := range listItem {
|
||||||
|
newCachedIDs[i] = media.ID
|
||||||
|
}
|
||||||
|
_ = r.c.Set(ctx, queryKey, newCachedIDs, constants.ListCacheDuration)
|
||||||
|
return listItem, err
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := r.q.GetRoles(ctx)
|
rows, err := r.q.GetRoles(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
var roles []*models.RoleEntity
|
||||||
|
var ids []string
|
||||||
|
roleToCache := make(map[string]any)
|
||||||
|
|
||||||
var users []*models.RoleEntity
|
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
user := &models.RoleEntity{
|
role := &models.RoleEntity{
|
||||||
ID: convert.UUIDToString(row.ID),
|
ID: convert.UUIDToString(row.ID),
|
||||||
Name: row.Name,
|
Name: row.Name,
|
||||||
IsDeleted: row.IsDeleted,
|
IsDeleted: row.IsDeleted,
|
||||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||||
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||||
}
|
}
|
||||||
users = append(users, user)
|
ids = append(ids, role.ID)
|
||||||
|
roles = append(roles, role)
|
||||||
|
|
||||||
|
roleToCache[fmt.Sprintf("role:id:%s", role.ID)] = role
|
||||||
}
|
}
|
||||||
|
|
||||||
return users, nil
|
if len(roleToCache) > 0 {
|
||||||
|
_ = r.c.MSet(ctx, roleToCache, constants.NormalCacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ids) > 0 {
|
||||||
|
_ = r.c.Set(ctx, queryKey, ids, constants.ListCacheDuration)
|
||||||
|
}
|
||||||
|
|
||||||
|
return roles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *roleRepository) Delete(ctx context.Context, id pgtype.UUID) error {
|
func (r *roleRepository) Delete(ctx context.Context, id pgtype.UUID) error {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type UserRepository interface {
|
|||||||
GetByIDWithoutDeleted(ctx context.Context, id pgtype.UUID) (*models.UserEntity, error)
|
GetByIDWithoutDeleted(ctx context.Context, id pgtype.UUID) (*models.UserEntity, error)
|
||||||
GetByEmail(ctx context.Context, email string) (*models.UserEntity, error)
|
GetByEmail(ctx context.Context, email string) (*models.UserEntity, error)
|
||||||
Search(ctx context.Context, params sqlc.SearchUsersParams) ([]*models.UserEntity, error)
|
Search(ctx context.Context, params sqlc.SearchUsersParams) ([]*models.UserEntity, error)
|
||||||
|
Count(ctx context.Context, params sqlc.CountUsersParams) (int64, error)
|
||||||
UpsertUser(ctx context.Context, params sqlc.UpsertUserParams) (*models.UserEntity, error)
|
UpsertUser(ctx context.Context, params sqlc.UpsertUserParams) (*models.UserEntity, error)
|
||||||
CreateProfile(ctx context.Context, params sqlc.CreateUserProfileParams) (*models.UserProfileSimple, error)
|
CreateProfile(ctx context.Context, params sqlc.CreateUserProfileParams) (*models.UserProfileSimple, error)
|
||||||
UpdateProfile(ctx context.Context, params sqlc.UpdateUserProfileParams) (*models.UserEntity, error)
|
UpdateProfile(ctx context.Context, params sqlc.UpdateUserProfileParams) (*models.UserEntity, error)
|
||||||
@@ -205,9 +206,8 @@ func (r *userRepository) UpsertUser(ctx context.Context, params sqlc.UpsertUserP
|
|||||||
}
|
}
|
||||||
go func() {
|
go func() {
|
||||||
bgCtx := context.Background()
|
bgCtx := context.Background()
|
||||||
|
|
||||||
_ = r.c.DelByPattern(bgCtx, "user:all*")
|
|
||||||
_ = r.c.DelByPattern(bgCtx, "user:search*")
|
_ = r.c.DelByPattern(bgCtx, "user:search*")
|
||||||
|
_ = r.c.DelByPattern(bgCtx, "user:count*")
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return &models.UserEntity{
|
return &models.UserEntity{
|
||||||
@@ -320,6 +320,22 @@ func (r *userRepository) Search(ctx context.Context, params sqlc.SearchUsersPara
|
|||||||
return users, nil
|
return users, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *userRepository) Count(ctx context.Context, params sqlc.CountUsersParams) (int64, error) {
|
||||||
|
queryKey := r.generateQueryKey("user:count", params)
|
||||||
|
var count int64
|
||||||
|
if err := r.c.Get(ctx, queryKey, &count); err == nil {
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := r.q.CountUsers(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = r.c.Set(ctx, queryKey, count, constants.NormalCacheDuration)
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *userRepository) Delete(ctx context.Context, id pgtype.UUID) error {
|
func (r *userRepository) Delete(ctx context.Context, id pgtype.UUID) error {
|
||||||
user, err := r.GetByID(ctx, id)
|
user, err := r.GetByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,9 +21,10 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
|
"github.com/rs/zerolog/log"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MediaService interface {
|
type MediaService interface {
|
||||||
@@ -111,60 +112,86 @@ func (m *mediaService) GetMediaByUserID(ctx context.Context, id string) ([]*resp
|
|||||||
return models.MediaEntitiesToResponse(medias), nil
|
return models.MediaEntitiesToResponse(medias), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mediaService) SearchMedia(ctx context.Context, dto *request.SearchMediaDto) (*response.PaginatedResponse, error) {
|
func (m *mediaService) fillSearchArgs(arg *sqlc.SearchMediasParams, dto *request.SearchMediaDto) {
|
||||||
arg := sqlc.SearchMediasParams{
|
|
||||||
Limit: int32(dto.Limit + 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
if dto.Sort != "" {
|
if dto.Sort != "" {
|
||||||
arg.Sort = pgtype.Text{String: dto.Sort, Valid: true}
|
arg.Sort = pgtype.Text{String: dto.Sort, Valid: true}
|
||||||
} else {
|
} else {
|
||||||
arg.Sort = pgtype.Text{String: "id", Valid: true}
|
arg.Sort = pgtype.Text{String: "id", Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.Order != "" {
|
|
||||||
arg.Order = pgtype.Text{String: dto.Order, Valid: true}
|
|
||||||
} else {
|
|
||||||
arg.Order = pgtype.Text{String: "asc", Valid: true}
|
arg.Order = pgtype.Text{String: "asc", Valid: true}
|
||||||
|
if dto.Order == "desc" {
|
||||||
|
arg.Order = pgtype.Text{String: "desc", Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.Cursor != "" {
|
if dto.MimeType != "" {
|
||||||
pgID, err := convert.StringToUUID(dto.Cursor)
|
arg.MimeType = pgtype.Text{String: dto.MimeType, Valid: true}
|
||||||
if err != nil {
|
}
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid cursor format")
|
|
||||||
|
if dto.MaxSize != nil {
|
||||||
|
arg.MaxSize = pgtype.Int8{Int64: *dto.MaxSize, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dto.MinSize != nil {
|
||||||
|
arg.MinSize = pgtype.Int8{Int64: *dto.MinSize, Valid: true}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dto.UserIDs) > 0 {
|
||||||
|
for _, id := range dto.UserIDs {
|
||||||
|
if u, err := convert.StringToUUID(id); err == nil {
|
||||||
|
arg.UserIds = append(arg.UserIds, u)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
arg.Cursor = pgID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.Search != "" {
|
if dto.Search != "" {
|
||||||
arg.SearchText = pgtype.Text{String: dto.Search, Valid: true}
|
arg.SearchText = pgtype.Text{String: dto.Search, Valid: true}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rows, err := m.mediaRepo.Search(ctx, arg)
|
func (m *mediaService) SearchMedia(ctx context.Context, dto *request.SearchMediaDto) (*response.PaginatedResponse, error) {
|
||||||
if err != nil {
|
if dto.Page < 1 {
|
||||||
|
dto.Page = 1
|
||||||
|
}
|
||||||
|
offset := (dto.Page - 1) * dto.Limit
|
||||||
|
|
||||||
|
arg := sqlc.SearchMediasParams{
|
||||||
|
Limit: int32(dto.Limit),
|
||||||
|
Offset: int32(offset),
|
||||||
|
}
|
||||||
|
|
||||||
|
m.fillSearchArgs(&arg, dto)
|
||||||
|
|
||||||
|
var rows []*models.MediaEntity
|
||||||
|
var totalRecords int64
|
||||||
|
|
||||||
|
g, gCtx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var err error
|
||||||
|
rows, err = m.mediaRepo.Search(gCtx, arg)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
countArg := sqlc.CountMediasParams{
|
||||||
|
UserIds: arg.UserIds,
|
||||||
|
MimeType: arg.MimeType,
|
||||||
|
MinSize: arg.MinSize,
|
||||||
|
MaxSize: arg.MaxSize,
|
||||||
|
SearchText: arg.SearchText,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
totalRecords, err = m.mediaRepo.Count(gCtx, countArg)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMore := false
|
return response.BuildPaginatedResponse(rows, totalRecords, dto.Page, dto.Limit), nil
|
||||||
var nextCursor string
|
|
||||||
|
|
||||||
if len(rows) > dto.Limit {
|
|
||||||
hasMore = true
|
|
||||||
nextCursor = rows[dto.Limit-1].ID
|
|
||||||
rows = rows[:dto.Limit]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
res := &response.PaginatedResponse{
|
|
||||||
Data: rows,
|
|
||||||
Status: true,
|
|
||||||
Message: "",
|
|
||||||
}
|
|
||||||
res.Pagination.HasMore = hasMore
|
|
||||||
res.Pagination.NextCursor = nextCursor
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *mediaService) UploadServerSide(ctx context.Context, userId string, fileHeader *multipart.FileHeader) (*response.MediaResponse, error) {
|
func (m *mediaService) UploadServerSide(ctx context.Context, userId string, fileHeader *multipart.FileHeader) (*response.MediaResponse, error) {
|
||||||
userIdUUID, err := convert.StringToUUID(userId)
|
userIdUUID, err := convert.StringToUUID(userId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/gofiber/fiber/v3"
|
"github.com/gofiber/fiber/v3"
|
||||||
"github.com/jackc/pgx/v5/pgtype"
|
"github.com/jackc/pgx/v5/pgtype"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
type UserService interface {
|
type UserService interface {
|
||||||
@@ -208,81 +209,90 @@ func (u *userService) RestoreUser(ctx context.Context, userId string) (*response
|
|||||||
return user.ToResponse(), nil
|
return user.ToResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *userService) SearchUser(ctx context.Context, dto *request.SearchUserDto) (*response.PaginatedResponse, error) {
|
func (m *userService) fillSearchArgs(arg *sqlc.SearchUsersParams, dto *request.SearchUserDto) {
|
||||||
arg := sqlc.SearchUsersParams{
|
|
||||||
Limit: int32(dto.Limit + 1),
|
|
||||||
}
|
|
||||||
|
|
||||||
if dto.Sort != "" {
|
if dto.Sort != "" {
|
||||||
arg.Sort = pgtype.Text{String: dto.Sort, Valid: true}
|
arg.Sort = pgtype.Text{String: dto.Sort, Valid: true}
|
||||||
} else {
|
} else {
|
||||||
arg.Sort = pgtype.Text{String: "id", Valid: true}
|
arg.Sort = pgtype.Text{String: "id", Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.Order != "" {
|
|
||||||
arg.Order = pgtype.Text{String: dto.Order, Valid: true}
|
|
||||||
} else {
|
|
||||||
arg.Order = pgtype.Text{String: "asc", Valid: true}
|
arg.Order = pgtype.Text{String: "asc", Valid: true}
|
||||||
|
if dto.Order == "desc" {
|
||||||
|
arg.Order = pgtype.Text{String: "desc", Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.Cursor != "" {
|
if dto.AuthProvider != "" {
|
||||||
pgID, err := convert.StringToUUID(dto.Cursor)
|
arg.AuthProvider = pgtype.Text{String: dto.AuthProvider, Valid: true}
|
||||||
if err != nil {
|
|
||||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid cursor format")
|
|
||||||
}
|
|
||||||
arg.Cursor = pgID
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.Search != "" {
|
if dto.CreatedFrom != nil {
|
||||||
pgID, err := convert.StringToUUID(dto.Search)
|
arg.CreatedFrom = pgtype.Timestamp{Time: *dto.CreatedFrom, Valid: true}
|
||||||
if err == nil {
|
|
||||||
arg.SearchID = pgID
|
|
||||||
} else {
|
|
||||||
arg.SearchText = pgtype.Text{String: dto.Search, Valid: true}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if dto.CreatedTo != nil {
|
||||||
|
arg.CreatedTo = pgtype.Timestamp{Time: *dto.CreatedTo, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
if dto.IsDeleted != nil {
|
if dto.IsDeleted != nil {
|
||||||
arg.IsDeleted = pgtype.Bool{Bool: *dto.IsDeleted, Valid: true}
|
arg.IsDeleted = pgtype.Bool{Bool: *dto.IsDeleted, Valid: true}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(dto.RoleIDs) > 0 {
|
if len(dto.RoleIDs) > 0 {
|
||||||
var pgRoleIDs []pgtype.UUID
|
for _, id := range dto.RoleIDs {
|
||||||
for _, idStr := range dto.RoleIDs {
|
if u, err := convert.StringToUUID(id); err == nil {
|
||||||
pgID, err := convert.StringToUUID(idStr)
|
arg.RoleIds = append(arg.RoleIds, u)
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
pgRoleIDs = append(pgRoleIDs, pgID)
|
|
||||||
}
|
}
|
||||||
arg.RoleIds = pgRoleIDs
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rows, err := u.userRepo.Search(ctx, arg)
|
if dto.Search != "" {
|
||||||
if err != nil {
|
arg.SearchText = pgtype.Text{String: dto.Search, Valid: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (u *userService) SearchUser(ctx context.Context, dto *request.SearchUserDto) (*response.PaginatedResponse, error) {
|
||||||
|
if dto.Page < 1 {
|
||||||
|
dto.Page = 1
|
||||||
|
}
|
||||||
|
offset := (dto.Page - 1) * dto.Limit
|
||||||
|
|
||||||
|
arg := sqlc.SearchUsersParams{
|
||||||
|
Limit: int32(dto.Limit),
|
||||||
|
Offset: int32(offset),
|
||||||
|
}
|
||||||
|
|
||||||
|
u.fillSearchArgs(&arg, dto)
|
||||||
|
|
||||||
|
var rows []*models.UserEntity
|
||||||
|
var totalRecords int64
|
||||||
|
|
||||||
|
g, gCtx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
var err error
|
||||||
|
rows, err = u.userRepo.Search(gCtx, arg)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
g.Go(func() error {
|
||||||
|
countArg := sqlc.CountUsersParams{
|
||||||
|
RoleIds: arg.RoleIds,
|
||||||
|
AuthProvider: arg.AuthProvider,
|
||||||
|
CreatedFrom: arg.CreatedFrom,
|
||||||
|
CreatedTo: arg.CreatedTo,
|
||||||
|
IsDeleted: arg.IsDeleted,
|
||||||
|
SearchText: arg.SearchText,
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
totalRecords, err = u.userRepo.Count(gCtx, countArg)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
|
||||||
|
if err := g.Wait(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasMore := false
|
return response.BuildPaginatedResponse(rows, totalRecords, dto.Page, dto.Limit), nil
|
||||||
var nextCursor string
|
|
||||||
|
|
||||||
if len(rows) > dto.Limit {
|
|
||||||
hasMore = true
|
|
||||||
nextCursor = rows[dto.Limit-1].ID
|
|
||||||
rows = rows[:dto.Limit]
|
|
||||||
}
|
|
||||||
|
|
||||||
users := models.UsersEntityToResponse(rows)
|
|
||||||
|
|
||||||
res := &response.PaginatedResponse{
|
|
||||||
Data: users,
|
|
||||||
Status: true,
|
|
||||||
Message: "",
|
|
||||||
}
|
|
||||||
|
|
||||||
res.Pagination.HasMore = hasMore
|
|
||||||
res.Pagination.NextCursor = nextCursor
|
|
||||||
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *userService) GetUserByID(ctx context.Context, userId string) (*response.UserResponse, error) {
|
func (u *userService) GetUserByID(ctx context.Context, userId string) (*response.UserResponse, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user