mirror of
https://github.com/tgdrive/teldrive.git
synced 2024-09-20 08:15:55 +08:00
refactor: use uuid7 for fileId
This commit is contained in:
parent
2d564eb1e8
commit
149d8c6140
44
internal/database/migrations/20240802212949_uuid.sql
Normal file
44
internal/database/migrations/20240802212949_uuid.sql
Normal file
|
@ -0,0 +1,44 @@
|
|||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
create or replace function public.uuid7() returns uuid as $$
|
||||
declare
|
||||
begin
|
||||
return uuid7(clock_timestamp());
|
||||
end $$ language plpgsql;
|
||||
|
||||
create or replace function public.uuid7(p_timestamp timestamp with time zone) returns uuid as $$
|
||||
declare
|
||||
|
||||
v_time double precision := null;
|
||||
|
||||
v_unix_t bigint := null;
|
||||
v_rand_a bigint := null;
|
||||
v_rand_b bigint := null;
|
||||
|
||||
v_unix_t_hex varchar := null;
|
||||
v_rand_a_hex varchar := null;
|
||||
v_rand_b_hex varchar := null;
|
||||
|
||||
c_milli double precision := 10^3; -- 1 000
|
||||
c_micro double precision := 10^6; -- 1 000 000
|
||||
c_scale double precision := 4.096; -- 4.0 * (1024 / 1000)
|
||||
|
||||
c_version bigint := x'0000000000007000'::bigint; -- RFC-9562 version: b'0111...'
|
||||
c_variant bigint := x'8000000000000000'::bigint; -- RFC-9562 variant: b'10xx...'
|
||||
|
||||
begin
|
||||
|
||||
v_time := extract(epoch from p_timestamp);
|
||||
|
||||
v_unix_t := trunc(v_time * c_milli);
|
||||
v_rand_a := trunc((v_time * c_micro - v_unix_t * c_milli) * c_scale);
|
||||
v_rand_b := trunc(random() * 2^30)::bigint << 32 | trunc(random() * 2^32)::bigint;
|
||||
|
||||
v_unix_t_hex := lpad(to_hex(v_unix_t), 12, '0');
|
||||
v_rand_a_hex := lpad(to_hex((v_rand_a | c_version)::bigint), 4, '0');
|
||||
v_rand_b_hex := lpad(to_hex((v_rand_b | c_variant)::bigint), 16, '0');
|
||||
|
||||
return (v_unix_t_hex || v_rand_a_hex || v_rand_b_hex)::uuid;
|
||||
|
||||
end $$ language plpgsql;
|
||||
-- +goose StatementEnd
|
381
internal/database/migrations/20240802213957_alter_table.sql
Normal file
381
internal/database/migrations/20240802213957_alter_table.sql
Normal file
|
@ -0,0 +1,381 @@
|
|||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
|
||||
UPDATE teldrive.files
|
||||
SET parent_id = NULL where files.parent_id = 'root';
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
ADD COLUMN uuid_id UUID,
|
||||
ADD COLUMN uuid_parent_id UUID;
|
||||
|
||||
UPDATE teldrive.files
|
||||
SET uuid_id = uuid7(files.created_at);
|
||||
|
||||
UPDATE teldrive.files f1
|
||||
SET uuid_parent_id = f2.uuid_id
|
||||
FROM teldrive.files f2
|
||||
WHERE f1.parent_id = f2.id;
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
DROP CONSTRAINT IF EXISTS files_pkey;
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
RENAME COLUMN id TO old_id;
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
RENAME COLUMN uuid_id TO id;
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
RENAME COLUMN parent_id TO old_parent_id;
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
RENAME COLUMN uuid_parent_id TO parent_id;
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
ADD CONSTRAINT files_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
ALTER COLUMN id SET DEFAULT uuid7();
|
||||
|
||||
DROP INDEX IF EXISTS teldrive.files_category_type_user_id_index;
|
||||
DROP INDEX IF EXISTS teldrive.name_idx;
|
||||
DROP INDEX IF EXISTS teldrive.name_search_idx;
|
||||
DROP INDEX IF EXISTS teldrive.name_status_user_id_idx;
|
||||
DROP INDEX IF EXISTS teldrive.parent_idx;
|
||||
DROP INDEX IF EXISTS teldrive.starred_updated_at_idx;
|
||||
DROP INDEX IF EXISTS teldrive.status_idx;
|
||||
DROP INDEX IF EXISTS teldrive.status_user_id_idx;
|
||||
DROP INDEX IF EXISTS teldrive.unique_file;
|
||||
DROP INDEX IF EXISTS teldrive.unique_folder;
|
||||
DROP INDEX IF EXISTS teldrive.updated_at_idx;
|
||||
DROP INDEX IF EXISTS teldrive.updated_at_status_user_id_idx;
|
||||
DROP INDEX IF EXISTS teldrive.user_id_idx;
|
||||
DROP PROCEDURE IF EXISTS teldrive.update_size;
|
||||
DROP FUNCTION IF EXISTS teldrive.move_items;
|
||||
DROP FUNCTION IF EXISTS teldrive.move_directory;
|
||||
DROP PROCEDURE IF EXISTS teldrive.delete_folder_recursive;
|
||||
DROP PROCEDURE IF EXISTS teldrive.delete_files_bulk;
|
||||
DROP FUNCTION IF EXISTS teldrive.create_directories;
|
||||
DROP FUNCTION IF EXISTS teldrive.get_file_from_path;
|
||||
|
||||
|
||||
CREATE INDEX idx_files_category_type_user_id ON teldrive.files USING btree (category, type, user_id);
|
||||
CREATE INDEX idx_files_name ON teldrive.files USING btree (name);
|
||||
CREATE INDEX idx_files_name_search ON teldrive.files USING pgroonga (regexp_replace(name, '[.,-_]'::text, ' '::text, 'g'::text)) WITH (tokenizer='TokenNgram');
|
||||
CREATE INDEX idx_files_name_user_id_status ON teldrive.files USING btree (name, user_id, status);
|
||||
CREATE INDEX idx_files_parent_id ON teldrive.files USING btree (parent_id);
|
||||
CREATE INDEX idx_files_starred_updated_at ON teldrive.files USING btree (starred, updated_at DESC);
|
||||
CREATE INDEX idx_files_status ON teldrive.files USING btree (status);
|
||||
CREATE INDEX idx_files_updated_at_user_id_status ON teldrive.files USING btree (updated_at DESC, user_id, status);
|
||||
CREATE UNIQUE INDEX idx_files_unique_file ON teldrive.files USING btree (name, parent_id, user_id, size) WHERE ((status = 'active'::text) AND (type = 'file'::text));
|
||||
CREATE UNIQUE INDEX idx_files_unique_folder ON teldrive.files USING btree (name, parent_id, user_id) WHERE (type = 'folder'::text);
|
||||
CREATE INDEX idx_files_updated_at ON teldrive.files USING btree (updated_at);
|
||||
CREATE INDEX idx_files_user_id ON teldrive.files USING btree (user_id);
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION teldrive.create_directories(u_id bigint, long_path text)
|
||||
RETURNS SETOF teldrive.files
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
DECLARE
|
||||
path_parts TEXT[];
|
||||
current_directory_id UUID;
|
||||
new_directory_id UUID;
|
||||
directory_name TEXT;
|
||||
path_so_far TEXT;
|
||||
BEGIN
|
||||
path_parts := string_to_array(regexp_replace(long_path, '^/+', ''), '/');
|
||||
|
||||
path_so_far := '';
|
||||
|
||||
SELECT id INTO current_directory_id
|
||||
FROM teldrive.files
|
||||
WHERE parent_id is NULL AND user_id = u_id AND type = 'folder';
|
||||
|
||||
FOR directory_name IN SELECT unnest(path_parts) LOOP
|
||||
path_so_far := CONCAT(path_so_far, '/', directory_name);
|
||||
|
||||
SELECT id INTO new_directory_id
|
||||
FROM teldrive.files
|
||||
WHERE parent_id = current_directory_id
|
||||
AND "name" = directory_name
|
||||
AND "user_id" = u_id;
|
||||
|
||||
IF new_directory_id IS NULL THEN
|
||||
INSERT INTO teldrive.files ("name", "type", mime_type, parent_id, "user_id", starred)
|
||||
VALUES (directory_name, 'folder', 'drive/folder', current_directory_id, u_id, false)
|
||||
RETURNING id INTO new_directory_id;
|
||||
END IF;
|
||||
|
||||
current_directory_id := new_directory_id;
|
||||
END LOOP;
|
||||
|
||||
RETURN QUERY SELECT * FROM teldrive.files WHERE id = current_directory_id;
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
CREATE OR REPLACE FUNCTION teldrive.get_file_from_path(full_path text, u_id bigint, throw_error boolean DEFAULT false)
|
||||
RETURNS SETOF teldrive.files
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
DECLARE
|
||||
target_id UUID;
|
||||
begin
|
||||
|
||||
IF full_path = '/' then
|
||||
full_path := '';
|
||||
END IF;
|
||||
|
||||
WITH RECURSIVE dir_hierarchy AS (
|
||||
SELECT
|
||||
root.id,
|
||||
root.name,
|
||||
root.parent_id,
|
||||
0 AS depth,
|
||||
'' as path
|
||||
FROM
|
||||
teldrive.files as root
|
||||
WHERE
|
||||
root.parent_id is NULL AND root.user_id = u_id and root.type='folder'
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
f.id,
|
||||
f.name,
|
||||
f.parent_id,
|
||||
dh.depth + 1 AS depth,
|
||||
dh.path || '/' || f.name
|
||||
FROM
|
||||
teldrive.files f
|
||||
JOIN
|
||||
dir_hierarchy dh ON dh.id = f.parent_id
|
||||
WHERE f.type = 'folder' AND f.user_id = u_id
|
||||
)
|
||||
|
||||
SELECT id into target_id FROM dir_hierarchy dh
|
||||
WHERE dh.path = full_path
|
||||
ORDER BY dh.depth DESC
|
||||
LIMIT 1;
|
||||
|
||||
IF throw_error IS true AND target_id IS NULL THEN
|
||||
RAISE EXCEPTION 'file not found for path: %', full_path;
|
||||
END IF;
|
||||
|
||||
RETURN QUERY select * from teldrive.files where id=target_id;
|
||||
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE teldrive.delete_files_bulk(IN file_ids text[], IN u_id bigint)
|
||||
LANGUAGE plpgsql
|
||||
AS $procedure$
|
||||
DECLARE
|
||||
folder_ids UUID[];
|
||||
BEGIN
|
||||
WITH RECURSIVE folder_tree AS (
|
||||
SELECT id
|
||||
FROM teldrive.files
|
||||
WHERE id = ANY (file_ids::UUID[])
|
||||
AND user_id = u_id AND type = 'folder'
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT f.id
|
||||
FROM teldrive.files f
|
||||
JOIN folder_tree ft ON f.parent_id = ft.id
|
||||
WHERE f.user_id = u_id AND f.type = 'folder'
|
||||
) SELECT array_agg(id) INTO folder_ids FROM folder_tree;
|
||||
|
||||
UPDATE teldrive.files
|
||||
SET status = 'pending_deletion'
|
||||
WHERE (id = ANY (file_ids::UUID[]) OR parent_id = ANY (folder_ids))
|
||||
AND type = 'file' AND user_id = u_id;
|
||||
|
||||
DELETE FROM teldrive.files
|
||||
WHERE id = ANY (folder_ids) AND user_id = u_id;
|
||||
END;
|
||||
$procedure$
|
||||
;
|
||||
|
||||
|
||||
CREATE OR REPLACE PROCEDURE teldrive.delete_folder_recursive(IN src text, IN u_id bigint)
|
||||
LANGUAGE plpgsql
|
||||
AS $procedure$
|
||||
DECLARE
|
||||
folder_id UUID;
|
||||
folder_ids UUID[];
|
||||
BEGIN
|
||||
|
||||
IF position('/' in src) = 1 THEN
|
||||
select id into folder_id from teldrive.get_file_from_path(src,u_id);
|
||||
IF folder_id IS NULL THEN
|
||||
RAISE EXCEPTION 'source not found';
|
||||
END IF;
|
||||
ELSE
|
||||
folder_id := src;
|
||||
IF NOT EXISTS (SELECT 1 FROM teldrive.files WHERE id = folder_id) THEN
|
||||
RAISE EXCEPTION 'source not found';
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
WITH RECURSIVE folder_tree AS (
|
||||
SELECT id
|
||||
FROM teldrive.files
|
||||
WHERE id = folder_id
|
||||
AND user_id = u_id and type='folder'
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT f.id
|
||||
FROM teldrive.files f
|
||||
JOIN folder_tree ft ON f.parent_id = ft.id
|
||||
WHERE f.user_id = u_id and f.type='folder'
|
||||
) SELECT array_agg(id) INTO folder_ids FROM folder_tree;
|
||||
|
||||
UPDATE teldrive.files
|
||||
SET status = 'pending_deletion'
|
||||
WHERE parent_id = ANY (folder_ids) and type='file' and user_id = u_id;
|
||||
|
||||
DELETE FROM teldrive.files
|
||||
WHERE id = ANY (folder_ids) AND user_id = u_id;
|
||||
END;
|
||||
$procedure$
|
||||
;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION teldrive.move_directory(src text, dest text, u_id bigint)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
DECLARE
|
||||
src_parent TEXT;
|
||||
src_base TEXT;
|
||||
dest_parent TEXT;
|
||||
dest_base TEXT;
|
||||
dest_id UUID;
|
||||
dest_parent_id UUID;
|
||||
src_id UUID;
|
||||
begin
|
||||
|
||||
select id into src_id from teldrive.get_file_from_path(src,u_id);
|
||||
|
||||
select id into dest_id from teldrive.get_file_from_path(dest,u_id);
|
||||
|
||||
IF src_id is NULL THEN
|
||||
RAISE EXCEPTION 'source directory not found';
|
||||
END IF;
|
||||
|
||||
IF dest_id is not NULL then
|
||||
RAISE EXCEPTION 'destination directory exists';
|
||||
END IF;
|
||||
|
||||
SELECT parent, base INTO src_parent,src_base FROM teldrive.split_path(src);
|
||||
|
||||
SELECT parent, base INTO dest_parent, dest_base FROM teldrive.split_path(dest);
|
||||
|
||||
IF src_parent != dest_parent then
|
||||
select id into dest_id from teldrive.create_directories(u_id,dest);
|
||||
UPDATE teldrive.files SET parent_id = dest_id WHERE parent_id = src_id;
|
||||
delete from teldrive.files where id = src_id;
|
||||
END IF;
|
||||
|
||||
IF src_base != dest_base and src_parent = dest_parent then
|
||||
UPDATE teldrive.files SET name = dest_base WHERE id = src_id;
|
||||
END IF;
|
||||
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
|
||||
CREATE OR REPLACE FUNCTION teldrive.move_items(file_ids text[], dest text, u_id bigint)
|
||||
RETURNS void
|
||||
LANGUAGE plpgsql
|
||||
AS $function$
|
||||
declare
|
||||
dest_id UUID;
|
||||
BEGIN
|
||||
|
||||
select id into dest_id from teldrive.get_file_from_path(dest,u_id);
|
||||
|
||||
IF dest_id is NULL then
|
||||
select id into dest_id from teldrive.create_directories(u_id,dest);
|
||||
END IF;
|
||||
|
||||
UPDATE teldrive.files
|
||||
SET parent_id = dest_id
|
||||
WHERE id = ANY(file_ids::UUID[]);
|
||||
END;
|
||||
$function$
|
||||
;
|
||||
|
||||
CREATE OR REPLACE PROCEDURE teldrive.update_size()
|
||||
LANGUAGE plpgsql
|
||||
AS $procedure$
|
||||
begin
|
||||
|
||||
WITH RECURSIVE folder_hierarchy AS (
|
||||
|
||||
SELECT
|
||||
id,
|
||||
name,
|
||||
parent_id,
|
||||
ARRAY[id] AS path
|
||||
FROM
|
||||
teldrive.files
|
||||
WHERE
|
||||
type = 'folder' AND parent_id is NULL
|
||||
|
||||
UNION ALL
|
||||
|
||||
SELECT
|
||||
f.id,
|
||||
f.name,
|
||||
f.parent_id,
|
||||
fh.path || f.id
|
||||
FROM
|
||||
teldrive.files f
|
||||
JOIN
|
||||
folder_hierarchy fh ON f.parent_id = fh.id
|
||||
WHERE
|
||||
f.type = 'folder'
|
||||
)
|
||||
, folder_sizes AS (
|
||||
SELECT
|
||||
f.id,
|
||||
f.path,
|
||||
COALESCE(SUM(CASE WHEN c.type != 'folder' THEN c.size ELSE 0 END), 0) AS direct_size
|
||||
FROM
|
||||
folder_hierarchy f
|
||||
LEFT JOIN
|
||||
teldrive.files c ON f.id = c.parent_id
|
||||
GROUP BY
|
||||
f.id, f.path
|
||||
)
|
||||
, cumulative_sizes AS (
|
||||
SELECT
|
||||
fs.id,
|
||||
SUM(fs2.direct_size) AS total_size
|
||||
FROM
|
||||
folder_sizes fs
|
||||
JOIN
|
||||
folder_sizes fs2 ON fs2.path @> fs.path
|
||||
GROUP BY
|
||||
fs.id
|
||||
)
|
||||
UPDATE teldrive.files f
|
||||
SET size = cs.total_size
|
||||
FROM cumulative_sizes cs
|
||||
WHERE f.id = cs.id AND f.type = 'folder';
|
||||
END;
|
||||
$procedure$
|
||||
;
|
||||
|
||||
ALTER TABLE teldrive.files
|
||||
DROP COLUMN old_id,
|
||||
DROP COLUMN old_parent_id;
|
||||
DROP FUNCTION IF EXISTS teldrive.generate_uid;
|
||||
-- +goose StatementEnd
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
type File struct {
|
||||
Id string `gorm:"type:text;primaryKey;default:generate_uid(16)"`
|
||||
Id string `gorm:"type:uuid;primaryKey;default:uuid7()"`
|
||||
Name string `gorm:"type:text;not null"`
|
||||
Type string `gorm:"type:text;not null"`
|
||||
MimeType string `gorm:"type:text;not null"`
|
||||
|
@ -18,7 +18,7 @@ type File struct {
|
|||
Encrypted bool `gorm:"default:false"`
|
||||
UserID int64 `gorm:"type:bigint;not null"`
|
||||
Status string `gorm:"type:text"`
|
||||
ParentID string `gorm:"type:text;index"`
|
||||
ParentID string `gorm:"type:uuid;index"`
|
||||
Parts datatypes.JSONSlice[schemas.Part] `gorm:"type:jsonb"`
|
||||
ChannelID *int64 `gorm:"type:bigint"`
|
||||
CreatedAt time.Time `gorm:"default:timezone('utc'::text, now())"`
|
||||
|
|
|
@ -94,7 +94,6 @@ func (as *AuthService) LogIn(c *gin.Context, session *schemas.TgSession) (*schem
|
|||
MimeType: "drive/folder",
|
||||
UserID: session.UserID,
|
||||
Status: "active",
|
||||
ParentID: "root",
|
||||
}
|
||||
if err := as.db.Clauses(clause.OnConflict{DoNothing: true}).Create(file).Error; err != nil {
|
||||
return err
|
||||
|
|
Loading…
Reference in a new issue