Skip to content

Commit f8e538e

Browse files
authored
Merge pull request #2354 from scpwiki/WJ-1224-text-blocks
[WJ-1224] [WJ-99] [WJ-1301] [WJ-1302] Implement hosted text blocks
2 parents 9d67bc1 + 8c2569c commit f8e538e

File tree

45 files changed

+1828
-253
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1828
-253
lines changed

deepwell/Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deepwell/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ keywords = ["wikijump", "api", "backend", "wiki"]
88
categories = ["asynchronous", "database", "web-programming::http-server"]
99
exclude = [".gitignore", ".editorconfig"]
1010

11-
version = "2025.4.21"
11+
version = "2025.5.4"
1212
authors = ["Emmie Smith <[email protected]>"]
1313
edition = "2021"
1414

deepwell/config.example.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,10 @@ path = "../locales"
237237
# aggressive, but still not extremely long.
238238
render-timeout-ms = 2000
239239

240+
# Similarly, this is how long that the preprocessing steps are allowed to take.
241+
# This value should be shorter than the render timeout.
242+
preprocess-timeout-ms = 500
243+
240244

241245
# Under what conditions a rerender job should be skipped rather than processed.
242246
#

deepwell/migrations/20220906103252_deepwell.sql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,7 @@ CREATE TABLE text_block (
580580
page_id BIGINT NOT NULL REFERENCES page(page_id),
581581
block_index SMALLINT NOT NULL CHECK (block_index > 0),
582582
block_name TEXT CHECK (length(block_name) > 0),
583+
text_type TEXT,
583584

584585
PRIMARY KEY (block_type, page_id, block_index),
585586
UNIQUE (page_id, block_name)

deepwell/src/api.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ use crate::config::{Config, Secrets};
3030
use crate::endpoints::{
3131
auth::*, blob::*, category::*, domain::*, email::*, file::*, file_revision::*,
3232
info::*, link::*, locale::*, message::*, misc::*, page::*, page_revision::*,
33-
parent::*, routing::*, site::*, site_member::*, special_error::*, text::*, user::*,
34-
user_bot::*, view::*, vote::*,
33+
parent::*, routing::*, site::*, site_member::*, special_error::*, text::*,
34+
text_block::*, user::*, user_bot::*, view::*, vote::*,
3535
};
3636
use crate::locales::Localizations;
3737
use crate::services::blob::MimeAnalyzer;
@@ -223,6 +223,17 @@ async fn build_module(app_state: ServerState) -> anyhow::Result<RpcModule<Server
223223
"special_error_missing_custom_domain",
224224
special_error_missing_custom_domain,
225225
);
226+
register!(
227+
"special_error_missing_page_slug",
228+
special_error_missing_page_slug,
229+
);
230+
register!("special_error_page_fetch", special_error_page_fetch);
231+
register!(
232+
"special_error_missing_file_name",
233+
special_error_missing_file_name,
234+
);
235+
register!("special_error_file_fetch", special_error_file_fetch);
236+
register!("special_error_text_block", special_error_text_block);
226237
register!("special_error_file_root", special_error_file_root);
227238

228239
// Authentication
@@ -293,6 +304,9 @@ async fn build_module(app_state: ServerState) -> anyhow::Result<RpcModule<Server
293304
register!("parent_get_all", parent_get_all);
294305
register!("parent_update", parent_update);
295306

307+
// Hosted text blocks
308+
register!("text_block_get_index", text_block_get_index);
309+
296310
// Blob data
297311
register!("blob_get", blob_get);
298312
register!("blob_upload", blob_upload);

deepwell/src/config/file.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ struct Domain {
144144
#[derive(Serialize, Deserialize, Debug, Clone)]
145145
#[serde(rename_all = "kebab-case")]
146146
struct Ftml {
147+
preprocess_timeout_ms: u64,
147148
render_timeout_ms: u64,
148149
rerender_skip: Vec<RerenderSkip>,
149150
layout: FtmlLayout,
@@ -292,6 +293,7 @@ impl ConfigFile {
292293
},
293294
ftml:
294295
Ftml {
296+
preprocess_timeout_ms,
295297
render_timeout_ms,
296298
rerender_skip,
297299
layout:
@@ -407,6 +409,7 @@ impl ConfigFile {
407409
job_lift_expired_punishments: StdDuration::from_secs(
408410
job_lift_expired_punishments_secs,
409411
),
412+
preprocess_timeout: StdDuration::from_millis(preprocess_timeout_ms),
410413
render_timeout: StdDuration::from_millis(render_timeout_ms),
411414
rerender_skip: rerender_skip
412415
.iter()

deepwell/src/config/object.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,10 @@ pub struct Config {
143143
/// How often to run the "lift expired punishments" recurring job.
144144
pub job_lift_expired_punishments: StdDuration,
145145

146-
/// Maximum run time for a render request.
146+
/// Maximum run time for the preprocessing steps of wikitext.
147+
pub preprocess_timeout: StdDuration,
148+
149+
/// Maximum run time for parsing and rendering wikitext.
147150
pub render_timeout: StdDuration,
148151

149152
/// In what circumstances a page rerender should be skipped.

deepwell/src/endpoints/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub mod site;
6262
pub mod site_member;
6363
pub mod special_error;
6464
pub mod text;
65+
pub mod text_block;
6566
pub mod user;
6667
pub mod user_bot;
6768
pub mod view;

deepwell/src/endpoints/special_error.rs

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,122 @@ pub async fn special_error_missing_custom_domain(
5252
SpecialErrorService::missing_custom_domain(ctx, &locales, &domain).await
5353
}
5454

55+
pub async fn special_error_missing_page_slug(
56+
ctx: &ServiceContext<'_>,
57+
params: Params<'static>,
58+
) -> Result<SpecialErrorOutput> {
59+
#[derive(Deserialize, Debug)]
60+
struct Input {
61+
locales: Vec<String>,
62+
site_id: i64,
63+
page_slug: String,
64+
}
65+
66+
let Input {
67+
locales,
68+
site_id,
69+
page_slug,
70+
} = params.parse()?;
71+
72+
let locales = parse_locales(&locales)?;
73+
SpecialErrorService::missing_page_slug(ctx, &locales, site_id, &page_slug).await
74+
}
75+
76+
pub async fn special_error_page_fetch(
77+
ctx: &ServiceContext<'_>,
78+
params: Params<'static>,
79+
) -> Result<SpecialErrorOutput> {
80+
#[derive(Deserialize, Debug)]
81+
struct Input {
82+
locales: Vec<String>,
83+
site_id: i64,
84+
page_slug: String,
85+
}
86+
87+
let Input {
88+
locales,
89+
site_id,
90+
page_slug,
91+
} = params.parse()?;
92+
93+
let locales = parse_locales(&locales)?;
94+
SpecialErrorService::page_fetch(ctx, &locales, site_id, &page_slug).await
95+
}
96+
97+
pub async fn special_error_missing_file_name(
98+
ctx: &ServiceContext<'_>,
99+
params: Params<'static>,
100+
) -> Result<SpecialErrorOutput> {
101+
#[derive(Deserialize, Debug)]
102+
struct Input {
103+
locales: Vec<String>,
104+
site_id: i64,
105+
page_slug: String,
106+
filename: String,
107+
}
108+
109+
let Input {
110+
locales,
111+
site_id,
112+
page_slug,
113+
filename,
114+
} = params.parse()?;
115+
116+
let locales = parse_locales(&locales)?;
117+
SpecialErrorService::missing_file_name(ctx, &locales, site_id, &page_slug, &filename)
118+
.await
119+
}
120+
121+
pub async fn special_error_file_fetch(
122+
ctx: &ServiceContext<'_>,
123+
params: Params<'static>,
124+
) -> Result<SpecialErrorOutput> {
125+
#[derive(Deserialize, Debug)]
126+
struct Input {
127+
locales: Vec<String>,
128+
site_id: i64,
129+
page_slug: String,
130+
filename: String,
131+
}
132+
133+
let Input {
134+
locales,
135+
site_id,
136+
page_slug,
137+
filename,
138+
} = params.parse()?;
139+
140+
let locales = parse_locales(&locales)?;
141+
SpecialErrorService::file_fetch(ctx, &locales, site_id, &page_slug, &filename).await
142+
}
143+
144+
pub async fn special_error_text_block(
145+
ctx: &ServiceContext<'_>,
146+
params: Params<'static>,
147+
) -> Result<SpecialErrorOutput> {
148+
#[derive(Deserialize, Debug)]
149+
struct Input {
150+
locales: Vec<String>,
151+
site_id: i64,
152+
index: String,
153+
block_type: String,
154+
reason: String,
155+
}
156+
157+
let Input {
158+
locales,
159+
site_id,
160+
index,
161+
block_type,
162+
reason,
163+
} = params.parse()?;
164+
165+
let locales = parse_locales(&locales)?;
166+
167+
SpecialErrorService::text_block(ctx, &locales, site_id, &index, &block_type, &reason)
168+
.await
169+
}
170+
55171
pub async fn special_error_file_root(
56172
ctx: &ServiceContext<'_>,
57173
params: Params<'static>,
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* endpoints/text.rs
3+
*
4+
* DEEPWELL - Wikijump API provider and database manager
5+
* Copyright (C) 2019-2025 Wikijump Team
6+
*
7+
* This program is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU Affero General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU Affero General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU Affero General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
21+
use super::prelude::*;
22+
use crate::models::sea_orm_active_enums::TextBlockType;
23+
use crate::services::text_block::TextBlockIndex;
24+
25+
#[derive(Deserialize, Debug, Clone)]
26+
struct GetIndexInput {
27+
page_id: i64,
28+
block_type: TextBlockType,
29+
name: String,
30+
}
31+
32+
pub async fn text_block_get_index(
33+
ctx: &ServiceContext<'_>,
34+
params: Params<'static>,
35+
) -> Result<Option<TextBlockIndex>> {
36+
let GetIndexInput {
37+
page_id,
38+
block_type,
39+
name,
40+
} = params.parse()?;
41+
42+
TextBlockService::get_block_index(ctx, page_id, block_type, &name).await
43+
}

0 commit comments

Comments
 (0)