Skip to content

Commit

Permalink
Merge pull request #2105 from scpwiki/frontend
Browse files Browse the repository at this point in the history
Frontend things [5]
  • Loading branch information
emmiegit authored Sep 30, 2024
2 parents 599751d + b84fe2e commit da245f7
Show file tree
Hide file tree
Showing 38 changed files with 1,042 additions and 107 deletions.
2 changes: 2 additions & 0 deletions deepwell/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ async fn build_module(app_state: ServerState) -> anyhow::Result<RpcModule<Server
// Web server
register!("page_view", page_view);
register!("user_view", user_view);
register!("admin_view", admin_view);

// Authentication
register!("login", auth_login);
Expand Down Expand Up @@ -226,6 +227,7 @@ async fn build_module(app_state: ServerState) -> anyhow::Result<RpcModule<Server
register!("page_create", page_create);
register!("page_get", page_get);
register!("page_get_direct", page_get_direct);
register!("page_get_deleted", page_get_deleted);
register!("page_get_score", page_get_score);
register!("page_edit", page_edit);
register!("page_delete", page_delete);
Expand Down
57 changes: 54 additions & 3 deletions deepwell/src/endpoints/page.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ use super::prelude::*;
use crate::models::page::Model as PageModel;
use crate::services::page::{
CreatePage, CreatePageOutput, DeletePage, DeletePageOutput, EditPage, EditPageOutput,
GetPageAnyDetails, GetPageDirect, GetPageOutput, GetPageReference,
GetPageReferenceDetails, GetPageScoreOutput, MovePage, MovePageOutput, RestorePage,
RestorePageOutput, RollbackPage, SetPageLayout,
GetDeletedPageOutput, GetPageAnyDetails, GetPageDirect, GetPageOutput,
GetPageReference, GetPageReferenceDetails, GetPageScoreOutput, GetPageSlug, MovePage,
MovePageOutput, RestorePage, RestorePageOutput, RollbackPage, SetPageLayout,
};
use crate::services::{Result, TextService};
use crate::web::{PageDetails, Reference};
use futures::future::try_join_all;

pub async fn page_create(
ctx: &ServiceContext<'_>,
Expand Down Expand Up @@ -73,6 +74,27 @@ pub async fn page_get_direct(
}
}

pub async fn page_get_deleted(
ctx: &ServiceContext<'_>,
params: Params<'static>,
) -> Result<Vec<GetDeletedPageOutput>> {
let GetPageSlug { site_id, slug } = params.parse()?;

info!("Getting deleted page {slug} in site ID {site_id}");
let get_deleted_page = PageService::get_deleted_by_slug(ctx, site_id, &slug)
.await?
.into_iter()
.map(|page| build_page_deleted_output(ctx, page));

let result = try_join_all(get_deleted_page)
.await?
.into_iter()
.flatten()
.collect();

Ok(result)
}

pub async fn page_get_score(
ctx: &ServiceContext<'_>,
params: Params<'static>,
Expand Down Expand Up @@ -235,3 +257,32 @@ async fn build_page_output(
layout,
}))
}

async fn build_page_deleted_output(
ctx: &ServiceContext<'_>,
page: PageModel,
) -> Result<Option<GetDeletedPageOutput>> {
// Get page revision
let revision =
PageRevisionService::get_latest(ctx, page.site_id, page.page_id).await?;

// Calculate score and determine layout
let rating = ScoreService::score(ctx, page.page_id).await?;

// Build result struct
Ok(Some(GetDeletedPageOutput {
page_id: page.page_id,
page_created_at: page.created_at,
page_updated_at: page.updated_at,
page_deleted_at: page.deleted_at.expect("Page should be deleted"),
page_revision_count: revision.revision_number,
site_id: page.site_id,
discussion_thread_id: page.discussion_thread_id,
hidden_fields: revision.hidden,
title: revision.title,
alt_title: revision.alt_title,
slug: revision.slug,
tags: revision.tags,
rating,
}))
}
12 changes: 11 additions & 1 deletion deepwell/src/endpoints/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@

use super::prelude::*;
use crate::services::view::{
GetPageView, GetPageViewOutput, GetUserView, GetUserViewOutput,
GetAdminView, GetAdminViewOutput, GetPageView, GetPageViewOutput, GetUserView,
GetUserViewOutput,
};

/// Returns relevant context for rendering a page from a processed web request.
Expand All @@ -40,3 +41,12 @@ pub async fn user_view(
let input: GetUserView = params.parse()?;
ViewService::user(ctx, input).await
}

/// Returns relevant context for rendering admin panel from a processed web request.
pub async fn admin_view(
ctx: &ServiceContext<'_>,
params: Params<'static>,
) -> Result<GetAdminViewOutput> {
let input: GetAdminView = params.parse()?;
ViewService::admin(ctx, input).await
}
30 changes: 25 additions & 5 deletions deepwell/src/services/page/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,29 @@ impl PageService {
Ok(page)
}

/// Gets all deleted pages that match the provided slug.
pub async fn get_deleted_by_slug(
ctx: &ServiceContext<'_>,
site_id: i64,
slug: &str,
) -> Result<Vec<PageModel>> {
let txn = ctx.transaction();
let pages = {
Page::find()
.filter(
Condition::all()
.add(page::Column::Slug.eq(trim_default(slug)))
.add(page::Column::SiteId.eq(site_id))
.add(page::Column::DeletedAt.is_not_null()),
)
.order_by_desc(page::Column::CreatedAt)
.all(txn)
.await?
};

Ok(pages)
}

/// Get the layout associated with this page.
///
/// If this page has a specific layout override,
Expand All @@ -576,13 +599,10 @@ impl PageService {
page_id: i64,
) -> Result<Layout> {
debug!("Getting page layout for site ID {site_id} page ID {page_id}");
let page = Self::get(ctx, site_id, Reference::Id(page_id)).await?;
let page = Self::get_direct(ctx, page_id, true).await?;
match page.layout {
// Parse layout from string in page table
Some(layout) => match layout.parse() {
Ok(layout) => Ok(layout),
Err(_) => Err(Error::InvalidEnumValue),
},
Some(layout) => layout.parse().map_err(|_| Error::InvalidEnumValue),

// Fallback to site layout
None => SiteService::get_layout(ctx, site_id).await,
Expand Down
23 changes: 23 additions & 0 deletions deepwell/src/services/page/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ pub struct GetPageReferenceDetails<'a> {
pub details: PageDetails,
}

#[derive(Deserialize, Debug, Clone)]
pub struct GetPageSlug {
pub site_id: i64,
pub slug: String,
}

#[derive(Deserialize, Debug, Clone)]
pub struct GetPageDirect {
pub site_id: i64,
Expand Down Expand Up @@ -113,6 +119,23 @@ pub struct GetPageOutput {
pub layout: Layout,
}

#[derive(Serialize, Debug, Clone)]
pub struct GetDeletedPageOutput {
pub page_id: i64,
pub page_created_at: OffsetDateTime,
pub page_updated_at: Option<OffsetDateTime>,
pub page_deleted_at: OffsetDateTime,
pub page_revision_count: i32,
pub site_id: i64,
pub discussion_thread_id: Option<i64>,
pub hidden_fields: Vec<String>,
pub title: String,
pub alt_title: Option<String>,
pub slug: String,
pub tags: Vec<String>,
pub rating: ScoreValue,
}

#[derive(Serialize, Debug, Clone)]
pub struct GetPageScoreOutput {
pub page_id: i64,
Expand Down
11 changes: 1 addition & 10 deletions deepwell/src/services/relation/site_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,7 @@ impl RelationService {
}

// Checks done, create
create_operation!(
ctx,
SiteMember,
Site,
site_id,
User,
user_id,
created_by,
&()
)
create_operation!(ctx, SiteUser, Site, site_id, User, user_id, created_by, &())
}

pub async fn get_site_user_id_for_site(
Expand Down
5 changes: 1 addition & 4 deletions deepwell/src/services/special_page/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ impl SpecialPageService {
SpecialPageType::Banned => {
(vec![cow!(config.special_page_banned)], "wiki-page-banned")
}
SpecialPageType::Unauthorized => (vec![], "admin-unauthorized"),
};

// Look through each option to get the special page wikitext.
Expand Down Expand Up @@ -131,10 +132,6 @@ impl SpecialPageService {
page_info: &PageInfo<'_>,
) -> Result<String> {
debug!("Getting wikitext for special page, {} slugs", slugs.len());
debug_assert!(
!slugs.is_empty(),
"No slugs to check for special page existence",
);

// Try all the pages listed.
for slug in slugs {
Expand Down
1 change: 1 addition & 0 deletions deepwell/src/services/special_page/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ pub enum SpecialPageType {
Missing,
Private,
Banned,
Unauthorized,
}

#[derive(Serialize, Debug)]
Expand Down
109 changes: 109 additions & 0 deletions deepwell/src/services/view/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,105 @@ impl ViewService {
Ok(output)
}

pub async fn admin(
ctx: &ServiceContext<'_>,
GetAdminView {
domain,
locales: locales_str,
session_token,
}: GetAdminView,
) -> Result<GetAdminViewOutput> {
info!(
"Getting site view data for domain '{}', locales '{:?}'",
domain, locales_str,
);

// Parse all locales
let mut locales = parse_locales(&locales_str)?;
let config = ctx.config();

// Attempt to get a viewer helper structure, but if the site doesn't exist
// then return right away with the "no such site" response.
let viewer = match Self::get_viewer(
ctx,
&mut locales,
&domain,
session_token.ref_map(|s| s.as_str()),
)
.await?
{
ViewerResult::FoundSite(viewer) => viewer,
ViewerResult::MissingSite(html) => {
return Ok(GetAdminViewOutput::SiteMissing { html });
}
};

let page_info = PageInfo {
page: cow!(""),
category: cow_opt!(Some("admin")),
title: cow!(""),
alt_title: None,
site: cow!(viewer.site.slug),
score: ScoreValue::Integer(0),
tags: vec![],
language: if !locales.is_empty() {
Cow::Owned(locales[0].to_string())
} else {
cow!(viewer.site.locale)
},
};

let GetSpecialPageOutput {
wikitext: _,
render_output,
} = SpecialPageService::get(
ctx,
&viewer.site,
SpecialPageType::Unauthorized,
&locales,
config.default_page_layout,
page_info,
)
.await?;

let RenderOutput {
html_output:
HtmlOutput {
body: compiled_html,
..
},
..
} = render_output;

// Check user access to site settings
let user_permissions = match viewer.user_session {
Some(ref session) => session.user_permissions,
None => {
debug!("No user for session, disallow admin access");

return Ok(GetAdminViewOutput::AdminPermissions {
viewer,
html: compiled_html,
});
}
};

// Determine whether to return the actual admin panel content
let output = if Self::can_access_admin(ctx, user_permissions).await? {
debug!("User has admin access, return data");
GetAdminViewOutput::SiteFound { viewer }
} else {
warn!("User doesn't have admin access, returning permission page");

GetAdminViewOutput::AdminPermissions {
viewer,
html: compiled_html,
}
};

Ok(output)
}

/// Gets basic data and runs common logic for all web routes.
///
/// All views seen by end users require a few translations before
Expand Down Expand Up @@ -510,6 +609,16 @@ impl ViewService {
Ok(true)
}

async fn can_access_admin(
_ctx: &ServiceContext<'_>,
permissions: UserPermissions,
) -> Result<bool> {
info!("Checking admin access: {permissions:?}");
debug!("TODO: stub");
// TODO perform permission checks
Ok(true)
}

fn should_redirect_site(
ctx: &ServiceContext,
site: &SiteModel,
Expand Down
Loading

0 comments on commit da245f7

Please sign in to comment.