Skip to content

Commit

Permalink
Merge pull request #1641 from scpwiki/WJ-1193-interactions
Browse files Browse the repository at this point in the history
[WJ-1193] Implement interactions
  • Loading branch information
emmiegit authored Oct 6, 2023
2 parents 9c7b580 + 8ae049c commit c2f852c
Show file tree
Hide file tree
Showing 34 changed files with 1,203 additions and 356 deletions.
1 change: 1 addition & 0 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 @@ -38,6 +38,7 @@ intl-memoizer = "0.5"
notify = { version = "6", optional = true }
once_cell = "1"
otp = { git = "https://github.com/TimDumol/rust-otp" }
paste = "1"
rand = "0.8"
ref-map = "0.1"
regex = "1"
Expand Down
60 changes: 42 additions & 18 deletions deepwell/migrations/20220906103252_deepwell.sql
Original file line number Diff line number Diff line change
Expand Up @@ -101,20 +101,6 @@ ALTER TABLE site
ADD CONSTRAINT site_custom_domain_fk
FOREIGN KEY (custom_domain) REFERENCES site_domain(domain);

--
-- Site Membership
--

CREATE TABLE site_member (
membership_id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES "user"(user_id),
site_id BIGINT NOT NULL REFERENCES site(site_id),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
deleted_at TIMESTAMP WITH TIME ZONE,

UNIQUE (user_id, site_id, deleted_at)
);

--
-- Aliases
--
Expand All @@ -135,6 +121,44 @@ CREATE TABLE alias (
UNIQUE (alias_type, slug)
);

--
-- Interactions
--

-- See also https://github.com/scpwiki/wikijump/blob/legacy-php/web/database/migrations/2021_07_30_231009_create_interactions_table.php
-- and https://github.com/scpwiki/wikijump/blob/legacy-php/web/app/Models/Interaction.php

CREATE TYPE interaction_object_type AS ENUM (
'site',
'user',
'page',
'file'
);

CREATE TABLE interaction (
interaction_id BIGSERIAL PRIMARY KEY,
interaction_type TEXT NOT NULL, -- check enum value in runtime
dest_type interaction_object_type NOT NULL,
dest_id BIGINT NOT NULL,
from_type interaction_object_type NOT NULL,
from_id BIGINT NOT NULL,
metadata JSON NOT NULL DEFAULT '{}',
created_by BIGINT NOT NULL REFERENCES "user"(user_id),
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
overwritten_by BIGINT REFERENCES "user"(user_id),
overwritten_at TIMESTAMP WITH TIME ZONE,
deleted_by BIGINT REFERENCES "user"(user_id),
deleted_at TIMESTAMP WITH TIME ZONE,

UNIQUE (interaction_type, dest_type, dest_id, from_type, from_id, overwritten_at, deleted_at),
CHECK ((overwritten_by IS NULL) = (overwritten_at IS NULL)), -- ensure overwritten field consistency
CHECK ((deleted_by IS NULL) = (deleted_at IS NULL)), -- ensure deleted field consistency
CHECK (
((overwritten_by IS NULL) AND (deleted_at IS NULL)) OR -- entries are active
((overwritten_by IS NULL) != (deleted_at IS NULL)) -- or they are overwritten XOR deleted
)
);

--
-- Session
--
Expand Down Expand Up @@ -331,7 +355,7 @@ CREATE TYPE page_connection_type AS ENUM (
CREATE TABLE page_link (
page_id BIGINT REFERENCES page(page_id),
url TEXT,
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE,
count INT NOT NULL CHECK (count > 0),

Expand All @@ -342,7 +366,7 @@ CREATE TABLE page_connection (
from_page_id BIGINT REFERENCES page(page_id),
to_page_id BIGINT REFERENCES page(page_id),
connection_type TEXT, -- Cannot use page_connection_type right now because Sea-ORM issues
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE,
count INT NOT NULL CHECK (count > 0),

Expand All @@ -354,7 +378,7 @@ CREATE TABLE page_connection_missing (
to_site_id BIGINT REFERENCES page(page_id),
to_page_slug TEXT,
connection_type TEXT, -- Ditto
created_at TIMESTAMP WITH TIME ZONE NOT NULL,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE,
count INT NOT NULL CHECK (count > 0),

Expand Down Expand Up @@ -480,7 +504,7 @@ CREATE TABLE file_revision (
-- If a filter has all the "affects_*" columns false, then it is effectively disabled.
CREATE TABLE filter (
filter_id BIGSERIAL PRIMARY KEY,
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT 'now()',
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
updated_at TIMESTAMP WITH TIME ZONE,
deleted_at TIMESTAMP WITH TIME ZONE,
site_id BIGINT REFERENCES site(site_id),
Expand Down
4 changes: 0 additions & 4 deletions deepwell/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,10 +172,6 @@ fn build_routes(mut app: ApiServer) -> ApiServer {
.put(membership_put)
.delete(membership_delete);
app.at("/site/member/get").put(membership_retrieve);
app.at("/site/member/list/get")
.put(membership_site_retrieve);
app.at("/user/sites/get").put(membership_user_retrieve); // More appropriate to put here,
// as part of membership endpoints.

// Category
app.at("/category").get(category_get);
Expand Down
1 change: 1 addition & 0 deletions deepwell/src/database/seeder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ pub async fn seed(state: &ApiServerState) -> Result<()> {
*/

txn.commit().await?;
tide::log::info!("Finished running seeder.");
Ok(())
}

Expand Down
19 changes: 15 additions & 4 deletions deepwell/src/endpoints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,27 @@ mod prelude {
pub use crate::api::{ApiRequest, ApiResponse};
pub use crate::services::{
AliasService, BlobService, CategoryService, DomainService, Error as ServiceError,
FileRevisionService, FileService, LinkService, MfaService, PageRevisionService,
PageService, ParentService, RenderService, RequestFetchService, ScoreService,
ServiceContext, SessionService, SiteMemberService, SiteService, TextService,
UserService, ViewService, VoteService,
FileRevisionService, FileService, InteractionService, LinkService, MfaService,
PageRevisionService, PageService, ParentService, RenderService,
RequestFetchService, ScoreService, ServiceContext, SessionService, SiteService,
TextService, UserService, ViewService, VoteService,
};
pub use crate::utils::error_response;
pub use crate::web::HttpUnwrap;
pub use sea_orm::{ConnectionTrait, TransactionTrait};
pub use std::convert::TryFrom;
pub use tide::{Body, Error as TideError, Request, Response, StatusCode};

use serde::Serialize;

pub fn build_json_response<T: Serialize>(
data: &T,
status: StatusCode,
) -> ApiResponse {
let body = Body::from_json(data)?;
let response = Response::builder(status).body(body).into();
Ok(response)
}
}

pub mod auth;
Expand Down
56 changes: 12 additions & 44 deletions deepwell/src/endpoints/site_member.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,69 +19,37 @@
*/

use super::prelude::*;
use crate::services::site_member::SiteMembership;
use serde::Serialize;
use crate::services::interaction::{CreateSiteMember, GetSiteMember, RemoveSiteMember};

pub async fn membership_retrieve(mut req: ApiRequest) -> ApiResponse {
let txn = req.database().begin().await?;
let ctx = ServiceContext::new(&req, &txn);

let input: SiteMembership = req.body_json().await?;
let output = SiteMemberService::get(&ctx, input).await?;
txn.commit().await?;
let input: GetSiteMember = req.body_json().await?;
let output = InteractionService::get_site_member(&ctx, input).await?;

build_membership_response(&output, StatusCode::Ok)
txn.commit().await?;
build_json_response(&output, StatusCode::Ok)
}

pub async fn membership_put(mut req: ApiRequest) -> ApiResponse {
let txn = req.database().begin().await?;
let ctx = ServiceContext::new(&req, &txn);

let input: SiteMembership = req.body_json().await?;
let created = SiteMemberService::add(&ctx, input).await?;
txn.commit().await?;

match created {
Some(model) => build_membership_response(&model, StatusCode::Created),
None => Ok(Response::new(StatusCode::NoContent)),
}
}

pub async fn membership_delete(mut req: ApiRequest) -> ApiResponse {
let txn = req.database().begin().await?;
let ctx = ServiceContext::new(&req, &txn);
let input: CreateSiteMember = req.body_json().await?;
InteractionService::create_site_member(&ctx, input).await?;

let input: SiteMembership = req.body_json().await?;
let output = SiteMemberService::remove(&ctx, input).await?;
txn.commit().await?;

build_membership_response(&output, StatusCode::Ok)
Ok(Response::new(StatusCode::Created))
}

pub async fn membership_site_retrieve(mut req: ApiRequest) -> ApiResponse {
pub async fn membership_delete(mut req: ApiRequest) -> ApiResponse {
let txn = req.database().begin().await?;
let ctx = ServiceContext::new(&req, &txn);

let input: i64 = req.body_json().await?;
let output = SiteMemberService::get_site_members(&ctx, input).await?;
txn.commit().await?;

build_membership_response(&output, StatusCode::Ok)
}

pub async fn membership_user_retrieve(mut req: ApiRequest) -> ApiResponse {
let txn = req.database().begin().await?;
let ctx = ServiceContext::new(&req, &txn);
let input: RemoveSiteMember = req.body_json().await?;
let output = InteractionService::remove_site_member(&ctx, input).await?;

let input: i64 = req.body_json().await?;
let output = SiteMemberService::get_user_sites(&ctx, input).await?;
txn.commit().await?;

build_membership_response(&output, StatusCode::Ok)
}

fn build_membership_response<T: Serialize>(data: &T, status: StatusCode) -> ApiResponse {
let body = Body::from_json(data)?;
let response = Response::builder(status).body(body).into();
Ok(response)
build_json_response(&output, StatusCode::Ok)
}
56 changes: 56 additions & 0 deletions deepwell/src/models/interaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
use super::sea_orm_active_enums::InteractionObjectType;
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
#[sea_orm(table_name = "interaction")]
#[serde(rename_all = "camelCase")]
pub struct Model {
#[sea_orm(primary_key)]
pub interaction_id: i64,
#[sea_orm(column_type = "Text")]
pub interaction_type: String,
pub dest_type: InteractionObjectType,
pub dest_id: i64,
pub from_type: InteractionObjectType,
pub from_id: i64,
pub metadata: Json,
pub created_by: i64,
pub created_at: TimeDateTimeWithTimeZone,
pub overwritten_by: Option<i64>,
pub overwritten_at: Option<TimeDateTimeWithTimeZone>,
pub deleted_by: Option<i64>,
pub deleted_at: Option<TimeDateTimeWithTimeZone>,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::CreatedBy",
to = "super::user::Column::UserId",
on_update = "NoAction",
on_delete = "NoAction"
)]
User3,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::DeletedBy",
to = "super::user::Column::UserId",
on_update = "NoAction",
on_delete = "NoAction"
)]
User2,
#[sea_orm(
belongs_to = "super::user::Entity",
from = "Column::OverwrittenBy",
to = "super::user::Column::UserId",
on_update = "NoAction",
on_delete = "NoAction"
)]
User1,
}

impl ActiveModelBehavior for ActiveModel {}
2 changes: 1 addition & 1 deletion deepwell/src/models/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod alias;
pub mod file;
pub mod file_revision;
pub mod filter;
pub mod interaction;
pub mod page;
pub mod page_attribution;
pub mod page_category;
Expand All @@ -20,7 +21,6 @@ pub mod sea_orm_active_enums;
pub mod session;
pub mod site;
pub mod site_domain;
pub mod site_member;
pub mod text;
pub mod user;
pub mod user_bot_owner;
2 changes: 1 addition & 1 deletion deepwell/src/models/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub use super::alias::Entity as Alias;
pub use super::file::Entity as File;
pub use super::file_revision::Entity as FileRevision;
pub use super::filter::Entity as Filter;
pub use super::interaction::Entity as Interaction;
pub use super::page::Entity as Page;
pub use super::page_attribution::Entity as PageAttribution;
pub use super::page_category::Entity as PageCategory;
Expand All @@ -17,7 +18,6 @@ pub use super::page_vote::Entity as PageVote;
pub use super::session::Entity as Session;
pub use super::site::Entity as Site;
pub use super::site_domain::Entity as SiteDomain;
pub use super::site_member::Entity as SiteMember;
pub use super::text::Entity as Text;
pub use super::user::Entity as User;
pub use super::user_bot_owner::Entity as UserBotOwner;
19 changes: 19 additions & 0 deletions deepwell/src/models/sea_orm_active_enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,26 @@ pub enum FileRevisionType {
Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy, Serialize, Deserialize,
)]
#[serde(rename_all = "kebab-case")]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
enum_name = "interaction_object_type"
)]
pub enum InteractionObjectType {
#[sea_orm(string_value = "file")]
File,
#[sea_orm(string_value = "page")]
Page,
#[sea_orm(string_value = "site")]
Site,
#[sea_orm(string_value = "user")]
User,
}
#[derive(
Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Copy, Serialize, Deserialize,
)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "page_revision_type")]
#[serde(rename_all = "kebab-case")]
pub enum PageRevisionType {
#[sea_orm(string_value = "create")]
Create,
Expand Down
8 changes: 0 additions & 8 deletions deepwell/src/models/site.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,6 @@ pub enum Relation {
on_delete = "NoAction"
)]
SiteDomain,
#[sea_orm(has_many = "super::site_member::Entity")]
SiteMember,
}

impl Related<super::filter::Entity> for Entity {
Expand Down Expand Up @@ -81,10 +79,4 @@ impl Related<super::site_domain::Entity> for Entity {
}
}

impl Related<super::site_member::Entity> for Entity {
fn to() -> RelationDef {
Relation::SiteMember.def()
}
}

impl ActiveModelBehavior for ActiveModel {}
Loading

0 comments on commit c2f852c

Please sign in to comment.