Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 61 additions & 4 deletions deepwell/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions deepwell/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
104 changes: 104 additions & 0 deletions deepwell/src/services/page_query/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -483,4 +484,107 @@ 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 populated as typed fields.
pub fn select(found: FoundPages, fields: SelectedFields) -> Result<SelectedPages> {
info!(
"Selecting {} fields from {} pages",
fields.len(),
found.total(),
);

let pages = found
.pages
.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)
Comment thread
0x5267 marked this conversation as resolved.
Outdated
{
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())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is closer, though I was saying I think we should also modify the struct type row uses to have full_slug, page_slug, and category, so here we're just moving the field instead of processing it.

(That is, this processing, e.g. get_category_name(), is done when building SelectedPageRow instead of here)

} 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();

Ok(SelectedPages { pages })
}
}
100 changes: 100 additions & 0 deletions deepwell/src/services/page_query/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -352,3 +353,102 @@ impl FoundPages {
self.pages.len()
}
}

/// A single field to select from the query results.
///
/// Mirrors `PageQueryVariables` but is serializable and
/// does not carry lifetime parameters.
#[derive(EnumSetType, Deserialize, Serialize, Debug)]
#[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,
}

/// 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<SelectedField>;

/// A single page in the selected output, containing only
/// the fields requested via `SelectedFields`.
#[derive(Serialize, Debug, Clone, PartialEq, Default)]
pub struct SelectedPageRow {
pub page_id: i64,

#[serde(skip_serializing_if = "Option::is_none")]
pub site_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub alt_title: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub slug: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub category: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub created_at: Option<TimeDateTimeWithTimeZone>,
#[serde(skip_serializing_if = "Option::is_none")]
pub created_by: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(with = "time::serde::rfc3339::option")]
pub updated_at: Option<TimeDateTimeWithTimeZone>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated_by: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hidden_tags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score: Option<f32>,
#[serde(skip_serializing_if = "Option::is_none")]
pub score_votes: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub revisions: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub comments: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub children: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_category_id: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub page_revision_id: Option<i64>,
}

/// 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<SelectedPageRow>,
}

impl SelectedPages {
#[inline]
pub fn total(&self) -> usize {
self.pages.len()
}
}
Loading