Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 88a8cac048 | |||
| aae0c587e8 |
@@ -0,0 +1,28 @@
|
|||||||
|
import { get } from 'svelte/store';
|
||||||
|
import { api } from '$lib/api/client';
|
||||||
|
import type { Category, CategoryOffsetPage } from '$lib/api/types';
|
||||||
|
import { categorySorting } from '$lib/stores/sorting';
|
||||||
|
|
||||||
|
// The /categories endpoint caps limit at 200 per request. Category dropdowns
|
||||||
|
// show the whole list, so page through to get them all — otherwise categories
|
||||||
|
// past the first 200 are missing from the picker.
|
||||||
|
const PAGE = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches every category, paging past the server's per-request cap. Ordered by
|
||||||
|
* the sort the user picked on the categories page (categorySorting).
|
||||||
|
*/
|
||||||
|
export async function fetchAllCategories(): Promise<Category[]> {
|
||||||
|
const { sort, order } = get(categorySorting);
|
||||||
|
const all: Category[] = [];
|
||||||
|
for (let offset = 0; ; offset += PAGE) {
|
||||||
|
const page = await api.get<CategoryOffsetPage>(
|
||||||
|
`/categories?limit=${PAGE}&offset=${offset}&sort=${sort}&order=${order}`
|
||||||
|
);
|
||||||
|
const items = page.items ?? [];
|
||||||
|
all.push(...items);
|
||||||
|
const total = page.total ?? all.length;
|
||||||
|
if (items.length < PAGE || all.length >= total) break;
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { get } from 'svelte/store';
|
||||||
|
import { api } from '$lib/api/client';
|
||||||
|
import type { Tag, TagOffsetPage } from '$lib/api/types';
|
||||||
|
import { tagSorting } from '$lib/stores/sorting';
|
||||||
|
|
||||||
|
// The /tags endpoint caps limit at 200 per request. Pickers and the filter bar
|
||||||
|
// filter the tag list client-side, so they need the *whole* list — otherwise
|
||||||
|
// tags past the first 200 are invisible and unsearchable. Page through until we
|
||||||
|
// have them all.
|
||||||
|
const PAGE = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches every tag, paging past the server's per-request cap. Ordered by the
|
||||||
|
* sort the user picked on the tags page (tagSorting), so the pickers and filter
|
||||||
|
* bar show tags in the same order as that page.
|
||||||
|
*/
|
||||||
|
export async function fetchAllTags(): Promise<Tag[]> {
|
||||||
|
const { sort, order } = get(tagSorting);
|
||||||
|
const all: Tag[] = [];
|
||||||
|
for (let offset = 0; ; offset += PAGE) {
|
||||||
|
const page = await api.get<TagOffsetPage>(
|
||||||
|
`/tags?limit=${PAGE}&offset=${offset}&sort=${sort}&order=${order}`
|
||||||
|
);
|
||||||
|
const items = page.items ?? [];
|
||||||
|
all.push(...items);
|
||||||
|
const total = page.total ?? all.length;
|
||||||
|
if (items.length < PAGE || all.length >= total) break;
|
||||||
|
}
|
||||||
|
return all;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api } from '$lib/api/client';
|
import { api } from '$lib/api/client';
|
||||||
import type { Tag, TagOffsetPage } from '$lib/api/types';
|
import type { Tag } from '$lib/api/types';
|
||||||
|
import { fetchAllTags } from '$lib/api/tags';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fileIds: string[];
|
fileIds: string[];
|
||||||
@@ -30,13 +31,13 @@
|
|||||||
error = '';
|
error = '';
|
||||||
try {
|
try {
|
||||||
const [tagsRes, commonRes] = await Promise.all([
|
const [tagsRes, commonRes] = await Promise.all([
|
||||||
api.get<TagOffsetPage>('/tags?limit=200&sort=name&order=asc'),
|
fetchAllTags(),
|
||||||
api.post<{ common_tag_ids: string[]; partial_tag_ids: string[] }>(
|
api.post<{ common_tag_ids: string[]; partial_tag_ids: string[] }>(
|
||||||
'/files/bulk/common-tags',
|
'/files/bulk/common-tags',
|
||||||
{ file_ids: fileIds }
|
{ file_ids: fileIds }
|
||||||
)
|
)
|
||||||
]);
|
]);
|
||||||
allTags = tagsRes.items ?? [];
|
allTags = tagsRes;
|
||||||
commonIds = new Set(commonRes.common_tag_ids ?? []);
|
commonIds = new Set(commonRes.common_tag_ids ?? []);
|
||||||
partialIds = new Set(commonRes.partial_tag_ids ?? []);
|
partialIds = new Set(commonRes.partial_tag_ids ?? []);
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api } from '$lib/api/client';
|
import type { Tag } from '$lib/api/types';
|
||||||
import type { Tag, TagOffsetPage } from '$lib/api/types';
|
import { fetchAllTags } from '$lib/api/tags';
|
||||||
import { buildDslFilter, parseDslFilter, tokenLabel } from '$lib/utils/dsl';
|
import { buildDslFilter, parseDslFilter, tokenLabel } from '$lib/utils/dsl';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
api.get<TagOffsetPage>('/tags?limit=200&sort=name&order=asc').then((page) => {
|
fetchAllTags().then((all) => {
|
||||||
tags = page.items ?? [];
|
tags = all;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api } from '$lib/api/client';
|
import type { Tag } from '$lib/api/types';
|
||||||
import type { Tag, TagOffsetPage } from '$lib/api/types';
|
import { fetchAllTags } from '$lib/api/tags';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fileTags: Tag[];
|
fileTags: Tag[];
|
||||||
@@ -15,8 +15,8 @@
|
|||||||
let busy = $state(false);
|
let busy = $state(false);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
api.get<TagOffsetPage>('/tags?limit=200&sort=name&order=asc').then((p) => {
|
fetchAllTags().then((all) => {
|
||||||
allTags = p.items ?? [];
|
allTags = all;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { api, ApiError } from '$lib/api/client';
|
import { api, ApiError } from '$lib/api/client';
|
||||||
import type { Tag, TagOffsetPage, TagRule } from '$lib/api/types';
|
import type { Tag, TagRule } from '$lib/api/types';
|
||||||
|
import { fetchAllTags } from '$lib/api/tags';
|
||||||
import TagBadge from './TagBadge.svelte';
|
import TagBadge from './TagBadge.svelte';
|
||||||
import { appSettings } from '$lib/stores/appSettings';
|
import { appSettings } from '$lib/stores/appSettings';
|
||||||
|
|
||||||
@@ -18,8 +19,8 @@
|
|||||||
let error = $state('');
|
let error = $state('');
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
api.get<TagOffsetPage>('/tags?limit=200&sort=name&order=asc').then((p) => {
|
fetchAllTags().then((all) => {
|
||||||
allTags = p.items ?? [];
|
allTags = all;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { api, ApiError } from '$lib/api/client';
|
import { api, ApiError } from '$lib/api/client';
|
||||||
import type { Category, CategoryOffsetPage, Tag, TagRule } from '$lib/api/types';
|
import type { Category, Tag, TagRule } from '$lib/api/types';
|
||||||
|
import { fetchAllCategories } from '$lib/api/categories';
|
||||||
import TagRuleEditor from '$lib/components/tag/TagRuleEditor.svelte';
|
import TagRuleEditor from '$lib/components/tag/TagRuleEditor.svelte';
|
||||||
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
import ConfirmDialog from '$lib/components/common/ConfirmDialog.svelte';
|
||||||
|
|
||||||
@@ -32,12 +33,12 @@
|
|||||||
loadError = '';
|
loadError = '';
|
||||||
void Promise.all([
|
void Promise.all([
|
||||||
api.get<Tag>(`/tags/${id}`),
|
api.get<Tag>(`/tags/${id}`),
|
||||||
api.get<CategoryOffsetPage>('/categories?limit=200&sort=name&order=asc'),
|
fetchAllCategories(),
|
||||||
api.get<TagRule[]>(`/tags/${id}/rules`).catch(() => [] as TagRule[])
|
api.get<TagRule[]>(`/tags/${id}/rules`).catch(() => [] as TagRule[])
|
||||||
])
|
])
|
||||||
.then(([t, cats, r]) => {
|
.then(([t, cats, r]) => {
|
||||||
tag = t;
|
tag = t;
|
||||||
categories = cats.items ?? [];
|
categories = cats;
|
||||||
rules = r;
|
rules = r;
|
||||||
|
|
||||||
name = t.name ?? '';
|
name = t.name ?? '';
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { api, ApiError } from '$lib/api/client';
|
import { api, ApiError } from '$lib/api/client';
|
||||||
import type { Category, CategoryOffsetPage } from '$lib/api/types';
|
import type { Category } from '$lib/api/types';
|
||||||
|
import { fetchAllCategories } from '$lib/api/categories';
|
||||||
|
|
||||||
let name = $state('');
|
let name = $state('');
|
||||||
let notes = $state('');
|
let notes = $state('');
|
||||||
@@ -13,8 +14,8 @@
|
|||||||
let categories = $state<Category[]>([]);
|
let categories = $state<Category[]>([]);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
api.get<CategoryOffsetPage>('/categories?limit=200&sort=name&order=asc').then((p) => {
|
fetchAllCategories().then((all) => {
|
||||||
categories = p.items ?? [];
|
categories = all;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ psql "$NEW_DSN" -v ON_ERROR_STOP=1 -f transform.sql
|
|||||||
| `mime` | `core.mime_types` | id **uuid → smallint** (remapped by `name`); types not already seeded are added |
|
| `mime` | `core.mime_types` | id **uuid → smallint** (remapped by `name`); types not already seeded are added |
|
||||||
| `categories` | `data.categories` | id kept; `is_private` → **`is_public`** (inverted) |
|
| `categories` | `data.categories` | id kept; `is_private` → **`is_public`** (inverted) |
|
||||||
| `tags` | `data.tags` | id + `category_id` kept; inverted privacy |
|
| `tags` | `data.tags` | id + `category_id` kept; inverted privacy |
|
||||||
| `autotags` | `data.tag_rules` | `parent_id` → `when_tag_id`, `child_id` → `then_tag_id` |
|
| `autotags` | `data.tag_rules` | `child_id` → `when_tag_id`, `parent_id` → `then_tag_id` (old: adding the child pulled in the parent) |
|
||||||
| `files` | `data.files` | id kept; `datetime` → `content_datetime`; `orig_name` → `original_name`; **EXIF** lifted from `metadata->'exif'` into the `exif` column, the rest stays as user `metadata` |
|
| `files` | `data.files` | id kept; `datetime` → `content_datetime`; `orig_name` → `original_name`; **EXIF** lifted from `metadata->'exif'` into the `exif` column, the rest stays as user `metadata` |
|
||||||
| `file_tag` | `data.file_tag` | orphan rows skipped |
|
| `file_tag` | `data.file_tag` | orphan rows skipped |
|
||||||
| `pools` | `data.pools` | id kept; `parent_id` + `created` preserved under `metadata` (see below) |
|
| `pools` | `data.pools` | id kept; `parent_id` + `created` preserved under `metadata` (see below) |
|
||||||
|
|||||||
@@ -92,14 +92,17 @@ JOIN user_id_map um ON um.old_id = t.creator_id
|
|||||||
ON CONFLICT (id) DO NOTHING;
|
ON CONFLICT (id) DO NOTHING;
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
-- 5. Tag rules (old `autotags`): parent -> when_tag, child -> then_tag.
|
-- 5. Tag rules (old `autotags`). In the old schema adding the child tag pulled
|
||||||
|
-- in its parent (tfm__add_file_to_tag_recursive: for child_id = t_id it adds
|
||||||
|
-- parent_id). The new model is "when_tag applied -> then_tag follows", so the
|
||||||
|
-- trigger child becomes when_tag and the auto-applied parent becomes then_tag.
|
||||||
-- Skip rules whose tags didn't migrate.
|
-- Skip rules whose tags didn't migrate.
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
INSERT INTO data.tag_rules (when_tag_id, then_tag_id, is_active)
|
INSERT INTO data.tag_rules (when_tag_id, then_tag_id, is_active)
|
||||||
SELECT a.parent_id, a.child_id, a.is_active
|
SELECT a.child_id, a.parent_id, a.is_active
|
||||||
FROM legacy.autotags a
|
FROM legacy.autotags a
|
||||||
WHERE EXISTS (SELECT 1 FROM data.tags t WHERE t.id = a.parent_id)
|
WHERE EXISTS (SELECT 1 FROM data.tags t WHERE t.id = a.child_id)
|
||||||
AND EXISTS (SELECT 1 FROM data.tags t WHERE t.id = a.child_id)
|
AND EXISTS (SELECT 1 FROM data.tags t WHERE t.id = a.parent_id)
|
||||||
ON CONFLICT (when_tag_id, then_tag_id) DO NOTHING;
|
ON CONFLICT (when_tag_id, then_tag_id) DO NOTHING;
|
||||||
|
|
||||||
-- ---------------------------------------------------------------------------
|
-- ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user