[server] db support for file versions

This commit is contained in:
Abhishek Shroff
2025-06-02 13:13:13 +05:30
parent d5e8f67d4a
commit af143bcb76
8 changed files with 66 additions and 20 deletions
@@ -50,6 +50,7 @@ func ResourceFromFS(r fs.Resource) Resource {
ContentSHA256: r.ContentSHA256(),
InheritedPermissions: r.InheritedPermissions(),
Grants: r.Grants(),
Versions: r.Versions(),
Links: r.Links(),
}
}
@@ -28,6 +28,7 @@ type Resource struct {
ContentSHA256 string `json:"c_sha256"`
InheritedPermissions json.RawMessage `json:"inherited_permissions,omitempty"`
Grants json.RawMessage `json:"grants"`
Versions json.RawMessage `json:"versions"`
Links json.RawMessage `json:"publinks"`
}
@@ -6,9 +6,6 @@ CREATE TABLE resources (
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted TIMESTAMP,
content_length BIGINT NOT NULL DEFAULT 0,
content_type TEXT NOT NULL DEFAULT '',
content_sha256 TEXT NOT NULL DEFAULT '',
permissions JSONB NOT NULL DEFAULT '{}',
grants JSONB NOT NULL DEFAULT '{}'
);
@@ -0,0 +1,24 @@
CREATE TABLE resource_versions(
id UUID PRIMARY KEY,
resource_id UUID NOT NULL REFERENCES resources(id),
created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
deleted TIMESTAMP,
size BIGINT NOT NULL,
mime_type TEXT NOT NULL,
sha256 TEXT NOT NULL
);
ALTER TABLE resources DROP COLUMN content_length;
ALTER TABLE resources DROP COLUMN content_type;
ALTER TABLE resources DROP COLUMN content_sha256;
---- create above / drop below ----
DROP TABLE resource_versions;
ALTER TABLE resources ADD COLUMN content_length BIGINT NOT NULL DEFAULT 0;
ALTER TABLE resources ADD COLUMN content_type TEXT NOT NULL DEFAULT '';
ALTER TABLE resources ADD COLUMN content_sha256 TEXT NOT NULL DEFAULT '';
+4 -2
View File
@@ -46,7 +46,6 @@ func (f filesystem) ResourceByPath(path string) (Resource, error) {
nodes := goqu.T("nodes").As("n")
r := goqu.T("resources").As("r")
p := goqu.T("resources").As("p")
l := goqu.T("publinks").As("l")
sub := pg.
Select(r.Col("id"), r.Col("parent"), nodes.Col("search"), goqu.L("n.depth + 1")).
From(r).
@@ -62,8 +61,11 @@ func (f filesystem) ResourceByPath(path string) (Resource, error) {
Where(r.Col("id").Eq(goqu.V(f.pathRoot))).
UnionAll(sub)
l := goqu.T("publinks").As("l")
v := goqu.T("resource_versions").As("v")
q := pg.Select(r.All(),
pg.Select(goqu.L(publinkFieldsQuery)).From(l).Where(l.Col("root").Eq(r.Col("id"))),
pg.Select(goqu.L(versionsQuery)).From(v).Where(v.Col("resource_id").Eq(r.Col("id"))),
pg.Select(goqu.L(publinksQuery)).From(l).Where(l.Col("root").Eq(r.Col("id"))),
pg.Select(goqu.L("CASE WHEN COALESCE(p.permissions[?::INT]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent", f.userID)),
pg.Select(goqu.L("COALESCE(p.permissions, '{}'::JSONB)")),
).
+4 -6
View File
@@ -23,6 +23,7 @@ type Resource struct {
contentSHA256 string
permissions []byte
grants []byte
versions []byte
links []byte
inheritedPermissions []byte
visibleParent pgtype.UUID
@@ -39,6 +40,7 @@ func (r Resource) ContentLength() int { return r.contentLength }
func (r Resource) ContentSHA256() string { return r.contentSHA256 }
func (r Resource) ContentType() string { return r.contentType }
func (r Resource) Grants() []byte { return r.grants }
func (r Resource) Versions() []byte { return r.versions }
func (r Resource) Links() []byte { return r.links }
func (r Resource) VisibleParentID() pgtype.UUID { return r.visibleParent }
func (r Resource) InheritedPermissions() []byte { return r.inheritedPermissions }
@@ -69,11 +71,9 @@ func (f filesystem) scanFullResource(row pgx.CollectableRow) (Resource, error) {
&r.created,
&r.modified,
&r.deleted,
&r.contentLength,
&r.contentType,
&r.contentSHA256,
&r.permissions,
&r.grants,
&r.versions,
&r.links,
&r.visibleParent,
&r.inheritedPermissions,
@@ -106,11 +106,9 @@ func (f filesystem) scanResourceWithoutParent(row pgx.CollectableRow) (Resource,
&r.created,
&r.modified,
&r.deleted,
&r.contentLength,
&r.contentType,
&r.contentSHA256,
&r.permissions,
&r.grants,
&r.versions,
&r.links,
)
if err != nil {
+29 -8
View File
@@ -11,21 +11,32 @@ import (
"github.com/jackc/pgx/v5/pgtype"
)
const publinkFieldsQuery = "COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT(" +
const publinksQuery = "COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT(" +
"'id',l.id," +
"'expires',EXTRACT(EPOCH FROM l.expires)::integer," +
"'protected',l.password_hash <> ''," +
"'access_limit',l.access_limit," +
"'accessed',l.accessed)), '[]'::JSONB)"
"'accessed',l.accessed" +
")), '[]'::JSONB)"
const versionsQuery = "COALESCE(JSONB_AGG(JSONB_BUILD_OBJECT(" +
"'id',v.id," +
"'created',EXTRACT(EPOCH FROM v.created)::integer," +
"'deleted',EXTRACT(EPOCH FROM v.deleted)::integer," +
"'mime_type',v.mime_type," +
"'size',v.size," +
"'sha256',v.sha256," +
")), '[]'::JSONB)"
const fullResourceQuery = `SELECT r.*,
(SELECT ` + publinkFieldsQuery + ` FROM publinks l WHERE l.root = r.id) AS links,
(SELECT ` + versionsQuery + ` FROM resource_versions v WHERE v.resource_id = r.id) AS versions,
(SELECT ` + publinksQuery + ` FROM publinks l WHERE l.root = r.id) AS links,
CASE WHEN COALESCE(p.permissions[@user_id::INT]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent,
COALESCE(p.permissions, '{}'::JSONB) AS inherited_permissions
FROM resources r LEFT JOIN resources p ON p.id = r.parent
`
func selectResourceTree(id uuid.UUID, excludeTreeRoot, includeDeleted, includeLinks bool, extraCols ...string) (exp.IdentifierExpression, exp.AliasedExpression, *goqu.SelectDataset) {
func selectResourceTree(id uuid.UUID, excludeTreeRoot, includeDeleted, includeSubQueries bool, extraCols ...string) (exp.IdentifierExpression, exp.AliasedExpression, *goqu.SelectDataset) {
t := goqu.T("resources")
r := t.As("r")
n := goqu.T("nodes").As("n")
@@ -64,11 +75,16 @@ func selectResourceTree(id uuid.UUID, excludeTreeRoot, includeDeleted, includeLi
idSelect = idSelect.Where(n.Col("depth").Gt(0))
}
l := goqu.T("publinks").As("l")
q := pg.
From(t)
if includeLinks {
q = q.Select(t.All(), pg.Select(goqu.L(publinkFieldsQuery)).From(l).Where(l.Col("root").Eq(t.Col("id"))))
if includeSubQueries {
v := goqu.T("resource_versions").As("v")
l := goqu.T("publinks").As("l")
q = q.Select(
r.All(),
pg.Select(goqu.L(versionsQuery)).From(v).Where(v.Col("resource_id").Eq(r.Col("id"))),
pg.Select(goqu.L(publinksQuery)).From(l).Where(l.Col("root").Eq(r.Col("id"))),
)
}
q = q.WithRecursive("nodes("+strings.Join(cols, ",")+")", base.UnionAll(rec)).
Where(t.Col("id").Eq(idSelect))
@@ -76,11 +92,16 @@ func selectResourceTree(id uuid.UUID, excludeTreeRoot, includeDeleted, includeLi
}
func selectDirectChildren(id uuid.UUID, deleted pgtype.Timestamp, includeDeleted bool) (exp.AliasedExpression, *goqu.SelectDataset) {
v := goqu.T("resource_versions").As("v")
l := goqu.T("publinks").As("l")
r := goqu.T("resources").As("r")
q := pg.
From(r).
Select(r.All(), pg.Select(goqu.L(publinkFieldsQuery)).From(l).Where(l.Col("root").Eq(r.Col("id")))).
Select(
r.All(),
pg.Select(goqu.L(versionsQuery)).From(v).Where(v.Col("resource_id").Eq(r.Col("id"))),
pg.Select(goqu.L(publinksQuery)).From(l).Where(l.Col("root").Eq(r.Col("id"))),
).
Where(r.Col("parent").Eq(goqu.V(id)))
if !includeDeleted {
q = q.Where(goqu.L("? IS NOT DISTINCT FROM ?", r.Col("deleted"), goqu.V(deleted)))
+3 -1
View File
@@ -15,13 +15,15 @@ func (f filesystem) TrashList(cursor string, n uint) ([]Resource, string, error)
t := goqu.T("trash")
r := goqu.T("resources").As("r")
p := goqu.T("resources").As("p")
v := goqu.T("resource_versions").As("v")
l := goqu.T("publinks").As("l")
q := pg.From(t).
Join(r, goqu.On(t.Col("id").Eq(r.Col("id")))).
Join(p, goqu.On(r.Col("parent").Eq(p.Col("id")))).
Select(
r.All(),
pg.Select(goqu.L(publinkFieldsQuery)).From(l).Where(l.Col("root").Eq(r.Col("id"))),
pg.Select(goqu.L(versionsQuery)).From(v).Where(v.Col("resource_id").Eq(r.Col("id"))),
pg.Select(goqu.L(publinksQuery)).From(l).Where(l.Col("root").Eq(r.Col("id"))),
// TODO: Always select p.id when fullAccess
pg.Select(goqu.L("CASE WHEN COALESCE(p.permissions[?::INT]::INTEGER, 0) <> 0 THEN p.id ELSE NULL END AS visible_parent", f.userID)),
pg.Select(goqu.L("COALESCE(p.permissions, '{}'::JSONB)")),