mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 13:23:28 +00:00
Rename Files to Objects (#396)
* backend rename complete * finalize Object refactor frontend + remove some exhaustive dep suppression + FIX INVALIDATE QUERY BUG
This commit is contained in:
parent
ed06e3051e
commit
cbd58ee438
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -66,3 +66,4 @@ examples/*/*.lock
|
||||||
|
|
||||||
/sdserver_data
|
/sdserver_data
|
||||||
.spacedrive
|
.spacedrive
|
||||||
|
dev.db-journal
|
|
@ -14,7 +14,7 @@ const placeholderFileItems: FilePath[] = [
|
||||||
date_indexed: '2020-01-01T00:00:00.000Z',
|
date_indexed: '2020-01-01T00:00:00.000Z',
|
||||||
date_modified: '2020-01-01T00:00:00.000Z',
|
date_modified: '2020-01-01T00:00:00.000Z',
|
||||||
extension: '',
|
extension: '',
|
||||||
file_id: 1,
|
object_id: 1,
|
||||||
id: 1,
|
id: 1,
|
||||||
location_id: 1,
|
location_id: 1,
|
||||||
materialized_path: '',
|
materialized_path: '',
|
||||||
|
@ -28,7 +28,7 @@ const placeholderFileItems: FilePath[] = [
|
||||||
date_indexed: '2020-01-01T00:00:00.000Z',
|
date_indexed: '2020-01-01T00:00:00.000Z',
|
||||||
date_modified: '2020-01-01T00:00:00.000Z',
|
date_modified: '2020-01-01T00:00:00.000Z',
|
||||||
extension: '',
|
extension: '',
|
||||||
file_id: 2,
|
object_id: 2,
|
||||||
id: 2,
|
id: 2,
|
||||||
location_id: 2,
|
location_id: 2,
|
||||||
materialized_path: '',
|
materialized_path: '',
|
||||||
|
@ -42,7 +42,7 @@ const placeholderFileItems: FilePath[] = [
|
||||||
date_indexed: '2020-01-01T00:00:00.000Z',
|
date_indexed: '2020-01-01T00:00:00.000Z',
|
||||||
date_modified: '2020-01-01T00:00:00.000Z',
|
date_modified: '2020-01-01T00:00:00.000Z',
|
||||||
extension: 'tsx',
|
extension: 'tsx',
|
||||||
file_id: 3,
|
object_id: 3,
|
||||||
id: 3,
|
id: 3,
|
||||||
location_id: 3,
|
location_id: 3,
|
||||||
materialized_path: '',
|
materialized_path: '',
|
||||||
|
@ -56,7 +56,7 @@ const placeholderFileItems: FilePath[] = [
|
||||||
date_indexed: '2020-01-01T00:00:00.000Z',
|
date_indexed: '2020-01-01T00:00:00.000Z',
|
||||||
date_modified: '2020-01-01T00:00:00.000Z',
|
date_modified: '2020-01-01T00:00:00.000Z',
|
||||||
extension: 'vite',
|
extension: 'vite',
|
||||||
file_id: 4,
|
object_id: 4,
|
||||||
id: 4,
|
id: 4,
|
||||||
location_id: 4,
|
location_id: 4,
|
||||||
materialized_path: '',
|
materialized_path: '',
|
||||||
|
@ -70,7 +70,7 @@ const placeholderFileItems: FilePath[] = [
|
||||||
date_indexed: '2020-01-01T00:00:00.000Z',
|
date_indexed: '2020-01-01T00:00:00.000Z',
|
||||||
date_modified: '2020-01-01T00:00:00.000Z',
|
date_modified: '2020-01-01T00:00:00.000Z',
|
||||||
extension: 'docker',
|
extension: 'docker',
|
||||||
file_id: 5,
|
object_id: 5,
|
||||||
id: 5,
|
id: 5,
|
||||||
location_id: 5,
|
location_id: 5,
|
||||||
materialized_path: '',
|
materialized_path: '',
|
||||||
|
@ -90,7 +90,7 @@ export interface DeviceProps {
|
||||||
|
|
||||||
const Device = ({ name, locations, size, type }: DeviceProps) => {
|
const Device = ({ name, locations, size, type }: DeviceProps) => {
|
||||||
return (
|
return (
|
||||||
<View style={tw`bg-gray-600 border rounded-md border-gray-550 my-2`}>
|
<View style={tw`my-2 bg-gray-600 border rounded-md border-gray-550`}>
|
||||||
<View style={tw`flex flex-row items-center px-3.5 pt-3 pb-2`}>
|
<View style={tw`flex flex-row items-center px-3.5 pt-3 pb-2`}>
|
||||||
<View style={tw`flex flex-row items-center`}>
|
<View style={tw`flex flex-row items-center`}>
|
||||||
{type === 'phone' && (
|
{type === 'phone' && (
|
||||||
|
@ -107,7 +107,7 @@ const Device = ({ name, locations, size, type }: DeviceProps) => {
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
{/* Size */}
|
{/* Size */}
|
||||||
<Text style={tw`font-semibold text-sm ml-2 text-gray-400`}>{size}</Text>
|
<Text style={tw`ml-2 text-sm font-semibold text-gray-400`}>{size}</Text>
|
||||||
</View>
|
</View>
|
||||||
{/* Locations/Files TODO: Maybe use FlashList? */}
|
{/* Locations/Files TODO: Maybe use FlashList? */}
|
||||||
<FlatList
|
<FlatList
|
||||||
|
|
|
@ -53,11 +53,9 @@ export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" }
|
||||||
|
|
||||||
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
|
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
|
||||||
|
|
||||||
export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null } | { type: "Object" } & { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, paths: Array<FilePath> }
|
export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, object: Object | null } | { type: "Object" } & { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, file_paths: Array<FilePath> }
|
||||||
|
|
||||||
export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string }
|
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string }
|
||||||
|
|
||||||
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string }
|
|
||||||
|
|
||||||
export interface GenerateThumbsForLocationArgs { id: number, path: string }
|
export interface GenerateThumbsForLocationArgs { id: number, path: string }
|
||||||
|
|
||||||
|
@ -93,17 +91,19 @@ export interface NodeConfig { version: string | null, id: string, name: string,
|
||||||
|
|
||||||
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
|
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
|
||||||
|
|
||||||
|
export interface Object { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string }
|
||||||
|
|
||||||
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
|
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
|
||||||
|
|
||||||
export interface SetFavoriteArgs { id: number, favorite: boolean }
|
export interface SetFavoriteArgs { id: number, favorite: boolean }
|
||||||
|
|
||||||
export interface SetNoteArgs { id: number, note: string | null }
|
export interface SetNoteArgs { id: number, note: string | null }
|
||||||
|
|
||||||
export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
|
export interface Statistics { id: number, date_captured: string, total_object_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
|
||||||
|
|
||||||
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string }
|
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_objects: number | null, redundancy_goal: number | null, date_created: string, date_modified: string }
|
||||||
|
|
||||||
export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean }
|
export interface TagAssignArgs { object_id: number, tag_id: number, unassign: boolean }
|
||||||
|
|
||||||
export interface TagCreateArgs { name: string, color: string }
|
export interface TagCreateArgs { name: string, color: string }
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,5 @@
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "_migrations" (
|
CREATE TABLE "sync_event" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
|
||||||
"name" TEXT NOT NULL,
|
|
||||||
"checksum" TEXT NOT NULL,
|
|
||||||
"steps_applied" INTEGER NOT NULL DEFAULT 0,
|
|
||||||
"applied_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
|
||||||
);
|
|
||||||
|
|
||||||
-- CreateTable
|
|
||||||
CREATE TABLE "sync_events" (
|
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"node_id" INTEGER NOT NULL,
|
"node_id" INTEGER NOT NULL,
|
||||||
"timestamp" TEXT NOT NULL,
|
"timestamp" TEXT NOT NULL,
|
||||||
|
@ -16,14 +7,14 @@ CREATE TABLE "sync_events" (
|
||||||
"kind" INTEGER NOT NULL,
|
"kind" INTEGER NOT NULL,
|
||||||
"column" TEXT,
|
"column" TEXT,
|
||||||
"value" TEXT NOT NULL,
|
"value" TEXT NOT NULL,
|
||||||
CONSTRAINT "sync_events_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
CONSTRAINT "sync_event_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "statistics" (
|
CREATE TABLE "statistics" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"date_captured" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_captured" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"total_file_count" INTEGER NOT NULL DEFAULT 0,
|
"total_object_count" INTEGER NOT NULL DEFAULT 0,
|
||||||
"library_db_size" TEXT NOT NULL DEFAULT '0',
|
"library_db_size" TEXT NOT NULL DEFAULT '0',
|
||||||
"total_bytes_used" TEXT NOT NULL DEFAULT '0',
|
"total_bytes_used" TEXT NOT NULL DEFAULT '0',
|
||||||
"total_bytes_capacity" TEXT NOT NULL DEFAULT '0',
|
"total_bytes_capacity" TEXT NOT NULL DEFAULT '0',
|
||||||
|
@ -33,7 +24,7 @@ CREATE TABLE "statistics" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "nodes" (
|
CREATE TABLE "node" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"pub_id" BLOB NOT NULL,
|
"pub_id" BLOB NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
@ -45,7 +36,7 @@ CREATE TABLE "nodes" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "volumes" (
|
CREATE TABLE "volume" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"node_id" INTEGER NOT NULL,
|
"node_id" INTEGER NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
@ -59,7 +50,7 @@ CREATE TABLE "volumes" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "locations" (
|
CREATE TABLE "location" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"pub_id" BLOB NOT NULL,
|
"pub_id" BLOB NOT NULL,
|
||||||
"node_id" INTEGER NOT NULL,
|
"node_id" INTEGER NOT NULL,
|
||||||
|
@ -73,11 +64,11 @@ CREATE TABLE "locations" (
|
||||||
"is_online" BOOLEAN NOT NULL DEFAULT true,
|
"is_online" BOOLEAN NOT NULL DEFAULT true,
|
||||||
"is_archived" BOOLEAN NOT NULL DEFAULT false,
|
"is_archived" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
CONSTRAINT "locations_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
CONSTRAINT "location_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "files" (
|
CREATE TABLE "object" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"cas_id" TEXT NOT NULL,
|
"cas_id" TEXT NOT NULL,
|
||||||
"integrity_checksum" TEXT,
|
"integrity_checksum" TEXT,
|
||||||
|
@ -97,18 +88,18 @@ CREATE TABLE "files" (
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"date_indexed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_indexed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
CONSTRAINT "files_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "keys" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
CONSTRAINT "object_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "key" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "file_paths" (
|
CREATE TABLE "file_path" (
|
||||||
"id" INTEGER NOT NULL,
|
"id" INTEGER NOT NULL,
|
||||||
"is_dir" BOOLEAN NOT NULL DEFAULT false,
|
"is_dir" BOOLEAN NOT NULL DEFAULT false,
|
||||||
"location_id" INTEGER NOT NULL,
|
"location_id" INTEGER NOT NULL,
|
||||||
"materialized_path" TEXT NOT NULL,
|
"materialized_path" TEXT NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"extension" TEXT,
|
"extension" TEXT,
|
||||||
"file_id" INTEGER,
|
"object_id" INTEGER,
|
||||||
"parent_id" INTEGER,
|
"parent_id" INTEGER,
|
||||||
"key_id" INTEGER,
|
"key_id" INTEGER,
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
@ -116,19 +107,19 @@ CREATE TABLE "file_paths" (
|
||||||
"date_indexed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_indexed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
PRIMARY KEY ("location_id", "id"),
|
PRIMARY KEY ("location_id", "id"),
|
||||||
CONSTRAINT "file_paths_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
CONSTRAINT "file_path_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
CONSTRAINT "file_paths_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
CONSTRAINT "file_path_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
CONSTRAINT "file_paths_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "keys" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
CONSTRAINT "file_path_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "key" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "file_conflicts" (
|
CREATE TABLE "file_conflict" (
|
||||||
"original_file_id" INTEGER NOT NULL,
|
"original_object_id" INTEGER NOT NULL,
|
||||||
"detactched_file_id" INTEGER NOT NULL
|
"detactched_object_id" INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "keys" (
|
CREATE TABLE "key" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"checksum" TEXT NOT NULL,
|
"checksum" TEXT NOT NULL,
|
||||||
"name" TEXT,
|
"name" TEXT,
|
||||||
|
@ -150,34 +141,34 @@ CREATE TABLE "media_data" (
|
||||||
"duration_seconds" INTEGER,
|
"duration_seconds" INTEGER,
|
||||||
"codecs" TEXT,
|
"codecs" TEXT,
|
||||||
"streams" INTEGER,
|
"streams" INTEGER,
|
||||||
CONSTRAINT "media_data_id_fkey" FOREIGN KEY ("id") REFERENCES "files" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
CONSTRAINT "media_data_id_fkey" FOREIGN KEY ("id") REFERENCES "object" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "tags" (
|
CREATE TABLE "tag" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"pub_id" BLOB NOT NULL,
|
"pub_id" BLOB NOT NULL,
|
||||||
"name" TEXT,
|
"name" TEXT,
|
||||||
"color" TEXT,
|
"color" TEXT,
|
||||||
"total_files" INTEGER DEFAULT 0,
|
"total_objects" INTEGER DEFAULT 0,
|
||||||
"redundancy_goal" INTEGER DEFAULT 1,
|
"redundancy_goal" INTEGER DEFAULT 1,
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "tags_on_file" (
|
CREATE TABLE "tag_on_object" (
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"tag_id" INTEGER NOT NULL,
|
"tag_id" INTEGER NOT NULL,
|
||||||
"file_id" INTEGER NOT NULL,
|
"object_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
PRIMARY KEY ("tag_id", "file_id"),
|
PRIMARY KEY ("tag_id", "object_id"),
|
||||||
CONSTRAINT "tags_on_file_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tags" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
CONSTRAINT "tag_on_object_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tag" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
||||||
CONSTRAINT "tags_on_file_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
CONSTRAINT "tag_on_object_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "labels" (
|
CREATE TABLE "label" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"pub_id" BLOB NOT NULL,
|
"pub_id" BLOB NOT NULL,
|
||||||
"name" TEXT,
|
"name" TEXT,
|
||||||
|
@ -186,18 +177,18 @@ CREATE TABLE "labels" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "label_on_file" (
|
CREATE TABLE "label_on_object" (
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"label_id" INTEGER NOT NULL,
|
"label_id" INTEGER NOT NULL,
|
||||||
"file_id" INTEGER NOT NULL,
|
"object_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
PRIMARY KEY ("label_id", "file_id"),
|
PRIMARY KEY ("label_id", "object_id"),
|
||||||
CONSTRAINT "label_on_file_label_id_fkey" FOREIGN KEY ("label_id") REFERENCES "labels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
CONSTRAINT "label_on_object_label_id_fkey" FOREIGN KEY ("label_id") REFERENCES "label" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
||||||
CONSTRAINT "label_on_file_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
CONSTRAINT "label_on_object_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "spaces" (
|
CREATE TABLE "space" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"pub_id" BLOB NOT NULL,
|
"pub_id" BLOB NOT NULL,
|
||||||
"name" TEXT,
|
"name" TEXT,
|
||||||
|
@ -207,18 +198,18 @@ CREATE TABLE "spaces" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "file_in_space" (
|
CREATE TABLE "object_in_space" (
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"space_id" INTEGER NOT NULL,
|
"space_id" INTEGER NOT NULL,
|
||||||
"file_id" INTEGER NOT NULL,
|
"object_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
PRIMARY KEY ("space_id", "file_id"),
|
PRIMARY KEY ("space_id", "object_id"),
|
||||||
CONSTRAINT "file_in_space_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "spaces" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
CONSTRAINT "object_in_space_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "space" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
||||||
CONSTRAINT "file_in_space_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
CONSTRAINT "object_in_space_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "jobs" (
|
CREATE TABLE "job" (
|
||||||
"id" BLOB NOT NULL PRIMARY KEY,
|
"id" BLOB NOT NULL PRIMARY KEY,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
"node_id" INTEGER NOT NULL,
|
"node_id" INTEGER NOT NULL,
|
||||||
|
@ -231,11 +222,11 @@ CREATE TABLE "jobs" (
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"seconds_elapsed" INTEGER NOT NULL DEFAULT 0,
|
"seconds_elapsed" INTEGER NOT NULL DEFAULT 0,
|
||||||
CONSTRAINT "jobs_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
CONSTRAINT "job_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "albums" (
|
CREATE TABLE "album" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"pub_id" BLOB NOT NULL,
|
"pub_id" BLOB NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
@ -245,29 +236,29 @@ CREATE TABLE "albums" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "files_in_albums" (
|
CREATE TABLE "object_in_album" (
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"album_id" INTEGER NOT NULL,
|
"album_id" INTEGER NOT NULL,
|
||||||
"file_id" INTEGER NOT NULL,
|
"object_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
PRIMARY KEY ("album_id", "file_id"),
|
PRIMARY KEY ("album_id", "object_id"),
|
||||||
CONSTRAINT "files_in_albums_album_id_fkey" FOREIGN KEY ("album_id") REFERENCES "albums" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
CONSTRAINT "object_in_album_album_id_fkey" FOREIGN KEY ("album_id") REFERENCES "album" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
||||||
CONSTRAINT "files_in_albums_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
CONSTRAINT "object_in_album_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "comments" (
|
CREATE TABLE "comment" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"pub_id" BLOB NOT NULL,
|
"pub_id" BLOB NOT NULL,
|
||||||
"content" TEXT NOT NULL,
|
"content" TEXT NOT NULL,
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"file_id" INTEGER,
|
"object_id" INTEGER,
|
||||||
CONSTRAINT "comments_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
CONSTRAINT "comment_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE SET NULL ON UPDATE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "indexer_rules" (
|
CREATE TABLE "indexer_rule" (
|
||||||
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
"id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
"kind" INTEGER NOT NULL,
|
"kind" INTEGER NOT NULL,
|
||||||
"name" TEXT NOT NULL,
|
"name" TEXT NOT NULL,
|
||||||
|
@ -277,60 +268,57 @@ CREATE TABLE "indexer_rules" (
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateTable
|
-- CreateTable
|
||||||
CREATE TABLE "indexer_rules_in_location" (
|
CREATE TABLE "indexer_rule_in_location" (
|
||||||
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
"date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
"location_id" INTEGER NOT NULL,
|
"location_id" INTEGER NOT NULL,
|
||||||
"indexer_rule_id" INTEGER NOT NULL,
|
"indexer_rule_id" INTEGER NOT NULL,
|
||||||
|
|
||||||
PRIMARY KEY ("location_id", "indexer_rule_id"),
|
PRIMARY KEY ("location_id", "indexer_rule_id"),
|
||||||
CONSTRAINT "indexer_rules_in_location_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
CONSTRAINT "indexer_rule_in_location_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION,
|
||||||
CONSTRAINT "indexer_rules_in_location_indexer_rule_id_fkey" FOREIGN KEY ("indexer_rule_id") REFERENCES "indexer_rules" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
CONSTRAINT "indexer_rule_in_location_indexer_rule_id_fkey" FOREIGN KEY ("indexer_rule_id") REFERENCES "indexer_rule" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION
|
||||||
);
|
);
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "_migrations_checksum_key" ON "_migrations"("checksum");
|
CREATE UNIQUE INDEX "node_pub_id_key" ON "node"("pub_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "nodes_pub_id_key" ON "nodes"("pub_id");
|
CREATE UNIQUE INDEX "volume_node_id_mount_point_name_key" ON "volume"("node_id", "mount_point", "name");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "volumes_node_id_mount_point_name_key" ON "volumes"("node_id", "mount_point", "name");
|
CREATE UNIQUE INDEX "location_pub_id_key" ON "location"("pub_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "locations_pub_id_key" ON "locations"("pub_id");
|
CREATE UNIQUE INDEX "object_cas_id_key" ON "object"("cas_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "files_cas_id_key" ON "files"("cas_id");
|
CREATE UNIQUE INDEX "object_integrity_checksum_key" ON "object"("integrity_checksum");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "files_integrity_checksum_key" ON "files"("integrity_checksum");
|
CREATE INDEX "file_path_location_id_idx" ON "file_path"("location_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE INDEX "file_paths_location_id_idx" ON "file_paths"("location_id");
|
CREATE UNIQUE INDEX "file_path_location_id_materialized_path_name_extension_key" ON "file_path"("location_id", "materialized_path", "name", "extension");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "file_paths_location_id_materialized_path_name_extension_key" ON "file_paths"("location_id", "materialized_path", "name", "extension");
|
CREATE UNIQUE INDEX "file_conflict_original_object_id_key" ON "file_conflict"("original_object_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "file_conflicts_original_file_id_key" ON "file_conflicts"("original_file_id");
|
CREATE UNIQUE INDEX "file_conflict_detactched_object_id_key" ON "file_conflict"("detactched_object_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "file_conflicts_detactched_file_id_key" ON "file_conflicts"("detactched_file_id");
|
CREATE UNIQUE INDEX "key_checksum_key" ON "key"("checksum");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "keys_checksum_key" ON "keys"("checksum");
|
CREATE UNIQUE INDEX "tag_pub_id_key" ON "tag"("pub_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "tags_pub_id_key" ON "tags"("pub_id");
|
CREATE UNIQUE INDEX "label_pub_id_key" ON "label"("pub_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "labels_pub_id_key" ON "labels"("pub_id");
|
CREATE UNIQUE INDEX "space_pub_id_key" ON "space"("pub_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "spaces_pub_id_key" ON "spaces"("pub_id");
|
CREATE UNIQUE INDEX "album_pub_id_key" ON "album"("pub_id");
|
||||||
|
|
||||||
-- CreateIndex
|
-- CreateIndex
|
||||||
CREATE UNIQUE INDEX "albums_pub_id_key" ON "albums"("pub_id");
|
CREATE UNIQUE INDEX "comment_pub_id_key" ON "comment"("pub_id");
|
||||||
|
|
||||||
-- CreateIndex
|
|
||||||
CREATE UNIQUE INDEX "comments_pub_id_key" ON "comments"("pub_id");
|
|
|
@ -8,16 +8,6 @@ generator client {
|
||||||
output = "../src/prisma.rs"
|
output = "../src/prisma.rs"
|
||||||
}
|
}
|
||||||
|
|
||||||
model Migration {
|
|
||||||
id Int @id @default(autoincrement())
|
|
||||||
name String
|
|
||||||
checksum String @unique
|
|
||||||
steps_applied Int @default(0)
|
|
||||||
applied_at DateTime @default(now())
|
|
||||||
|
|
||||||
@@map("_migrations")
|
|
||||||
}
|
|
||||||
|
|
||||||
model SyncEvent {
|
model SyncEvent {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
node_id Int
|
node_id Int
|
||||||
|
@ -33,13 +23,13 @@ model SyncEvent {
|
||||||
|
|
||||||
node Node @relation(fields: [node_id], references: [id])
|
node Node @relation(fields: [node_id], references: [id])
|
||||||
|
|
||||||
@@map("sync_events")
|
@@map("sync_event")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Statistics {
|
model Statistics {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
date_captured DateTime @default(now())
|
date_captured DateTime @default(now())
|
||||||
total_file_count Int @default(0)
|
total_object_count Int @default(0)
|
||||||
library_db_size String @default("0")
|
library_db_size String @default("0")
|
||||||
total_bytes_used String @default("0")
|
total_bytes_used String @default("0")
|
||||||
total_bytes_capacity String @default("0")
|
total_bytes_capacity String @default("0")
|
||||||
|
@ -65,7 +55,7 @@ model Node {
|
||||||
|
|
||||||
Location Location[]
|
Location Location[]
|
||||||
|
|
||||||
@@map("nodes")
|
@@map("node")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Volume {
|
model Volume {
|
||||||
|
@ -81,7 +71,7 @@ model Volume {
|
||||||
date_modified DateTime @default(now())
|
date_modified DateTime @default(now())
|
||||||
|
|
||||||
@@unique([node_id, mount_point, name])
|
@@unique([node_id, mount_point, name])
|
||||||
@@map("volumes")
|
@@map("volume")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Location {
|
model Location {
|
||||||
|
@ -103,10 +93,10 @@ model Location {
|
||||||
file_paths FilePath[]
|
file_paths FilePath[]
|
||||||
indexer_rules IndexerRulesInLocation[]
|
indexer_rules IndexerRulesInLocation[]
|
||||||
|
|
||||||
@@map("locations")
|
@@map("location")
|
||||||
}
|
}
|
||||||
|
|
||||||
model File {
|
model Object {
|
||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
// content addressable storage id - sha256 sampled checksum
|
// content addressable storage id - sha256 sampled checksum
|
||||||
cas_id String @unique
|
cas_id String @unique
|
||||||
|
@ -118,11 +108,11 @@ model File {
|
||||||
kind Int @default(0)
|
kind Int @default(0)
|
||||||
size_in_bytes String
|
size_in_bytes String
|
||||||
key_id Int?
|
key_id Int?
|
||||||
// handy ways to mark a file
|
// handy ways to mark an object
|
||||||
hidden Boolean @default(false)
|
hidden Boolean @default(false)
|
||||||
favorite Boolean @default(false)
|
favorite Boolean @default(false)
|
||||||
important Boolean @default(false)
|
important Boolean @default(false)
|
||||||
// if we have generated preview media for this file
|
// if we have generated preview media for this object
|
||||||
has_thumbnail Boolean @default(false)
|
has_thumbnail Boolean @default(false)
|
||||||
has_thumbstrip Boolean @default(false)
|
has_thumbstrip Boolean @default(false)
|
||||||
has_video_preview Boolean @default(false)
|
has_video_preview Boolean @default(false)
|
||||||
|
@ -130,24 +120,24 @@ model File {
|
||||||
ipfs_id String?
|
ipfs_id String?
|
||||||
// plain text note
|
// plain text note
|
||||||
note String?
|
note String?
|
||||||
// the original known creation date of this file
|
// the original known creation date of this object
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
// the last time this file was modified
|
// the last time this object was modified
|
||||||
date_modified DateTime @default(now())
|
date_modified DateTime @default(now())
|
||||||
// when this file was first indexed
|
// when this object was first indexed
|
||||||
date_indexed DateTime @default(now())
|
date_indexed DateTime @default(now())
|
||||||
|
|
||||||
tags TagOnFile[]
|
tags TagOnObject[]
|
||||||
labels LabelOnFile[]
|
labels LabelOnObject[]
|
||||||
albums FileInAlbum[]
|
albums ObjectInAlbum[]
|
||||||
spaces FileInSpace[]
|
spaces ObjectInSpace[]
|
||||||
paths FilePath[]
|
file_paths FilePath[]
|
||||||
comments Comment[]
|
comments Comment[]
|
||||||
media_data MediaData?
|
media_data MediaData?
|
||||||
|
|
||||||
key Key? @relation(fields: [key_id], references: [id])
|
key Key? @relation(fields: [key_id], references: [id])
|
||||||
|
|
||||||
@@map("files")
|
@@map("object")
|
||||||
}
|
}
|
||||||
|
|
||||||
model FilePath {
|
model FilePath {
|
||||||
|
@ -160,8 +150,8 @@ model FilePath {
|
||||||
// the name and extension
|
// the name and extension
|
||||||
name String
|
name String
|
||||||
extension String?
|
extension String?
|
||||||
// the unique File for this file path
|
// the unique Object for this file path
|
||||||
file_id Int?
|
object_id Int?
|
||||||
// the parent in the file tree
|
// the parent in the file tree
|
||||||
parent_id Int?
|
parent_id Int?
|
||||||
key_id Int? // replacement for encryption
|
key_id Int? // replacement for encryption
|
||||||
|
@ -172,7 +162,7 @@ model FilePath {
|
||||||
date_modified DateTime @default(now())
|
date_modified DateTime @default(now())
|
||||||
date_indexed DateTime @default(now())
|
date_indexed DateTime @default(now())
|
||||||
|
|
||||||
file File? @relation(fields: [file_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
object Object? @relation(fields: [object_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
location Location? @relation(fields: [location_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
location Location? @relation(fields: [location_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
|
|
||||||
// NOTE: this self relation for the file tree was causing SQLite to go to forever bed, disabling until workaround
|
// NOTE: this self relation for the file tree was causing SQLite to go to forever bed, disabling until workaround
|
||||||
|
@ -184,15 +174,15 @@ model FilePath {
|
||||||
@@id([location_id, id])
|
@@id([location_id, id])
|
||||||
@@unique([location_id, materialized_path, name, extension])
|
@@unique([location_id, materialized_path, name, extension])
|
||||||
@@index([location_id])
|
@@index([location_id])
|
||||||
@@map("file_paths")
|
@@map("file_path")
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is a conflicting cas_id, the conficting file should be updated to have a larger cas_id as the field is unique, however this record is kept to tell the indexer (upon discovering this CAS) that there is alternate versions of the file and to check by a full integrity hash to define for which to associate with.
|
// if there is a conflicting cas_id, the conficting file should be updated to have a larger cas_id as the field is unique, however this record is kept to tell the indexer (upon discovering this CAS) that there is alternate versions of the file and to check by a full integrity hash to define for which to associate with.
|
||||||
model FileConflict {
|
model FileConflict {
|
||||||
original_file_id Int @unique
|
original_object_id Int @unique
|
||||||
detactched_file_id Int @unique
|
detactched_object_id Int @unique
|
||||||
|
|
||||||
@@map("file_conflicts")
|
@@map("file_conflict")
|
||||||
}
|
}
|
||||||
|
|
||||||
// keys allow us to know exactly which files can be decrypted with a given key
|
// keys allow us to know exactly which files can be decrypted with a given key
|
||||||
|
@ -207,10 +197,10 @@ model Key {
|
||||||
// so we know which algorithm to use, can be null if user must select
|
// so we know which algorithm to use, can be null if user must select
|
||||||
algorithm Int? @default(0)
|
algorithm Int? @default(0)
|
||||||
|
|
||||||
files File[]
|
objects Object[]
|
||||||
file_paths FilePath[]
|
file_paths FilePath[]
|
||||||
|
|
||||||
@@map("keys")
|
@@map("key")
|
||||||
}
|
}
|
||||||
|
|
||||||
model MediaData {
|
model MediaData {
|
||||||
|
@ -227,8 +217,8 @@ model MediaData {
|
||||||
codecs String? // eg: "h264,acc"
|
codecs String? // eg: "h264,acc"
|
||||||
streams Int?
|
streams Int?
|
||||||
|
|
||||||
// change this relation to File after testing
|
// change this relation to Object after testing
|
||||||
files File? @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
objects Object? @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
|
|
||||||
@@map("media_data")
|
@@map("media_data")
|
||||||
}
|
}
|
||||||
|
@ -238,27 +228,27 @@ model Tag {
|
||||||
pub_id Bytes @unique
|
pub_id Bytes @unique
|
||||||
name String?
|
name String?
|
||||||
color String?
|
color String?
|
||||||
total_files Int? @default(0)
|
total_objects Int? @default(0)
|
||||||
redundancy_goal Int? @default(1)
|
redundancy_goal Int? @default(1)
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
date_modified DateTime @default(now())
|
date_modified DateTime @default(now())
|
||||||
|
|
||||||
tag_files TagOnFile[]
|
tag_objects TagOnObject[]
|
||||||
|
|
||||||
@@map("tags")
|
@@map("tag")
|
||||||
}
|
}
|
||||||
|
|
||||||
model TagOnFile {
|
model TagOnObject {
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
|
|
||||||
tag_id Int
|
tag_id Int
|
||||||
tag Tag @relation(fields: [tag_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
tag Tag @relation(fields: [tag_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
file_id Int
|
object_id Int
|
||||||
file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
@@id([tag_id, file_id])
|
@@id([tag_id, object_id])
|
||||||
@@map("tags_on_file")
|
@@map("tag_on_object")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Label {
|
model Label {
|
||||||
|
@ -268,22 +258,22 @@ model Label {
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
date_modified DateTime @default(now())
|
date_modified DateTime @default(now())
|
||||||
|
|
||||||
label_files LabelOnFile[]
|
label_objects LabelOnObject[]
|
||||||
|
|
||||||
@@map("labels")
|
@@map("label")
|
||||||
}
|
}
|
||||||
|
|
||||||
model LabelOnFile {
|
model LabelOnObject {
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
|
|
||||||
label_id Int
|
label_id Int
|
||||||
label Label @relation(fields: [label_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
label Label @relation(fields: [label_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
file_id Int
|
object_id Int
|
||||||
file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
@@id([label_id, file_id])
|
@@id([label_id, object_id])
|
||||||
@@map("label_on_file")
|
@@map("label_on_object")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Space {
|
model Space {
|
||||||
|
@ -294,22 +284,22 @@ model Space {
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
date_modified DateTime @default(now())
|
date_modified DateTime @default(now())
|
||||||
|
|
||||||
files FileInSpace[]
|
objects ObjectInSpace[]
|
||||||
|
|
||||||
@@map("spaces")
|
@@map("space")
|
||||||
}
|
}
|
||||||
|
|
||||||
model FileInSpace {
|
model ObjectInSpace {
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
|
|
||||||
space_id Int
|
space_id Int
|
||||||
space Space @relation(fields: [space_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
space Space @relation(fields: [space_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
file_id Int
|
object_id Int
|
||||||
file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
@@id([space_id, file_id])
|
@@id([space_id, object_id])
|
||||||
@@map("file_in_space")
|
@@map("object_in_space")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Job {
|
model Job {
|
||||||
|
@ -329,7 +319,7 @@ model Job {
|
||||||
|
|
||||||
nodes Node @relation(fields: [node_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
nodes Node @relation(fields: [node_id], references: [id], onDelete: Cascade, onUpdate: Cascade)
|
||||||
|
|
||||||
@@map("jobs")
|
@@map("job")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Album {
|
model Album {
|
||||||
|
@ -341,22 +331,22 @@ model Album {
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
date_modified DateTime @default(now())
|
date_modified DateTime @default(now())
|
||||||
|
|
||||||
files FileInAlbum[]
|
objects ObjectInAlbum[]
|
||||||
|
|
||||||
@@map("albums")
|
@@map("album")
|
||||||
}
|
}
|
||||||
|
|
||||||
model FileInAlbum {
|
model ObjectInAlbum {
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
|
|
||||||
album_id Int
|
album_id Int
|
||||||
album Album @relation(fields: [album_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
album Album @relation(fields: [album_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
file_id Int
|
object_id Int
|
||||||
file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
@@id([album_id, file_id])
|
@@id([album_id, object_id])
|
||||||
@@map("files_in_albums")
|
@@map("object_in_album")
|
||||||
}
|
}
|
||||||
|
|
||||||
model Comment {
|
model Comment {
|
||||||
|
@ -365,10 +355,10 @@ model Comment {
|
||||||
content String
|
content String
|
||||||
date_created DateTime @default(now())
|
date_created DateTime @default(now())
|
||||||
date_modified DateTime @default(now())
|
date_modified DateTime @default(now())
|
||||||
file_id Int?
|
object_id Int?
|
||||||
file File? @relation(fields: [file_id], references: [id])
|
object Object? @relation(fields: [object_id], references: [id])
|
||||||
|
|
||||||
@@map("comments")
|
@@map("comment")
|
||||||
}
|
}
|
||||||
|
|
||||||
model IndexerRule {
|
model IndexerRule {
|
||||||
|
@ -381,7 +371,7 @@ model IndexerRule {
|
||||||
|
|
||||||
locations IndexerRulesInLocation[]
|
locations IndexerRulesInLocation[]
|
||||||
|
|
||||||
@@map("indexer_rules")
|
@@map("indexer_rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
model IndexerRulesInLocation {
|
model IndexerRulesInLocation {
|
||||||
|
@ -394,5 +384,5 @@ model IndexerRulesInLocation {
|
||||||
indexer_rule IndexerRule @relation(fields: [indexer_rule_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
indexer_rule IndexerRule @relation(fields: [indexer_rule_id], references: [id], onDelete: NoAction, onUpdate: NoAction)
|
||||||
|
|
||||||
@@id([location_id, indexer_rule_id])
|
@@id([location_id, indexer_rule_id])
|
||||||
@@map("indexer_rules_in_location")
|
@@map("indexer_rule_in_location")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{invalidate_query, prisma::file};
|
use crate::{invalidate_query, prisma::object};
|
||||||
|
|
||||||
use rspc::Type;
|
use rspc::Type;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
@ -26,8 +26,11 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
.library_mutation("setNote", |_, args: SetNoteArgs, library| async move {
|
.library_mutation("setNote", |_, args: SetNoteArgs, library| async move {
|
||||||
library
|
library
|
||||||
.db
|
.db
|
||||||
.file()
|
.object()
|
||||||
.update(file::id::equals(args.id), vec![file::note::set(args.note)])
|
.update(
|
||||||
|
object::id::equals(args.id),
|
||||||
|
vec![object::note::set(args.note)],
|
||||||
|
)
|
||||||
.exec()
|
.exec()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -40,10 +43,10 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
|_, args: SetFavoriteArgs, library| async move {
|
|_, args: SetFavoriteArgs, library| async move {
|
||||||
library
|
library
|
||||||
.db
|
.db
|
||||||
.file()
|
.object()
|
||||||
.update(
|
.update(
|
||||||
file::id::equals(args.id),
|
object::id::equals(args.id),
|
||||||
vec![file::favorite::set(args.favorite)],
|
vec![object::favorite::set(args.favorite)],
|
||||||
)
|
)
|
||||||
.exec()
|
.exec()
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -56,8 +59,8 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
.library_mutation("delete", |_, id: i32, library| async move {
|
.library_mutation("delete", |_, id: i32, library| async move {
|
||||||
library
|
library
|
||||||
.db
|
.db
|
||||||
.file()
|
.object()
|
||||||
.delete(file::id::equals(id))
|
.delete(object::id::equals(id))
|
||||||
.exec()
|
.exec()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
let params = vec![
|
let params = vec![
|
||||||
id::set(1), // Each library is a database so only one of these ever exists
|
id::set(1), // Each library is a database so only one of these ever exists
|
||||||
date_captured::set(Utc::now().into()),
|
date_captured::set(Utc::now().into()),
|
||||||
total_file_count::set(0),
|
total_object_count::set(0),
|
||||||
library_db_size::set(library_db_size.to_string()),
|
library_db_size::set(library_db_size.to_string()),
|
||||||
total_bytes_used::set(0.to_string()),
|
total_bytes_used::set(0.to_string()),
|
||||||
total_bytes_capacity::set(total_capacity.to_string()),
|
total_bytes_capacity::set(total_capacity.to_string()),
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
scan_location, LocationCreateArgs, LocationError, LocationUpdateArgs,
|
scan_location, LocationCreateArgs, LocationError, LocationUpdateArgs,
|
||||||
},
|
},
|
||||||
object::preview::THUMBNAIL_CACHE_DIR_NAME,
|
object::preview::THUMBNAIL_CACHE_DIR_NAME,
|
||||||
prisma::{file, file_path, indexer_rule, indexer_rules_in_location, location, tag},
|
prisma::{file_path, indexer_rule, indexer_rules_in_location, location, object, tag},
|
||||||
};
|
};
|
||||||
|
|
||||||
use rspc::{self, ErrorCode, Type};
|
use rspc::{self, ErrorCode, Type};
|
||||||
|
@ -29,14 +29,14 @@ pub enum ExplorerContext {
|
||||||
// Space(object_in_space::Data),
|
// Space(object_in_space::Data),
|
||||||
}
|
}
|
||||||
|
|
||||||
file_path::include!(file_path_with_file { file });
|
file_path::include!(file_path_with_object { object });
|
||||||
file::include!(file_with_paths { paths });
|
object::include!(object_with_file_paths { file_paths });
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Type, Debug)]
|
#[derive(Serialize, Deserialize, Type, Debug)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum ExplorerItem {
|
pub enum ExplorerItem {
|
||||||
Path(Box<file_path_with_file::Data>),
|
Path(Box<file_path_with_object::Data>),
|
||||||
Object(Box<file_with_paths::Data>),
|
Object(Box<object_with_file_paths::Data>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Serialize, Deserialize, Type, Debug)]
|
#[derive(Clone, Serialize, Deserialize, Type, Debug)]
|
||||||
|
@ -100,7 +100,7 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
file_path::location_id::equals(location.id),
|
file_path::location_id::equals(location.id),
|
||||||
file_path::parent_id::equals(Some(directory.id)),
|
file_path::parent_id::equals(Some(directory.id)),
|
||||||
])
|
])
|
||||||
.include(file_path_with_file::include())
|
.include(file_path_with_object::include())
|
||||||
.exec()
|
.exec()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
@ -109,16 +109,16 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
items: file_paths
|
items: file_paths
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut file_path| {
|
.map(|mut file_path| {
|
||||||
if let Some(file) = &mut file_path.file.as_mut() {
|
if let Some(object) = &mut file_path.object.as_mut() {
|
||||||
// TODO: Use helper function to build this url as as the Rust file loading layer
|
// TODO: Use helper function to build this url as as the Rust file loading layer
|
||||||
let thumb_path = library
|
let thumb_path = library
|
||||||
.config()
|
.config()
|
||||||
.data_directory()
|
.data_directory()
|
||||||
.join(THUMBNAIL_CACHE_DIR_NAME)
|
.join(THUMBNAIL_CACHE_DIR_NAME)
|
||||||
.join(&file.cas_id)
|
.join(&object.cas_id)
|
||||||
.with_extension("webp");
|
.with_extension("webp");
|
||||||
|
|
||||||
file.has_thumbnail = thumb_path.exists();
|
object.has_thumbnail = thumb_path.exists();
|
||||||
}
|
}
|
||||||
ExplorerItem::Path(Box::new(file_path))
|
ExplorerItem::Path(Box::new(file_path))
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,10 +4,10 @@ use tracing::info;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
api::locations::{file_with_paths, ExplorerContext, ExplorerData, ExplorerItem},
|
api::locations::{object_with_file_paths, ExplorerContext, ExplorerData, ExplorerItem},
|
||||||
invalidate_query,
|
invalidate_query,
|
||||||
object::preview::THUMBNAIL_CACHE_DIR_NAME,
|
object::preview::THUMBNAIL_CACHE_DIR_NAME,
|
||||||
prisma::{file, tag, tag_on_file},
|
prisma::{object, tag, tag_on_object},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{utils::LibraryRequest, RouterBuilder};
|
use super::{utils::LibraryRequest, RouterBuilder};
|
||||||
|
@ -20,7 +20,7 @@ pub struct TagCreateArgs {
|
||||||
|
|
||||||
#[derive(Debug, Type, Deserialize)]
|
#[derive(Debug, Type, Deserialize)]
|
||||||
pub struct TagAssignArgs {
|
pub struct TagAssignArgs {
|
||||||
pub file_id: i32,
|
pub object_id: i32,
|
||||||
pub tag_id: i32,
|
pub tag_id: i32,
|
||||||
pub unassign: bool,
|
pub unassign: bool,
|
||||||
}
|
}
|
||||||
|
@ -52,32 +52,32 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
|
|
||||||
let files: Vec<ExplorerItem> = library
|
let files: Vec<ExplorerItem> = library
|
||||||
.db
|
.db
|
||||||
.file()
|
.object()
|
||||||
.find_many(vec![file::tags::some(vec![tag_on_file::tag_id::equals(
|
.find_many(vec![object::tags::some(vec![
|
||||||
tag_id,
|
tag_on_object::tag_id::equals(tag_id),
|
||||||
)])])
|
])])
|
||||||
.include(file_with_paths::include())
|
.include(object_with_file_paths::include())
|
||||||
.exec()
|
.exec()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|mut file| {
|
.map(|mut object| {
|
||||||
// sorry brendan
|
// sorry brendan
|
||||||
// grab the first path and tac on the name
|
// grab the first path and tac on the name
|
||||||
let oldest_path = &file.paths[0];
|
let oldest_path = &object.file_paths[0];
|
||||||
file.name = Some(oldest_path.name.clone());
|
object.name = Some(oldest_path.name.clone());
|
||||||
file.extension = oldest_path.extension.clone();
|
object.extension = oldest_path.extension.clone();
|
||||||
// a long term fix for this would be to have the indexer give the Object a name and extension, sacrificing its own and only store newly found Path names that differ from the Object name
|
// a long term fix for this would be to have the indexer give the Object a name and extension, sacrificing its own and only store newly found Path names that differ from the Object name
|
||||||
|
|
||||||
let thumb_path = library
|
let thumb_path = library
|
||||||
.config()
|
.config()
|
||||||
.data_directory()
|
.data_directory()
|
||||||
.join(THUMBNAIL_CACHE_DIR_NAME)
|
.join(THUMBNAIL_CACHE_DIR_NAME)
|
||||||
.join(&file.cas_id)
|
.join(&object.cas_id)
|
||||||
.with_extension("webp");
|
.with_extension("webp");
|
||||||
|
|
||||||
file.has_thumbnail = thumb_path.exists();
|
object.has_thumbnail = thumb_path.exists();
|
||||||
|
|
||||||
ExplorerItem::Object(Box::new(file))
|
ExplorerItem::Object(Box::new(object))
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
@ -88,12 +88,12 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
items: files,
|
items: files,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.library_query("getForFile", |_, file_id: i32, library| async move {
|
.library_query("getForFile", |_, object_id: i32, library| async move {
|
||||||
Ok(library
|
Ok(library
|
||||||
.db
|
.db
|
||||||
.tag()
|
.tag()
|
||||||
.find_many(vec![tag::tag_files::some(vec![
|
.find_many(vec![tag::tag_objects::some(vec![
|
||||||
tag_on_file::file_id::equals(file_id),
|
tag_on_object::object_id::equals(object_id),
|
||||||
])])
|
])])
|
||||||
.exec()
|
.exec()
|
||||||
.await?)
|
.await?)
|
||||||
|
@ -128,17 +128,17 @@ pub(crate) fn mount() -> RouterBuilder {
|
||||||
if args.unassign {
|
if args.unassign {
|
||||||
library
|
library
|
||||||
.db
|
.db
|
||||||
.tag_on_file()
|
.tag_on_object()
|
||||||
.delete(tag_on_file::tag_id_file_id(args.tag_id, args.file_id))
|
.delete(tag_on_object::tag_id_object_id(args.tag_id, args.object_id))
|
||||||
.exec()
|
.exec()
|
||||||
.await?;
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
library
|
library
|
||||||
.db
|
.db
|
||||||
.tag_on_file()
|
.tag_on_object()
|
||||||
.create(
|
.create(
|
||||||
tag::id::equals(args.tag_id),
|
tag::id::equals(args.tag_id),
|
||||||
file::id::equals(args.file_id),
|
object::id::equals(args.object_id),
|
||||||
vec![],
|
vec![],
|
||||||
)
|
)
|
||||||
.exec()
|
.exec()
|
||||||
|
|
|
@ -92,7 +92,7 @@ impl InvalidRequests {
|
||||||
#[allow(clippy::crate_in_macro_def)]
|
#[allow(clippy::crate_in_macro_def)]
|
||||||
macro_rules! invalidate_query {
|
macro_rules! invalidate_query {
|
||||||
($ctx:expr, $key:literal) => {{
|
($ctx:expr, $key:literal) => {{
|
||||||
let _: &crate::library::LibraryContext = &$ctx; // Assert the context is the correct type
|
let ctx: &crate::library::LibraryContext = &$ctx; // Assert the context is the correct type
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
{
|
{
|
||||||
|
@ -105,13 +105,15 @@ macro_rules! invalidate_query {
|
||||||
.push(crate::api::utils::InvalidationRequest {
|
.push(crate::api::utils::InvalidationRequest {
|
||||||
key: $key,
|
key: $key,
|
||||||
arg_ty: None,
|
arg_ty: None,
|
||||||
macro_src: concat!(file!(), ":", line!()),
|
macro_src: concat!(file!(), ":", line!()),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The error are ignored here because they aren't mission critical. If they fail the UI might be outdated for a bit.
|
// The error are ignored here because they aren't mission critical. If they fail the UI might be outdated for a bit.
|
||||||
crate::api::utils::InvalidateOperationEvent::dangerously_create($key, serde_json::Value::Null)
|
ctx.emit(crate::api::CoreEvent::InvalidateOperation(
|
||||||
|
crate::api::utils::InvalidateOperationEvent::dangerously_create($key, serde_json::Value::Null)
|
||||||
|
))
|
||||||
}};
|
}};
|
||||||
($ctx:expr, $key:literal: $arg_ty:ty, $arg:expr $(,)?) => {{
|
($ctx:expr, $key:literal: $arg_ty:ty, $arg:expr $(,)?) => {{
|
||||||
let _: $arg_ty = $arg; // Assert the type the user provided is correct
|
let _: $arg_ty = $arg; // Assert the type the user provided is correct
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext},
|
job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext},
|
||||||
library::LibraryContext,
|
library::LibraryContext,
|
||||||
prisma::{file, file_path, location},
|
prisma::{file_path, location, object},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::{DateTime, FixedOffset};
|
||||||
|
@ -162,10 +162,10 @@ impl StatefulJob for FileIdentifierJob {
|
||||||
for file_path in &file_paths {
|
for file_path in &file_paths {
|
||||||
// get the cas_id and extract metadata
|
// get the cas_id and extract metadata
|
||||||
match assemble_object_metadata(&data.location_path, file_path).await {
|
match assemble_object_metadata(&data.location_path, file_path).await {
|
||||||
Ok(file) => {
|
Ok(object) => {
|
||||||
let cas_id = file.cas_id.clone();
|
let cas_id = object.cas_id.clone();
|
||||||
// create entry into chunks for created file data
|
// create entry into chunks for created file data
|
||||||
chunk.insert(file_path.id, file);
|
chunk.insert(file_path.id, object);
|
||||||
cas_lookup.insert(cas_id, file_path.id);
|
cas_lookup.insert(cas_id, file_path.id);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
|
@ -177,23 +177,23 @@ impl StatefulJob for FileIdentifierJob {
|
||||||
|
|
||||||
// find all existing files by cas id
|
// find all existing files by cas id
|
||||||
let generated_cas_ids = chunk.values().map(|c| c.cas_id.clone()).collect();
|
let generated_cas_ids = chunk.values().map(|c| c.cas_id.clone()).collect();
|
||||||
let existing_files = db
|
let existing_objects = db
|
||||||
.file()
|
.object()
|
||||||
.find_many(vec![file::cas_id::in_vec(generated_cas_ids)])
|
.find_many(vec![object::cas_id::in_vec(generated_cas_ids)])
|
||||||
.exec()
|
.exec()
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
info!("Found {} existing files", existing_files.len());
|
info!("Found {} existing files", existing_objects.len());
|
||||||
|
|
||||||
for existing_file in &existing_files {
|
for existing_object in &existing_objects {
|
||||||
if let Err(e) = db
|
if let Err(e) = db
|
||||||
.file_path()
|
.file_path()
|
||||||
.update(
|
.update(
|
||||||
file_path::location_id_id(
|
file_path::location_id_id(
|
||||||
state.init.location_id,
|
state.init.location_id,
|
||||||
*cas_lookup.get(&existing_file.cas_id).unwrap(),
|
*cas_lookup.get(&existing_object.cas_id).unwrap(),
|
||||||
),
|
),
|
||||||
vec![file_path::file_id::set(Some(existing_file.id))],
|
vec![file_path::object_id::set(Some(existing_object.id))],
|
||||||
)
|
)
|
||||||
.exec()
|
.exec()
|
||||||
.await
|
.await
|
||||||
|
@ -202,26 +202,26 @@ impl StatefulJob for FileIdentifierJob {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let existing_files_cas_ids = existing_files
|
let existing_object_cas_ids = existing_objects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|file| file.cas_id.clone())
|
.map(|object| object.cas_id.clone())
|
||||||
.collect::<HashSet<_>>();
|
.collect::<HashSet<_>>();
|
||||||
|
|
||||||
// extract files that don't already exist in the database
|
// extract objects that don't already exist in the database
|
||||||
let new_files = chunk
|
let new_objects = chunk
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(_id, create_file)| create_file)
|
.map(|(_id, create_file)| create_file)
|
||||||
.filter(|create_file| !existing_files_cas_ids.contains(&create_file.cas_id))
|
.filter(|create_file| !existing_object_cas_ids.contains(&create_file.cas_id))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
if !new_files.is_empty() {
|
if !new_objects.is_empty() {
|
||||||
// assemble prisma values for new unique files
|
// assemble prisma values for new unique files
|
||||||
let mut values = Vec::with_capacity(new_files.len() * 3);
|
let mut values = Vec::with_capacity(new_objects.len() * 3);
|
||||||
for file in &new_files {
|
for object in &new_objects {
|
||||||
values.extend([
|
values.extend([
|
||||||
PrismaValue::String(file.cas_id.clone()),
|
PrismaValue::String(object.cas_id.clone()),
|
||||||
PrismaValue::Int(file.size_in_bytes),
|
PrismaValue::Int(object.size_in_bytes),
|
||||||
PrismaValue::DateTime(file.date_created),
|
PrismaValue::DateTime(object.date_created),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -230,9 +230,9 @@ impl StatefulJob for FileIdentifierJob {
|
||||||
let created_files: Vec<FileCreated> = db
|
let created_files: Vec<FileCreated> = db
|
||||||
._query_raw(Raw::new(
|
._query_raw(Raw::new(
|
||||||
&format!(
|
&format!(
|
||||||
"INSERT INTO files (cas_id, size_in_bytes, date_created) VALUES {}
|
"INSERT INTO object (cas_id, size_in_bytes, date_created) VALUES {}
|
||||||
ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id",
|
ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id",
|
||||||
vec!["({}, {}, {})"; new_files.len()].join(",")
|
vec!["({}, {}, {})"; new_objects.len()].join(",")
|
||||||
),
|
),
|
||||||
values,
|
values,
|
||||||
))
|
))
|
||||||
|
@ -256,7 +256,7 @@ impl StatefulJob for FileIdentifierJob {
|
||||||
state.init.location_id,
|
state.init.location_id,
|
||||||
*cas_lookup.get(&created_file.cas_id).unwrap(),
|
*cas_lookup.get(&created_file.cas_id).unwrap(),
|
||||||
),
|
),
|
||||||
vec![file_path::file_id::set(Some(created_file.id))],
|
vec![file_path::object_id::set(Some(created_file.id))],
|
||||||
)
|
)
|
||||||
.exec()
|
.exec()
|
||||||
.await
|
.await
|
||||||
|
@ -305,7 +305,7 @@ impl StatefulJob for FileIdentifierJob {
|
||||||
|
|
||||||
fn orphan_path_filters(location_id: i32, file_path_id: Option<i32>) -> Vec<file_path::WhereParam> {
|
fn orphan_path_filters(location_id: i32, file_path_id: Option<i32>) -> Vec<file_path::WhereParam> {
|
||||||
let mut params = vec![
|
let mut params = vec![
|
||||||
file_path::file_id::equals(None),
|
file_path::object_id::equals(None),
|
||||||
file_path::is_dir::equals(false),
|
file_path::is_dir::equals(false),
|
||||||
file_path::location_id::equals(location_id),
|
file_path::location_id::equals(location_id),
|
||||||
];
|
];
|
||||||
|
@ -329,7 +329,7 @@ async fn count_orphan_file_paths(
|
||||||
.db
|
.db
|
||||||
.file_path()
|
.file_path()
|
||||||
.count(vec![
|
.count(vec![
|
||||||
file_path::file_id::equals(None),
|
file_path::object_id::equals(None),
|
||||||
file_path::is_dir::equals(false),
|
file_path::is_dir::equals(false),
|
||||||
file_path::location_id::equals(location_id),
|
file_path::location_id::equals(location_id),
|
||||||
])
|
])
|
||||||
|
|
|
@ -28,7 +28,7 @@ pub struct ObjectsForExplorer {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Type)]
|
#[derive(Debug, Serialize, Deserialize, Type)]
|
||||||
pub enum ObjectData {
|
pub enum ObjectData {
|
||||||
Object(Box<prisma::file::Data>),
|
Object(Box<prisma::object::Data>),
|
||||||
Path(Box<prisma::file_path::Data>),
|
Path(Box<prisma::file_path::Data>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ pub struct ThumbnailJobState {
|
||||||
root_path: PathBuf,
|
root_path: PathBuf,
|
||||||
}
|
}
|
||||||
|
|
||||||
file_path::include!(file_path_with_file { file });
|
file_path::include!(file_path_with_object { object });
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
|
||||||
enum ThumbnailJobStepKind {
|
enum ThumbnailJobStepKind {
|
||||||
|
@ -49,7 +49,7 @@ enum ThumbnailJobStepKind {
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct ThumbnailJobStep {
|
pub struct ThumbnailJobStep {
|
||||||
file: file_path_with_file::Data,
|
file_path: file_path_with_object::Data,
|
||||||
kind: ThumbnailJobStepKind,
|
kind: ThumbnailJobStepKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +179,7 @@ impl StatefulJob for ThumbnailJob {
|
||||||
let step = &state.steps[0];
|
let step = &state.steps[0];
|
||||||
ctx.progress(vec![JobReportUpdate::Message(format!(
|
ctx.progress(vec![JobReportUpdate::Message(format!(
|
||||||
"Processing {}",
|
"Processing {}",
|
||||||
step.file.materialized_path
|
step.file_path.materialized_path
|
||||||
))]);
|
))]);
|
||||||
|
|
||||||
let data = state
|
let data = state
|
||||||
|
@ -188,16 +188,16 @@ impl StatefulJob for ThumbnailJob {
|
||||||
.expect("critical error: missing data on job state");
|
.expect("critical error: missing data on job state");
|
||||||
|
|
||||||
// assemble the file path
|
// assemble the file path
|
||||||
let path = data.root_path.join(&step.file.materialized_path);
|
let path = data.root_path.join(&step.file_path.materialized_path);
|
||||||
trace!("image_file {:?}", step);
|
trace!("image_file {:?}", step);
|
||||||
|
|
||||||
// get cas_id, if none found skip
|
// get cas_id, if none found skip
|
||||||
let cas_id = match &step.file.file {
|
let cas_id = match &step.file_path.object {
|
||||||
Some(f) => f.cas_id.clone(),
|
Some(f) => f.cas_id.clone(),
|
||||||
_ => {
|
_ => {
|
||||||
warn!(
|
warn!(
|
||||||
"skipping thumbnail generation for {}",
|
"skipping thumbnail generation for {}",
|
||||||
step.file.materialized_path
|
step.file_path.materialized_path
|
||||||
);
|
);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -327,10 +327,10 @@ async fn get_files_by_extension(
|
||||||
.db
|
.db
|
||||||
.file_path()
|
.file_path()
|
||||||
.find_many(params)
|
.find_many(params)
|
||||||
.include(file_path_with_file::include())
|
.include(file_path_with_object::include())
|
||||||
.exec()
|
.exec()
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|file| ThumbnailJobStep { file, kind })
|
.map(|file_path| ThumbnailJobStep { file_path, kind })
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub enum MigrationError {
|
||||||
#[error("An error occurred while initialising a new database connection: {0}")]
|
#[error("An error occurred while initialising a new database connection: {0}")]
|
||||||
NewClient(#[from] Box<NewClientError>),
|
NewClient(#[from] Box<NewClientError>),
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
#[error("An error occurred during migartion: {0}")]
|
#[error("An error occurred during migration: {0}")]
|
||||||
MigrateFailed(#[from] DbPushError),
|
MigrateFailed(#[from] DbPushError),
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
#[error("An error occurred during migration: {0}")]
|
#[error("An error occurred during migration: {0}")]
|
||||||
|
|
|
@ -3,11 +3,11 @@
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prep": "pnpm db:gen && cargo test",
|
"prep": "pnpm gen:prisma && cargo test",
|
||||||
"build": "turbo run build",
|
"build": "turbo run build",
|
||||||
"landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web",
|
"landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web",
|
||||||
"db:migrate": "cd crates && cargo prisma migrate dev",
|
"gen:migrations": "cd core && cargo prisma migrate dev",
|
||||||
"db:gen": "cd core && cargo prisma generate",
|
"gen:prisma": "cd core && cargo prisma generate",
|
||||||
"format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
|
"format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"",
|
||||||
"desktop": "pnpm --filter @sd/desktop --",
|
"desktop": "pnpm --filter @sd/desktop --",
|
||||||
"web": "pnpm --filter @sd/web -- ",
|
"web": "pnpm --filter @sd/web -- ",
|
||||||
|
|
|
@ -53,11 +53,9 @@ export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" }
|
||||||
|
|
||||||
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
|
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
|
||||||
|
|
||||||
export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null } | { type: "Object" } & { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, paths: Array<FilePath> }
|
export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, object: Object | null } | { type: "Object" } & { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, file_paths: Array<FilePath> }
|
||||||
|
|
||||||
export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string }
|
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string }
|
||||||
|
|
||||||
export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string }
|
|
||||||
|
|
||||||
export interface GenerateThumbsForLocationArgs { id: number, path: string }
|
export interface GenerateThumbsForLocationArgs { id: number, path: string }
|
||||||
|
|
||||||
|
@ -93,17 +91,19 @@ export interface NodeConfig { version: string | null, id: string, name: string,
|
||||||
|
|
||||||
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
|
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
|
||||||
|
|
||||||
|
export interface Object { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string }
|
||||||
|
|
||||||
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
|
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
|
||||||
|
|
||||||
export interface SetFavoriteArgs { id: number, favorite: boolean }
|
export interface SetFavoriteArgs { id: number, favorite: boolean }
|
||||||
|
|
||||||
export interface SetNoteArgs { id: number, note: string | null }
|
export interface SetNoteArgs { id: number, note: string | null }
|
||||||
|
|
||||||
export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
|
export interface Statistics { id: number, date_captured: string, total_object_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string }
|
||||||
|
|
||||||
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string }
|
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_objects: number | null, redundancy_goal: number | null, date_created: string, date_modified: string }
|
||||||
|
|
||||||
export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean }
|
export interface TagAssignArgs { object_id: number, tag_id: number, unassign: boolean }
|
||||||
|
|
||||||
export interface TagCreateArgs { name: string, color: string }
|
export interface TagCreateArgs { name: string, color: string }
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useCurrentLibrary, useInvalidateQuery } from '@sd/client';
|
import { useCurrentLibrary, useInvalidateQuery } from '@sd/client';
|
||||||
import { Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes } from 'react-router-dom';
|
||||||
|
|
||||||
import { AppLayout } from './AppLayout';
|
import { AppLayout } from './AppLayout';
|
||||||
import { NotFound } from './NotFound';
|
import { NotFound } from './NotFound';
|
||||||
|
@ -10,7 +10,6 @@ import { DebugScreen } from './screens/Debug';
|
||||||
import { LocationExplorer } from './screens/LocationExplorer';
|
import { LocationExplorer } from './screens/LocationExplorer';
|
||||||
import { OverviewScreen } from './screens/Overview';
|
import { OverviewScreen } from './screens/Overview';
|
||||||
import { PhotosScreen } from './screens/Photos';
|
import { PhotosScreen } from './screens/Photos';
|
||||||
import { RedirectPage } from './screens/Redirect';
|
|
||||||
import { TagExplorer } from './screens/TagExplorer';
|
import { TagExplorer } from './screens/TagExplorer';
|
||||||
import { SettingsScreen } from './screens/settings/Settings';
|
import { SettingsScreen } from './screens/settings/Settings';
|
||||||
import AppearanceSettings from './screens/settings/client/AppearanceSettings';
|
import AppearanceSettings from './screens/settings/client/AppearanceSettings';
|
||||||
|
@ -49,12 +48,12 @@ export function AppRouter() {
|
||||||
<Route
|
<Route
|
||||||
path="*"
|
path="*"
|
||||||
element={
|
element={
|
||||||
<h1 className="text-white p-4">Please select or create a library in the sidebar.</h1>
|
<h1 className="p-4 text-white">Please select or create a library in the sidebar.</h1>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Route index element={<RedirectPage to="/overview" />} />
|
<Route index element={<Navigate to="/overview" />} />
|
||||||
<Route path="overview" element={<OverviewScreen />} />
|
<Route path="overview" element={<OverviewScreen />} />
|
||||||
<Route path="content" element={<ContentScreen />} />
|
<Route path="content" element={<ContentScreen />} />
|
||||||
<Route path="photos" element={<PhotosScreen />} />
|
<Route path="photos" element={<PhotosScreen />} />
|
||||||
|
|
|
@ -34,7 +34,7 @@ const AssignTagMenuItems = (props: { objectId: number }) => {
|
||||||
|
|
||||||
assignTag({
|
assignTag({
|
||||||
tag_id: tag.id,
|
tag_id: tag.id,
|
||||||
file_id: props.objectId,
|
object_id: props.objectId,
|
||||||
unassign: active
|
unassign: active
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -21,7 +21,7 @@ function FileItem({ data, selected, index, ...rest }: Props) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => {
|
||||||
const objectId = isObject(data) ? data.id : data.file?.id;
|
const objectId = isObject(data) ? data.id : data.object?.id;
|
||||||
if (objectId != undefined) {
|
if (objectId != undefined) {
|
||||||
getExplorerStore().contextMenuObjectId = objectId;
|
getExplorerStore().contextMenuObjectId = objectId;
|
||||||
if (index != undefined) {
|
if (index != undefined) {
|
||||||
|
|
|
@ -23,7 +23,7 @@ export default function FileThumb({ data, ...props }: Props) {
|
||||||
if (isPath(data) && data.is_dir)
|
if (isPath(data) && data.is_dir)
|
||||||
return <Folder className={props.iconClassNames} size={props.size * 0.7} />;
|
return <Folder className={props.iconClassNames} size={props.size * 0.7} />;
|
||||||
|
|
||||||
const cas_id = isObject(data) ? data.cas_id : data.file?.cas_id;
|
const cas_id = isObject(data) ? data.cas_id : data.object?.cas_id;
|
||||||
|
|
||||||
if (cas_id) {
|
if (cas_id) {
|
||||||
// this won't work
|
// this won't work
|
||||||
|
@ -32,7 +32,7 @@ export default function FileThumb({ data, ...props }: Props) {
|
||||||
const has_thumbnail = isObject(data)
|
const has_thumbnail = isObject(data)
|
||||||
? data.has_thumbnail
|
? data.has_thumbnail
|
||||||
: isPath(data)
|
: isPath(data)
|
||||||
? data.file?.has_thumbnail
|
? data.object?.has_thumbnail
|
||||||
: new_thumbnail;
|
: new_thumbnail;
|
||||||
|
|
||||||
const url = platform.getThumbnailUrlById(cas_id);
|
const url = platform.getThumbnailUrlById(cas_id);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ShareIcon } from '@heroicons/react/24/solid';
|
import { ShareIcon } from '@heroicons/react/24/solid';
|
||||||
import { useLibraryQuery } from '@sd/client';
|
import { useLibraryQuery } from '@sd/client';
|
||||||
import { ExplorerContext, ExplorerItem, File, FilePath, Location } from '@sd/client';
|
import { ExplorerContext, ExplorerItem, FilePath, Location, Object } from '@sd/client';
|
||||||
import { Button, TextArea } from '@sd/ui';
|
import { Button, TextArea } from '@sd/ui';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
@ -24,7 +24,7 @@ interface Props {
|
||||||
export const Inspector = (props: Props) => {
|
export const Inspector = (props: Props) => {
|
||||||
const is_dir = props.data?.type === 'Path' ? props.data.is_dir : false;
|
const is_dir = props.data?.type === 'Path' ? props.data.is_dir : false;
|
||||||
|
|
||||||
const objectData = isObject(props.data) ? props.data : props.data.file;
|
const objectData = isObject(props.data) ? props.data : props.data.object;
|
||||||
|
|
||||||
// this prevents the inspector from fetching data when the user is navigating quickly
|
// this prevents the inspector from fetching data when the user is navigating quickly
|
||||||
const [readyToFetch, setReadyToFetch] = useState(false);
|
const [readyToFetch, setReadyToFetch] = useState(false);
|
||||||
|
|
|
@ -198,6 +198,5 @@ const WrappedItem: React.FC<WrappedItemProps> = ({ item, index, isSelected, kind
|
||||||
// selected={isSelected}
|
// selected={isSelected}
|
||||||
// />
|
// />
|
||||||
// );
|
// );
|
||||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
// }, [item, index, isSelected]);
|
// }, [item, index, isSelected]);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import { useLibraryMutation } from '@sd/client';
|
import { useLibraryMutation } from '@sd/client';
|
||||||
import { File } from '@sd/client';
|
import { Object as SDObject } from '@sd/client';
|
||||||
import { Button } from '@sd/ui';
|
import { Button } from '@sd/ui';
|
||||||
import { Heart } from 'phosphor-react';
|
import { Heart } from 'phosphor-react';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: File;
|
data: SDObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FavoriteButton(props: Props) {
|
export default function FavoriteButton(props: Props) {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { useLibraryMutation } from '@sd/client';
|
import { useLibraryMutation } from '@sd/client';
|
||||||
import { File } from '@sd/client';
|
import { Object as SDObject } from '@sd/client';
|
||||||
import { TextArea } from '@sd/ui';
|
import { TextArea } from '@sd/ui';
|
||||||
import { debounce } from 'lodash';
|
import { debounce } from 'lodash';
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
@ -9,25 +8,28 @@ import { Divider } from './Divider';
|
||||||
import { MetaItem } from './MetaItem';
|
import { MetaItem } from './MetaItem';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
data: File;
|
data: SDObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Note(props: Props) {
|
export default function Note(props: Props) {
|
||||||
// notes are cached in a store by their file id
|
// notes are cached in a store by their file id
|
||||||
// this is so we can ensure every note has been sent to Rust even
|
// this is so we can ensure every note has been sent to Rust even
|
||||||
// when quickly navigating files, which cancels update function
|
// when quickly navigating files, which cancels update function
|
||||||
const [note, setNote] = useState((props.data as File)?.note || '');
|
const [note, setNote] = useState(props.data.note || '');
|
||||||
|
|
||||||
const { mutate: fileSetNote } = useLibraryMutation('files.setNote');
|
const { mutate: fileSetNote } = useLibraryMutation('files.setNote');
|
||||||
|
|
||||||
const debouncedNote = useCallback(
|
const debouncedNote = useCallback(
|
||||||
debounce((note: string) => {
|
(note: string) =>
|
||||||
fileSetNote({
|
debounce(
|
||||||
id: props.data.id,
|
() =>
|
||||||
note
|
fileSetNote({
|
||||||
});
|
id: props.data.id,
|
||||||
}, 2000),
|
note
|
||||||
[props.data.id]
|
}),
|
||||||
|
2000
|
||||||
|
),
|
||||||
|
[props.data.id, fileSetNote]
|
||||||
);
|
);
|
||||||
|
|
||||||
// when input is updated, cache note
|
// when input is updated, cache note
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ExplorerItem, File, FilePath } from '@sd/client';
|
import { ExplorerItem, FilePath } from '@sd/client';
|
||||||
|
|
||||||
export function isPath(item: ExplorerItem): item is Extract<ExplorerItem, { type: 'Path' }> {
|
export function isPath(item: ExplorerItem): item is Extract<ExplorerItem, { type: 'Path' }> {
|
||||||
return item.type === 'Path';
|
return item.type === 'Path';
|
||||||
|
|
|
@ -186,10 +186,10 @@ export function Sidebar() {
|
||||||
buttonText={isLoadingLibraries ? 'Loading...' : library ? library.config.name : ' '}
|
buttonText={isLoadingLibraries ? 'Loading...' : library ? library.config.name : ' '}
|
||||||
buttonTextClassName={library === null || isLoadingLibraries ? 'text-gray-300' : undefined}
|
buttonTextClassName={library === null || isLoadingLibraries ? 'text-gray-300' : undefined}
|
||||||
items={[
|
items={[
|
||||||
libraries?.map((library) => ({
|
libraries?.map((lib) => ({
|
||||||
name: library.config.name,
|
name: lib.config.name,
|
||||||
selected: library.uuid === library?.uuid,
|
selected: lib.uuid === library?.uuid,
|
||||||
onPress: () => switchLibrary(library.uuid)
|
onPress: () => switchLibrary(lib.uuid)
|
||||||
})) || [],
|
})) || [],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|
|
@ -53,13 +53,12 @@ const useCounter = ({ name, start = 0, end, duration = 2, saveState = true }: Us
|
||||||
duration,
|
duration,
|
||||||
easing: 'easeOutCubic'
|
easing: 'easeOutCubic'
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (saveState && value == end) {
|
if (saveState && value == end) {
|
||||||
setLastValue(name, end);
|
setLastValue(name, end);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [end, name, saveState, setLastValue, value]);
|
||||||
}, [end, value]);
|
|
||||||
|
|
||||||
if (start === end) return end;
|
if (start === end) return end;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
/* eslint-disable react-hooks/exhaustive-deps */
|
|
||||||
import { getExplorerStore, useCurrentLibrary, useLibraryQuery } from '@sd/client';
|
import { getExplorerStore, useCurrentLibrary, useLibraryQuery } from '@sd/client';
|
||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useParams, useSearchParams } from 'react-router-dom';
|
import { useParams, useSearchParams } from 'react-router-dom';
|
||||||
|
|
|
@ -100,42 +100,22 @@ export const OverviewScreen = () => {
|
||||||
|
|
||||||
// get app props from context
|
// get app props from context
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (platform.demoMode === true) {
|
const newStatistics: OverviewStats = {
|
||||||
if (!Object.entries(overviewStats).length)
|
total_bytes_capacity: '0',
|
||||||
setOverviewStats({
|
preview_media_bytes: '0',
|
||||||
total_bytes_capacity: '8093333345230',
|
library_db_size: '0',
|
||||||
preview_media_bytes: '2304387532',
|
total_object_count: '0',
|
||||||
library_db_size: '83345230',
|
total_bytes_free: '0',
|
||||||
total_file_count: '20342345',
|
total_bytes_used: '0',
|
||||||
total_bytes_free: '89734502034',
|
total_unique_bytes: '0'
|
||||||
total_bytes_used: '8093333345230',
|
};
|
||||||
total_unique_bytes: '9347397'
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const newStatistics: OverviewStats = {
|
|
||||||
total_bytes_capacity: '0',
|
|
||||||
preview_media_bytes: '0',
|
|
||||||
library_db_size: '0',
|
|
||||||
total_file_count: '0',
|
|
||||||
total_bytes_free: '0',
|
|
||||||
total_bytes_used: '0',
|
|
||||||
total_unique_bytes: '0'
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.entries((libraryStatistics as Statistics) || {}).forEach(([key, value]) => {
|
Object.entries((libraryStatistics as Statistics) || {}).forEach(([key, value]) => {
|
||||||
newStatistics[key as keyof Statistics] = `${value}`;
|
newStatistics[key as keyof Statistics] = `${value}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
setOverviewStats(newStatistics);
|
setOverviewStats(newStatistics);
|
||||||
}
|
}, [platform, libraryStatistics, setOverviewStats]);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [platform, libraryStatistics]);
|
|
||||||
|
|
||||||
// useEffect(() => {
|
|
||||||
// setTimeout(() => {
|
|
||||||
// setOverviewStat('total_bytes_capacity', '4093333345230');
|
|
||||||
// }, 2000);
|
|
||||||
// }, [overviewStats]);
|
|
||||||
|
|
||||||
const displayableStatItems = Object.keys(StatItemNames) as unknown as keyof typeof StatItemNames;
|
const displayableStatItems = Object.keys(StatItemNames) as unknown as keyof typeof StatItemNames;
|
||||||
|
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { useEffect } from 'react';
|
|
||||||
import { useNavigate } from 'react-router';
|
|
||||||
|
|
||||||
export interface RedirectPageProps {
|
|
||||||
to: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RedirectPage: React.FC<RedirectPageProps> = (props) => {
|
|
||||||
const { to: destination } = props;
|
|
||||||
|
|
||||||
const navigate = useNavigate();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
navigate(destination);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
|
@ -35,16 +35,14 @@ export default function LibraryGeneralSettings() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [nameDebounced, descriptionDebounced, library, editLibrary]);
|
||||||
}, [nameDebounced, descriptionDebounced]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (library) {
|
if (library) {
|
||||||
setName(library.config.name);
|
setName(library.config.name);
|
||||||
setDescription(library.config.description);
|
setDescription(library.config.description);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
}, [libraries, library]);
|
||||||
}, [libraries]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (library) {
|
if (library) {
|
||||||
|
@ -52,7 +50,6 @@ export default function LibraryGeneralSettings() {
|
||||||
setName(library.config.name);
|
setName(library.config.name);
|
||||||
setDescription(library.config.description);
|
setDescription(library.config.description);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [library]);
|
}, [library]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { TrashIcon } from '@heroicons/react/24/outline';
|
import { TrashIcon } from '@heroicons/react/24/outline';
|
||||||
import { useLibraryMutation, useLibraryQuery } from '@sd/client';
|
import { Tag, useLibraryMutation, useLibraryQuery } from '@sd/client';
|
||||||
import { TagUpdateArgs } from '@sd/client';
|
import { TagUpdateArgs } from '@sd/client';
|
||||||
import { Button, Input } from '@sd/ui';
|
import { Button, Input } from '@sd/ui';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
@ -23,11 +23,7 @@ export default function TagsSettings() {
|
||||||
|
|
||||||
const { data: tags } = useLibraryQuery(['tags.list']);
|
const { data: tags } = useLibraryQuery(['tags.list']);
|
||||||
|
|
||||||
const [selectedTag, setSelectedTag] = useState<null | number>(null);
|
const [selectedTag, setSelectedTag] = useState<null | Tag>(tags?.[0] ?? null);
|
||||||
|
|
||||||
const currentTag = useMemo(() => {
|
|
||||||
return tags?.find((t) => t.id === selectedTag);
|
|
||||||
}, [tags, selectedTag]);
|
|
||||||
|
|
||||||
const { mutate: createTag, isLoading } = useLibraryMutation('tags.create', {
|
const { mutate: createTag, isLoading } = useLibraryMutation('tags.create', {
|
||||||
onError: (e) => {
|
onError: (e) => {
|
||||||
|
@ -38,25 +34,27 @@ export default function TagsSettings() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const { mutate: updateTag } = useLibraryMutation('tags.update');
|
const updateTag = useLibraryMutation('tags.update');
|
||||||
|
|
||||||
const { mutate: deleteTag, isLoading: tagDeleteLoading } = useLibraryMutation('tags.delete');
|
const deleteTag = useLibraryMutation('tags.delete', {
|
||||||
|
onSuccess: () => {
|
||||||
// set default selected tag
|
setSelectedTag(null);
|
||||||
useEffect(() => {
|
}
|
||||||
if (!currentTag && tags?.length) setSelectedTag(tags[0].id);
|
|
||||||
}, [currentTag, tags]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
reset(currentTag);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [currentTag]);
|
|
||||||
|
|
||||||
const { register, handleSubmit, watch, reset, control } = useForm({
|
|
||||||
defaultValues: currentTag as TagUpdateArgs
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const submitTagUpdate = handleSubmit((data) => updateTag(data));
|
const { register, handleSubmit, watch, reset, control } = useForm({
|
||||||
|
defaultValues: selectedTag as TagUpdateArgs
|
||||||
|
});
|
||||||
|
|
||||||
|
const setTag = useCallback(
|
||||||
|
(tag: Tag | null) => {
|
||||||
|
if (tag) reset(tag);
|
||||||
|
setSelectedTag(tag);
|
||||||
|
},
|
||||||
|
[setSelectedTag, reset]
|
||||||
|
);
|
||||||
|
|
||||||
|
const submitTagUpdate = handleSubmit((data) => updateTag.mutate(data));
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const autoUpdateTag = useCallback(useDebounce(submitTagUpdate, 500), []);
|
const autoUpdateTag = useCallback(useDebounce(submitTagUpdate, 500), []);
|
||||||
|
@ -113,11 +111,11 @@ export default function TagsSettings() {
|
||||||
<div className="flex flex-wrap gap-2 m-1">
|
<div className="flex flex-wrap gap-2 m-1">
|
||||||
{tags?.map((tag) => (
|
{tags?.map((tag) => (
|
||||||
<div
|
<div
|
||||||
onClick={() => setSelectedTag(tag.id === selectedTag ? null : tag.id)}
|
onClick={() => setTag(tag.id === selectedTag?.id ? null : tag)}
|
||||||
key={tag.id}
|
key={tag.id}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex items-center rounded px-1.5 py-0.5',
|
'flex items-center rounded px-1.5 py-0.5',
|
||||||
selectedTag === tag.id && 'ring'
|
selectedTag?.id === tag.id && 'ring'
|
||||||
)}
|
)}
|
||||||
style={{ backgroundColor: tag.color + 'CC' }}
|
style={{ backgroundColor: tag.color + 'CC' }}
|
||||||
>
|
>
|
||||||
|
@ -126,7 +124,7 @@ export default function TagsSettings() {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
{currentTag ? (
|
{selectedTag ? (
|
||||||
<form onSubmit={submitTagUpdate}>
|
<form onSubmit={submitTagUpdate}>
|
||||||
<div className="flex flex-row mb-10 space-x-3">
|
<div className="flex flex-row mb-10 space-x-3">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
|
@ -159,9 +157,9 @@ export default function TagsSettings() {
|
||||||
title="Delete Tag"
|
title="Delete Tag"
|
||||||
description="Are you sure you want to delete this tag? This cannot be undone and tagged files will be unlinked."
|
description="Are you sure you want to delete this tag? This cannot be undone and tagged files will be unlinked."
|
||||||
ctaAction={() => {
|
ctaAction={() => {
|
||||||
deleteTag(currentTag.id);
|
deleteTag.mutate(selectedTag.id);
|
||||||
}}
|
}}
|
||||||
loading={tagDeleteLoading}
|
loading={deleteTag.isLoading}
|
||||||
ctaDanger
|
ctaDanger
|
||||||
ctaLabel="Delete"
|
ctaLabel="Delete"
|
||||||
trigger={
|
trigger={
|
||||||
|
|
Loading…
Reference in a new issue