From 75c8d35e7c11450e1bf8f0af96564609bc569d63 Mon Sep 17 00:00:00 2001 From: Syxles Date: Sat, 4 Apr 2026 22:28:02 +0200 Subject: [PATCH 1/4] WJ-1361: implement PageQueryService::select() with SelectedFields --- deepwell/src/services/page_query/service.rs | 165 ++++++++++++++++++++ deepwell/src/services/page_query/structs.rs | 60 +++++++ 2 files changed, 225 insertions(+) diff --git a/deepwell/src/services/page_query/service.rs b/deepwell/src/services/page_query/service.rs index 30e7c3243e..b4bbec78a9 100644 --- a/deepwell/src/services/page_query/service.rs +++ b/deepwell/src/services/page_query/service.rs @@ -483,4 +483,169 @@ impl PageQueryService { Ok(FoundPages { pages: rows }) } + + /// Takes the output of `find()` and extracts only the fields + /// specified in `SelectedFields`, producing a `SelectedPages` + /// result with each page's values as a JSON map. + pub fn select(found: &FoundPages, fields: &SelectedFields) -> Result { + info!( + "Selecting {} fields from {} pages", + fields.0.len(), + found.total() + ); + + let pages = found + .pages + .iter() + .map(|row| { + let mut values = serde_json::Map::new(); + + for field in &fields.0 { + let (key, value) = match field { + SelectedField::PageId => { + ("page_id", serde_json::Value::from(row.page_id)) + } + SelectedField::SiteId => { + ("site_id", serde_json::Value::from(row.site_id)) + } + SelectedField::Title => ( + "title", + row.title + .as_deref() + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::AltTitle => ( + "alt_title", + row.alt_title + .as_deref() + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::PageSlug | SelectedField::FullSlug => ( + "slug", + row.slug + .as_deref() + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::Category => { + // Extract category prefix from slug (e.g. "scp" from "scp:scp-173") + let category = row + .slug + .as_deref() + .and_then(|s| s.split(':').next()) + .filter(|_| { + row.slug + .as_deref() + .map(|s| s.contains(':')) + .unwrap_or(false) + }) + .unwrap_or("_default"); + ("category", serde_json::Value::from(category)) + } + SelectedField::CreatedAt => ( + "created_at", + row.created_at + .map(|t| { + serde_json::Value::from( + t.format(&time::format_description::well_known::Rfc3339) + .unwrap_or_default(), + ) + }) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::CreatedBy => ( + "created_by", + row.created_by + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::UpdatedAt => ( + "updated_at", + row.updated_at + .map(|t| { + serde_json::Value::from( + t.format(&time::format_description::well_known::Rfc3339) + .unwrap_or_default(), + ) + }) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::UpdatedBy => ( + "updated_by", + row.updated_by + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::Tags => ( + "tags", + row.tags + .as_ref() + .map(|t| serde_json::Value::from(t.clone())) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::HiddenTags => { + // Hidden tags are those starting with '_' + let hidden = row + .tags + .as_ref() + .map(|tags| { + tags.iter() + .filter(|t| t.starts_with('_')) + .cloned() + .collect::>() + }) + .map(serde_json::Value::from); + ("hidden_tags", hidden.unwrap_or(serde_json::Value::Null)) + } + SelectedField::Score => ( + "score", + row.score + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::PageCategoryId => ( + "page_category_id", + row.page_category_id + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null), + ), + SelectedField::PageRevisionId => ( + "page_revision_id", + row.page_revision_id + .map(serde_json::Value::from) + .unwrap_or(serde_json::Value::Null), + ), + // Fields that require additional data not yet + // available in FoundPageRow. + SelectedField::ScoreVotes => { + ("score_votes", serde_json::Value::Null) // TODO + } + SelectedField::Revisions => { + ("revisions", serde_json::Value::Null) // TODO + } + SelectedField::Comments => { + ("comments", serde_json::Value::Null) // TODO + } + SelectedField::Children => { + ("children", serde_json::Value::Null) // TODO + } + SelectedField::Size => { + ("size", serde_json::Value::Null) // TODO + } + }; + + values.insert(key.to_owned(), value); + } + + SelectedPageRow { + page_id: row.page_id, + values, + } + }) + .collect(); + + Ok(SelectedPages { pages }) + } } diff --git a/deepwell/src/services/page_query/structs.rs b/deepwell/src/services/page_query/structs.rs index 35b86a884e..39bba171b6 100644 --- a/deepwell/src/services/page_query/structs.rs +++ b/deepwell/src/services/page_query/structs.rs @@ -352,3 +352,63 @@ impl FoundPages { self.pages.len() } } + +/// Specifies which display fields the caller wants from the +/// page query results. Each variant corresponds to a ListPages +/// output variable like `%%title%%` or `%%created_at%%`. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct SelectedFields(pub Vec); + +/// A single field to select from the query results. +/// +/// Mirrors `PageQueryVariables` but is serializable and +/// does not carry lifetime parameters. +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)] +#[serde(rename_all = "snake_case")] +pub enum SelectedField { + PageId, + SiteId, + Title, + AltTitle, + PageSlug, + FullSlug, + Category, + CreatedAt, + CreatedBy, + UpdatedAt, + UpdatedBy, + Tags, + HiddenTags, + Score, + ScoreVotes, + Revisions, + Comments, + Children, + Size, + PageCategoryId, + PageRevisionId, +} + +/// A single page in the selected output, containing only +/// the fields requested via `SelectedFields`. +#[derive(Serialize, Debug, Clone, PartialEq)] +pub struct SelectedPageRow { + pub page_id: i64, + pub values: serde_json::Map, +} + +/// The result of `PageQueryService::select()`. +/// +/// Contains the pages in order with exactly the fields +/// described in the `SelectedFields` input. +#[derive(Serialize, Debug, Clone, PartialEq)] +pub struct SelectedPages { + pub pages: Vec, +} + +impl SelectedPages { + #[inline] + pub fn total(&self) -> usize { + self.pages.len() + } +} From db2932c06d2bff2296fdf36a34c353bb4782c870 Mon Sep 17 00:00:00 2001 From: Syxles Date: Thu, 9 Apr 2026 20:45:53 +0200 Subject: [PATCH 2/4] WJ-1361: use enumset bitset for SelectedFields, typed struct for SelectedPageRow --- deepwell/Cargo.lock | 65 +++++- deepwell/Cargo.toml | 1 + deepwell/src/services/page_query/service.rs | 241 ++++++++------------ deepwell/src/services/page_query/structs.rs | 58 ++++- 4 files changed, 201 insertions(+), 164 deletions(-) diff --git a/deepwell/Cargo.lock b/deepwell/Cargo.lock index b69e3cbe36..460f94c172 100644 --- a/deepwell/Cargo.lock +++ b/deepwell/Cargo.lock @@ -908,8 +908,18 @@ version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "darling_core", - "darling_macro", + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -925,13 +935,37 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "darling_macro" version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "darling_core", + "darling_core 0.20.11", + "quote", + "syn 2.0.114", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", "quote", "syn 2.0.114", ] @@ -981,6 +1015,7 @@ dependencies = [ "cuid2", "data-encoding", "dotenvy", + "enumset", "exn", "femme", "filemagic", @@ -1182,6 +1217,28 @@ dependencies = [ "syn 2.0.114", ] +[[package]] +name = "enumset" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25b07a8dfbbbfc0064c0a6bdf9edcf966de6b1c33ce344bdeca3b41615452634" +dependencies = [ + "enumset_derive", + "serde", +] + +[[package]] +name = "enumset_derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43e744e4ea338060faee68ed933e46e722fb7f3617e722a5772d7e856d8b3ce" +dependencies = [ + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.114", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -4048,7 +4105,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bae0cbad6ab996955664982739354128c58d16e126114fe88c2a493642502aab" dependencies = [ - "darling", + "darling 0.20.11", "heck 0.4.1", "proc-macro2", "quote", diff --git a/deepwell/Cargo.toml b/deepwell/Cargo.toml index 05e0a89f48..a115ef3af7 100644 --- a/deepwell/Cargo.toml +++ b/deepwell/Cargo.toml @@ -27,6 +27,7 @@ color-backtrace = "0.7" cuid2 = "0.1" data-encoding = "2" dotenvy = "0.15" +enumset = { version = "1", features = ["serde"] } exn = "0.3" femme = "2" filemagic = "0.13" diff --git a/deepwell/src/services/page_query/service.rs b/deepwell/src/services/page_query/service.rs index b4bbec78a9..0546179463 100644 --- a/deepwell/src/services/page_query/service.rs +++ b/deepwell/src/services/page_query/service.rs @@ -27,6 +27,7 @@ use crate::models::page_connection::{self, Entity as PageConnection}; use crate::models::page_parent::{self, Entity as PageParent}; use crate::models::{page_revision, text}; use crate::services::{PageService, ParentService}; +use crate::utils::get_category_name; use sea_query::{Expr, Query}; #[derive(Debug)] @@ -486,163 +487,101 @@ impl PageQueryService { /// Takes the output of `find()` and extracts only the fields /// specified in `SelectedFields`, producing a `SelectedPages` - /// result with each page's values as a JSON map. - pub fn select(found: &FoundPages, fields: &SelectedFields) -> Result { + /// result with each page's values populated as typed fields. + pub fn select(found: FoundPages, fields: SelectedFields) -> Result { info!( "Selecting {} fields from {} pages", - fields.0.len(), - found.total() + fields.len(), + found.total(), ); let pages = found .pages - .iter() - .map(|row| { - let mut values = serde_json::Map::new(); - - for field in &fields.0 { - let (key, value) = match field { - SelectedField::PageId => { - ("page_id", serde_json::Value::from(row.page_id)) - } - SelectedField::SiteId => { - ("site_id", serde_json::Value::from(row.site_id)) - } - SelectedField::Title => ( - "title", - row.title - .as_deref() - .map(serde_json::Value::from) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::AltTitle => ( - "alt_title", - row.alt_title - .as_deref() - .map(serde_json::Value::from) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::PageSlug | SelectedField::FullSlug => ( - "slug", - row.slug - .as_deref() - .map(serde_json::Value::from) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::Category => { - // Extract category prefix from slug (e.g. "scp" from "scp:scp-173") - let category = row - .slug - .as_deref() - .and_then(|s| s.split(':').next()) - .filter(|_| { - row.slug - .as_deref() - .map(|s| s.contains(':')) - .unwrap_or(false) - }) - .unwrap_or("_default"); - ("category", serde_json::Value::from(category)) - } - SelectedField::CreatedAt => ( - "created_at", - row.created_at - .map(|t| { - serde_json::Value::from( - t.format(&time::format_description::well_known::Rfc3339) - .unwrap_or_default(), - ) - }) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::CreatedBy => ( - "created_by", - row.created_by - .map(serde_json::Value::from) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::UpdatedAt => ( - "updated_at", - row.updated_at - .map(|t| { - serde_json::Value::from( - t.format(&time::format_description::well_known::Rfc3339) - .unwrap_or_default(), - ) - }) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::UpdatedBy => ( - "updated_by", - row.updated_by - .map(serde_json::Value::from) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::Tags => ( - "tags", - row.tags - .as_ref() - .map(|t| serde_json::Value::from(t.clone())) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::HiddenTags => { - // Hidden tags are those starting with '_' - let hidden = row - .tags - .as_ref() - .map(|tags| { - tags.iter() - .filter(|t| t.starts_with('_')) - .cloned() - .collect::>() - }) - .map(serde_json::Value::from); - ("hidden_tags", hidden.unwrap_or(serde_json::Value::Null)) - } - SelectedField::Score => ( - "score", - row.score - .map(serde_json::Value::from) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::PageCategoryId => ( - "page_category_id", - row.page_category_id - .map(serde_json::Value::from) - .unwrap_or(serde_json::Value::Null), - ), - SelectedField::PageRevisionId => ( - "page_revision_id", - row.page_revision_id - .map(serde_json::Value::from) - .unwrap_or(serde_json::Value::Null), - ), - // Fields that require additional data not yet - // available in FoundPageRow. - SelectedField::ScoreVotes => { - ("score_votes", serde_json::Value::Null) // TODO - } - SelectedField::Revisions => { - ("revisions", serde_json::Value::Null) // TODO - } - SelectedField::Comments => { - ("comments", serde_json::Value::Null) // TODO - } - SelectedField::Children => { - ("children", serde_json::Value::Null) // TODO - } - SelectedField::Size => { - ("size", serde_json::Value::Null) // TODO - } - }; - - values.insert(key.to_owned(), value); - } - - SelectedPageRow { - page_id: row.page_id, - values, - } + .into_iter() + .map(|row| SelectedPageRow { + page_id: row.page_id, + site_id: fields + .contains(SelectedField::SiteId) + .then_some(row.site_id), + title: if fields.contains(SelectedField::Title) { + row.title + } else { + None + }, + alt_title: if fields.contains(SelectedField::AltTitle) { + row.alt_title + } else { + None + }, + slug: if fields.contains(SelectedField::PageSlug) + || fields.contains(SelectedField::FullSlug) + { + row.slug.clone() + } else { + None + }, + // Extract the category prefix from the slug (e.g. "scp" from "scp:scp-173"). + category: if fields.contains(SelectedField::Category) { + row.slug.as_deref().map(|s| get_category_name(s).to_owned()) + } else { + None + }, + created_at: if fields.contains(SelectedField::CreatedAt) { + row.created_at + } else { + None + }, + created_by: if fields.contains(SelectedField::CreatedBy) { + row.created_by + } else { + None + }, + updated_at: if fields.contains(SelectedField::UpdatedAt) { + row.updated_at + } else { + None + }, + updated_by: if fields.contains(SelectedField::UpdatedBy) { + row.updated_by + } else { + None + }, + tags: if fields.contains(SelectedField::Tags) { + row.tags.clone() + } else { + None + }, + // Hidden tags are those starting with '_'. + hidden_tags: if fields.contains(SelectedField::HiddenTags) { + row.tags.as_ref().map(|tags| { + tags.iter() + .filter(|t| t.starts_with('_')) + .cloned() + .collect() + }) + } else { + None + }, + score: if fields.contains(SelectedField::Score) { + row.score + } else { + None + }, + score_votes: None, // TODO: requires vote join + revisions: None, // TODO: requires revision count join + comments: None, // TODO: requires forum post count join + children: None, // TODO: requires page_parent count join + size: None, // TODO: requires text join + page_category_id: if fields.contains(SelectedField::PageCategoryId) { + row.page_category_id + } else { + None + }, + page_revision_id: if fields.contains(SelectedField::PageRevisionId) { + row.page_revision_id + } else { + None + }, }) .collect(); diff --git a/deepwell/src/services/page_query/structs.rs b/deepwell/src/services/page_query/structs.rs index 39bba171b6..2fa922881a 100644 --- a/deepwell/src/services/page_query/structs.rs +++ b/deepwell/src/services/page_query/structs.rs @@ -23,6 +23,7 @@ use super::prelude::*; use crate::services::score::ScoreValue; +use enumset::{EnumSet, EnumSetType}; use sea_orm::prelude::TimeDateTimeWithTimeZone; use std::borrow::Cow; use time::OffsetDateTime; @@ -353,17 +354,11 @@ impl FoundPages { } } -/// Specifies which display fields the caller wants from the -/// page query results. Each variant corresponds to a ListPages -/// output variable like `%%title%%` or `%%created_at%%`. -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] -pub struct SelectedFields(pub Vec); - /// A single field to select from the query results. /// /// Mirrors `PageQueryVariables` but is serializable and /// does not carry lifetime parameters. -#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash)] +#[derive(EnumSetType, Deserialize, Serialize, Debug)] #[serde(rename_all = "snake_case")] pub enum SelectedField { PageId, @@ -389,12 +384,57 @@ pub enum SelectedField { PageRevisionId, } +/// Specifies which display fields the caller wants from the +/// page query results. Each variant corresponds to a ListPages +/// output variable like `%%title%%` or `%%created_at%%`. +pub type SelectedFields = EnumSet; + /// A single page in the selected output, containing only /// the fields requested via `SelectedFields`. -#[derive(Serialize, Debug, Clone, PartialEq)] +#[derive(Serialize, Debug, Clone, PartialEq, Default)] pub struct SelectedPageRow { pub page_id: i64, - pub values: serde_json::Map, + + #[serde(skip_serializing_if = "Option::is_none")] + pub site_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub alt_title: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub slug: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub category: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "time::serde::rfc3339::option")] + pub created_at: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub created_by: Option, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(with = "time::serde::rfc3339::option")] + pub updated_at: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub updated_by: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub hidden_tags: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub score: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub score_votes: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub revisions: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub comments: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub children: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub size: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page_category_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page_revision_id: Option, } /// The result of `PageQueryService::select()`. From fe672e0ad13d7b85e525a9917b259542b2faf783 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:18:23 +0000 Subject: [PATCH 3/4] Bump @sveltejs/kit Bumps the npm_and_yarn group with 1 update in the /framerail directory: [@sveltejs/kit](https://github.com/sveltejs/kit/tree/HEAD/packages/kit). Updates `@sveltejs/kit` from 2.55.0 to 2.57.1 - [Release notes](https://github.com/sveltejs/kit/releases) - [Changelog](https://github.com/sveltejs/kit/blob/main/packages/kit/CHANGELOG.md) - [Commits](https://github.com/sveltejs/kit/commits/@sveltejs/kit@2.57.1/packages/kit) --- updated-dependencies: - dependency-name: "@sveltejs/kit" dependency-version: 2.57.1 dependency-type: direct:production dependency-group: npm_and_yarn ... Signed-off-by: dependabot[bot] --- framerail/package.json | 2 +- framerail/pnpm-lock.yaml | 68 ++++++++++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/framerail/package.json b/framerail/package.json index ec19cc5e34..9e571f6db4 100644 --- a/framerail/package.json +++ b/framerail/package.json @@ -49,7 +49,7 @@ }, "dependencies": { "@sveltejs/adapter-node": "^5.5.4", - "@sveltejs/kit": "^2.53.4", + "@sveltejs/kit": "^2.57.1", "accept-language-parser": "^1.5.0", "json-rpc-2.0": "^1.7.1", "svelte": "^5.53.7", diff --git a/framerail/pnpm-lock.yaml b/framerail/pnpm-lock.yaml index bdf2166eb7..0e14a158d7 100644 --- a/framerail/pnpm-lock.yaml +++ b/framerail/pnpm-lock.yaml @@ -10,10 +10,10 @@ importers: dependencies: '@sveltejs/adapter-node': specifier: ^5.5.4 - version: 5.5.4(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0))) + version: 5.5.4(@sveltejs/kit@2.57.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0))) '@sveltejs/kit': - specifier: ^2.53.4 - version: 2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)) + specifier: ^2.57.1 + version: 2.57.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)) accept-language-parser: specifier: ^1.5.0 version: 1.5.0 @@ -110,7 +110,7 @@ importers: version: 1.6.0(svelte@5.53.12) sveltekit-superforms: specifier: ^2.30.0 - version: 2.30.0(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(@types/json-schema@7.0.15)(svelte@5.53.12)(typescript@5.9.3) + version: 2.30.0(@sveltejs/kit@2.57.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(@types/json-schema@7.0.15)(svelte@5.53.12)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -852,25 +852,33 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@sveltejs/acorn-typescript@1.0.5': resolution: {integrity: sha512-IwQk4yfwLdibDlrXVE04jTZYlLnwsTT2PIOQQGNLWfjavGifnk1JD1LcZjZaBTRcxZu2FfPfNLOE04DSu9lqtQ==} peerDependencies: acorn: ^8.9.0 + '@sveltejs/acorn-typescript@1.0.9': + resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} + peerDependencies: + acorn: ^8.9.0 + '@sveltejs/adapter-node@5.5.4': resolution: {integrity: sha512-45X92CXW+2J8ZUzPv3eLlKWEzINKiiGeFWTjyER4ZN4sGgNoaoeSkCY/QYNxHpPXy71QPsctwccBo9jJs0ySPQ==} peerDependencies: '@sveltejs/kit': ^2.4.0 - '@sveltejs/kit@2.55.0': - resolution: {integrity: sha512-MdFRjevVxmAknf2NbaUkDF16jSIzXMWd4Nfah0Qp8TtQVoSp3bV4jKt8mX7z7qTUTWvgSaxtR0EG5WJf53gcuA==} + '@sveltejs/kit@2.57.1': + resolution: {integrity: sha512-VRdSbB96cI1EnRh09CqmnQqP/YJvET5buj8S6k7CxaJqBJD4bw4fRKDjcarAj/eX9k2eHifQfDH8NtOh+ZxxPw==} engines: {node: '>=18.13'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.0.0 '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 - typescript: ^5.3.3 + typescript: ^5.3.3 || ^6.0.0 vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 peerDependenciesMeta: '@opentelemetry/api': @@ -1205,6 +1213,9 @@ packages: devalue@5.6.4: resolution: {integrity: sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==} + devalue@5.7.1: + resolution: {integrity: sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA==} + devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} @@ -1958,8 +1969,8 @@ packages: engines: {node: '>=10'} hasBin: true - set-cookie-parser@3.0.1: - resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + set-cookie-parser@3.1.0: + resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} @@ -1973,8 +1984,8 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - sirv@3.0.1: - resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} + sirv@3.0.2: + resolution: {integrity: sha512-2wcC/oGxHis/BoHkkPwldgiPSYcpZK3JU28WoMVv55yHJgcZ8rlXvuG9iZggz+sU1d4bRgIGASwyWqjxu3FM0g==} engines: {node: '>=18'} slash@5.1.0: @@ -2811,35 +2822,42 @@ snapshots: '@sindresorhus/merge-streams@4.0.0': {} - '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.0.0': + optional: true + + '@standard-schema/spec@1.1.0': {} '@sveltejs/acorn-typescript@1.0.5(acorn@8.16.0)': dependencies: acorn: 8.16.0 - '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))': + '@sveltejs/acorn-typescript@1.0.9(acorn@8.16.0)': + dependencies: + acorn: 8.16.0 + + '@sveltejs/adapter-node@5.5.4(@sveltejs/kit@2.57.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))': dependencies: '@rollup/plugin-commonjs': 29.0.0(rollup@4.59.0) '@rollup/plugin-json': 6.1.0(rollup@4.59.0) '@rollup/plugin-node-resolve': 16.0.2(rollup@4.59.0) - '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)) + '@sveltejs/kit': 2.57.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)) rollup: 4.59.0 - '@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0))': + '@sveltejs/kit@2.57.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0))': dependencies: - '@standard-schema/spec': 1.0.0 - '@sveltejs/acorn-typescript': 1.0.5(acorn@8.16.0) + '@standard-schema/spec': 1.1.0 + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.16.0) '@sveltejs/vite-plugin-svelte': 6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)) '@types/cookie': 0.6.0 acorn: 8.16.0 cookie: 0.6.0 - devalue: 5.6.4 + devalue: 5.7.1 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 mrmime: 2.0.1 - set-cookie-parser: 3.0.1 - sirv: 3.0.1 + set-cookie-parser: 3.1.0 + sirv: 3.0.2 svelte: 5.53.12 vite: 7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0) optionalDependencies: @@ -3184,6 +3202,8 @@ snapshots: devalue@5.6.4: {} + devalue@5.7.1: {} + devlop@1.1.0: dependencies: dequal: 2.0.3 @@ -4045,7 +4065,7 @@ snapshots: semver@7.7.4: {} - set-cookie-parser@3.0.1: {} + set-cookie-parser@3.1.0: {} shebang-command@2.0.0: dependencies: @@ -4055,7 +4075,7 @@ snapshots: signal-exit@4.1.0: {} - sirv@3.0.1: + sirv@3.0.2: dependencies: '@polka/url': 1.0.0-next.29 mrmime: 2.0.1 @@ -4221,9 +4241,9 @@ snapshots: magic-string: 0.30.21 zimmerframe: 1.1.4 - sveltekit-superforms@2.30.0(@sveltejs/kit@2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(@types/json-schema@7.0.15)(svelte@5.53.12)(typescript@5.9.3): + sveltekit-superforms@2.30.0(@sveltejs/kit@2.57.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(@types/json-schema@7.0.15)(svelte@5.53.12)(typescript@5.9.3): dependencies: - '@sveltejs/kit': 2.55.0(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)) + '@sveltejs/kit': 2.57.1(@sveltejs/vite-plugin-svelte@6.2.4(svelte@5.53.12)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)))(svelte@5.53.12)(typescript@5.9.3)(vite@7.3.2(@types/node@25.5.2)(jiti@2.6.1)(sass@1.99.0)) devalue: 5.6.4 memoize-weak: 1.0.2 svelte: 5.53.12 From 0d6dc1682ae7ada9a6c968fcbc55b53b92ab6fcb Mon Sep 17 00:00:00 2001 From: Syxles Date: Sat, 11 Apr 2026 14:31:21 +0200 Subject: [PATCH 4/4] WJ-1361: split slug into full_slug, page_slug, and category fields --- deepwell/src/services/page_query/service.rs | 13 +++++++------ deepwell/src/services/page_query/structs.rs | 4 +++- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/deepwell/src/services/page_query/service.rs b/deepwell/src/services/page_query/service.rs index 0546179463..e6012361bf 100644 --- a/deepwell/src/services/page_query/service.rs +++ b/deepwell/src/services/page_query/service.rs @@ -27,7 +27,7 @@ use crate::models::page_connection::{self, Entity as PageConnection}; use crate::models::page_parent::{self, Entity as PageParent}; use crate::models::{page_revision, text}; use crate::services::{PageService, ParentService}; -use crate::utils::get_category_name; +use crate::utils::{get_category_name, split_category}; use sea_query::{Expr, Query}; #[derive(Debug)] @@ -513,14 +513,16 @@ impl PageQueryService { } else { None }, - slug: if fields.contains(SelectedField::PageSlug) - || fields.contains(SelectedField::FullSlug) - { + full_slug: if fields.contains(SelectedField::FullSlug) { row.slug.clone() } else { None }, - // Extract the category prefix from the slug (e.g. "scp" from "scp:scp-173"). + page_slug: if fields.contains(SelectedField::PageSlug) { + row.slug.as_deref().map(|s| split_category(s).1.to_owned()) + } else { + None + }, category: if fields.contains(SelectedField::Category) { row.slug.as_deref().map(|s| get_category_name(s).to_owned()) } else { @@ -551,7 +553,6 @@ impl PageQueryService { } else { None }, - // Hidden tags are those starting with '_'. hidden_tags: if fields.contains(SelectedField::HiddenTags) { row.tags.as_ref().map(|tags| { tags.iter() diff --git a/deepwell/src/services/page_query/structs.rs b/deepwell/src/services/page_query/structs.rs index 2fa922881a..1916cd7c2c 100644 --- a/deepwell/src/services/page_query/structs.rs +++ b/deepwell/src/services/page_query/structs.rs @@ -402,7 +402,9 @@ pub struct SelectedPageRow { #[serde(skip_serializing_if = "Option::is_none")] pub alt_title: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub slug: Option, + pub full_slug: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub page_slug: Option, #[serde(skip_serializing_if = "Option::is_none")] pub category: Option, #[serde(skip_serializing_if = "Option::is_none")]