Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions deepwell/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,7 @@ async fn build_module(app_state: ServerState) -> Result<RpcModule<ServerState>>
register!("import_wikidot_user", import_wikidot_user);
register!("import_wikidot_site", import_wikidot_site);
register!("import_wikidot_page", import_wikidot_page);
register!("import_wikidot_page_revision", import_wikidot_page_revision);

// Return
Ok(module)
Expand Down
19 changes: 17 additions & 2 deletions deepwell/src/endpoints/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@

use super::prelude::*;
use crate::services::import::{
ImportPage, ImportPageOutput, ImportService, ImportSite, ImportSiteOutput,
ImportUser, ImportUserOutput,
ImportPage, ImportPageOutput, ImportPageRevision, ImportPageRevisionOutput,
ImportService, ImportSite, ImportSiteOutput, ImportUser, ImportUserOutput,
};

pub async fn import_wikidot_user(
Expand Down Expand Up @@ -56,3 +56,18 @@ pub async fn import_wikidot_page(
Error::new("failed to import wikidot page", ErrorType::DatabaseImport)
})
}

pub async fn import_wikidot_page_revision(
ctx: &ServiceContext<'_>,
params: Params<'static>,
) -> Result<ImportPageRevisionOutput> {
let input: ImportPageRevision = parse!(params, DatabaseImport);
ImportService::add_page_revision(ctx, input)
.await
.or_raise(|| {
Error::new(
"failed to import wikidot page revision",
ErrorType::DatabaseImport,
)
})
}
214 changes: 211 additions & 3 deletions deepwell/src/services/import/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,24 @@ use crate::constants::SYSTEM_USER_ID;
use crate::models::known_user::{self, Model as KnownUserModel};
use crate::models::page::{self, Entity as Page};
use crate::models::page_category::Model as PageCategoryModel;
use crate::models::page_revision::{
self, Entity as PageRevision, Model as PageRevisionModel,
};
use crate::models::site::{self, Entity as Site};
use crate::models::wikidot_user::{self, Entity as WikidotUser};
use crate::services::audit::{AuditEvent, AuditService};
use crate::services::blob::{BlobService, FinalizeBlobUploadOutput};
use crate::services::page_lock::{CreatePageLockInput, PageLockService};
use crate::services::{CategoryService, UserService};
use crate::types::PageLockType;
use crate::services::page_revision::{
CreateFirstPageRevision, CreateFirstPageRevisionOutput, CreatePageRevision,
CreatePageRevisionBody, CreatePageRevisionOutput, PageRevisionService,
};
use crate::services::{CategoryService, TextService, UserService};
use crate::types::{PageId, PageLockType};
use crate::utils::get_category_name;
use ftml::layout::Layout;
use sea_orm::UpdateResult;
use sea_query::{Expr, Query};

#[derive(Debug)]
pub struct ImportService;
Expand Down Expand Up @@ -277,7 +287,205 @@ impl ImportService {
Ok(ImportPageOutput { site_id, page_id })
}

// TODO page_revision
pub async fn add_page_revision(
ctx: &ServiceContext<'_>,
ImportPageRevision {
revision_id,
revision_type,
created_at,
updated_at,
revision_number,
page_id,
site_id,
user_id,
wikitext,
comments,
title,
slug,
tags,
}: ImportPageRevision,
) -> Result<ImportPageRevisionOutput> {
info!(
"Creating page revision ID {} (number {}) on page ID {} on site ID {}",
revision_id, revision_number, page_id, site_id,
);

let txn = ctx.transaction();
let make_error = || {
Error::new(
format!(
"failed to import page revision ID {} (number {}) on page ID {} in site ID {}",
revision_id, revision_number, page_id, site_id,
),
ErrorType::DatabaseImport,
)
};

// Get page category
let PageCategoryModel { category_id, .. } =
CategoryService::get_or_create(ctx, site_id, get_category_name(&slug))
.await
.or_raise(make_error)?;

// Get prior revision
//
// Import operations don't require an initial page revision on page import,
// so it's possible that this is None.
//
// Then we check that the revision_number being inserted is one more than the
// prior revision (or 0 if None, i.e. this is the first).
let prev_revision = PageRevision::find()
.filter(
Condition::all()
.add(page_revision::Column::SiteId.eq(site_id))
.add(page_revision::Column::PageId.eq(page_id)),
)
.order_by_desc(page_revision::Column::RevisionNumber)
.one(txn)
.await
.or_raise(make_error)?;

let output_revision_id = match prev_revision {
// No prior revisions
// First revision for the page
None => {
if revision_number != 0 {
bail!(Error::new(
format!(
"failed to import page revision ID {} (number {}), because there are no prior revisions (should've been 0)",
revision_id, revision_number,
),
ErrorType::DatabaseImport,
));
}

let CreateFirstPageRevisionOutput {
revision_id: output_revision_id,
parser_errors: _,
} = PageRevisionService::create_first(
ctx,
PageId {
site_id,
category_id,
page_id,
},
CreateFirstPageRevision {
user_id,
comments,
wikitext,
title,
alt_title: None,
slug,
layout: Some(Layout::Wikidot),
},
)
.await
.or_raise(make_error)?;

output_revision_id
}

// Prior revisions
// Second revision or later
Some(prev_revision) => {
if revision_number != prev_revision.revision_number + 1 {
bail!(Error::new(
format!(
"failed to import page revision ID {} (number {}), because the prior revision was {} (should've been {})",
revision_id,
revision_number,
prev_revision.revision_number,
prev_revision.revision_number + 1,
),
ErrorType::DatabaseImport
));
}

let output = PageRevisionService::create(
ctx,
PageId {
site_id,
category_id,
page_id,
},
CreatePageRevision {
user_id,
comments,
revision_type,
body: CreatePageRevisionBody {
wikitext: Maybe::Set(wikitext),
title: Maybe::Set(title),
alt_title: Maybe::Unset,
slug: Maybe::Set(slug),
// NOTE: We set tags here so the "changes" value
// is correct for this revision.
tags: Maybe::Set(tags.clone()),
},
},
prev_revision,
)
.await
.or_raise(make_error)?;

match output {
// There should always be an output, if not, raise an error
None => bail!(Error::new(
format!(
"failed to import page revision ID {} (number {}), because no data was changed from the last revision",
revision_id, revision_number,
),
ErrorType::DatabaseImport
)),

// Extract revision ID and validate revision number
Some(CreatePageRevisionOutput {
revision_number: output_revision_number,
revision_id: output_revision_id,
parser_errors: _,
}) => {
assert_eq!(
revision_number, output_revision_number,
"Newly-created revision has a revision number of {}, which is not {} as expected",
output_revision_number, revision_number,
);

output_revision_id
}
}
}
};

// Update database fields that we can't set via PageRevisionService
//
// We need to use update_many since this changes the primary key,
// but only one row should be modified by this.

let UpdateResult { rows_affected, .. } = PageRevision::update_many()
.col_expr(page_revision::Column::RevisionId, Expr::value(revision_id))
.col_expr(page_revision::Column::CreatedAt, Expr::value(created_at))
.col_expr(page_revision::Column::UpdatedAt, Expr::value(updated_at))
// not settable on page create
.col_expr(page_revision::Column::Tags, Expr::value(tags))
.filter(page_revision::Column::RevisionId.eq(output_revision_id))
.exec(txn)
.await
.or_raise(make_error)?;

assert_eq!(
rows_affected, 1,
"More than one row updated in page_revision after revision creation",
);

// Return output

Ok(ImportPageRevisionOutput {
site_id,
page_id,
page_revision_id: revision_id,
page_revision_number: revision_number,
})
}

// TODO page_vote

// TODO file
Expand Down
26 changes: 26 additions & 0 deletions deepwell/src/services/import/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

use crate::types::PageRevisionType;
use ftml::data::KarmaLevel;
use std::net::IpAddr;
use time::{Date, OffsetDateTime};
Expand Down Expand Up @@ -97,3 +98,28 @@ pub struct ImportPageOutput {
pub site_id: i64,
pub page_id: i64,
}

#[derive(Deserialize, Debug)]
pub struct ImportPageRevision {
pub revision_id: i64,
pub revision_type: PageRevisionType,
pub created_at: OffsetDateTime,
pub updated_at: Option<OffsetDateTime>,
pub revision_number: i32,
pub page_id: i64,
pub site_id: i64,
pub user_id: i64,
pub wikitext: String,
pub comments: String,
pub title: String,
pub slug: String,
pub tags: Vec<String>,
}

#[derive(Serialize, Debug, Copy, Clone)]
pub struct ImportPageRevisionOutput {
pub site_id: i64,
pub page_id: i64,
pub page_revision_id: i64,
pub page_revision_number: i32,
}
Loading