Skip to content

Commit

Permalink
Merge pull request #352 from KisaragiEffective/fix/visibility
Browse files Browse the repository at this point in the history
  • Loading branch information
KisaragiEffective committed Mar 26, 2024
2 parents 0ad78db + 1d28b43 commit 239348d
Show file tree
Hide file tree
Showing 6 changed files with 70 additions and 26 deletions.
10 changes: 7 additions & 3 deletions packages/toy-blog-endpoint-model/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,7 @@ pub struct Article {
pub created_at: DateTime<Local>,
pub updated_at: DateTime<Local>,
pub content: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub visibility: Option<Visibility>
pub visibility: Visibility
}

#[derive(Deserialize, Serialize, Copy, Clone, Eq, PartialEq, Debug)]
Expand Down Expand Up @@ -241,4 +240,9 @@ impl<'de> Deserialize<'de> for OneOriginTwoDigitsMonth {

Ok(x)
}
}
}

#[derive(Deserialize)]
pub struct UpdateVisibilityPayload {
pub visibility: Visibility,
}
13 changes: 12 additions & 1 deletion packages/toy-blog/src/migration.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! data migration

use serde_json::{Map, Value};
use toy_blog_endpoint_model::Article;

trait SerdeJsonValueMoveExtension {
///
Expand Down Expand Up @@ -45,6 +46,16 @@ impl ArticleMigration for AddTagVersion {
}
}

macro_rules! name_of {
($t:tt::$field:ident) => {
{
// #[allow(unused)]
let _ = |arg: &$t| arg.$field;
stringify!($field)
}
};
}

struct AddAccessLevel;

impl ArticleMigration for AddAccessLevel {
Expand All @@ -63,7 +74,7 @@ impl ArticleMigration for AddAccessLevel {
.as_object_mut().expect("article table must be object");

article_table.values_mut().for_each(|article| {
article.as_object_mut().expect("article").insert("access".to_string(), Value::from("public"));
article.as_object_mut().expect("article").insert(name_of!(Article::visibility).to_string(), Value::from("public"));
});

Value::from(top)
Expand Down
6 changes: 3 additions & 3 deletions packages/toy-blog/src/service/persistence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl ArticleRepository {
fn create_default_file_if_absent(path: impl AsRef<Path>) {
if !path.as_ref().exists() {
info!("creating article table");
let mut file = File::options().write(true).read(true).create(true).open(path.as_ref()).unwrap();
let mut file = File::options().write(true).read(true).create(true).truncate(false).open(path.as_ref()).unwrap();
write!(
&mut (file),
"{default_json}",
Expand Down Expand Up @@ -81,7 +81,7 @@ impl ArticleRepository {
updated_at: current_date,
// visible: false,
content: article_content,
visibility: Some(visibility),
visibility,
});


Expand Down Expand Up @@ -123,7 +123,7 @@ impl ArticleRepository {
self.invalidate();

self.cache.write().expect("poisoned").deref_mut().data.get_mut(article_id)
.ok_or(PersistenceError::AbsentValue)?.visibility = Some(new_visibility);
.ok_or(PersistenceError::AbsentValue)?.visibility = new_visibility;

self.save()?;

Expand Down
1 change: 1 addition & 0 deletions packages/toy-blog/src/service/rest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ pub async fn boot_http_server(port: u16, host: &str, proxied_by_cloudflare: bool
article::fetch,
article::update,
article::remove,
article::update_visibility,
)
),
prefixed_service("/meta")
Expand Down
46 changes: 40 additions & 6 deletions packages/toy-blog/src/service/rest/api/article.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ use actix_web::{delete, get, post, put};

use actix_web::http::header::USER_AGENT;
use actix_web::http::StatusCode;
use actix_web::web::{Bytes, Path};
use actix_web::web::{Bytes, Json, Path};
use actix_web_httpauth::extractors::bearer::BearerAuth;
use log::{error, info};
use once_cell::unsync::Lazy;
use toy_blog_endpoint_model::{ArticleContent, ArticleCreatedNotice, ArticleCreateWarning, ArticleId, ArticleSnapshot, ArticleSnapshotMetadata, CreateArticleError, DeleteArticleError, GetArticleError, OwnedMetadata, UpdateArticleError, Visibility};
use toy_blog_endpoint_model::{ArticleContent, ArticleCreatedNotice, ArticleCreateWarning, ArticleId, ArticleSnapshot, ArticleSnapshotMetadata, CreateArticleError, DeleteArticleError, GetArticleError, OwnedMetadata, UpdateArticleError, UpdateVisibilityPayload, Visibility};
use crate::service::rest::auth::is_wrong_token;
use crate::service::rest::inner_no_leak::{UnhandledError};
use crate::service::rest::repository::GLOBAL_FILE;
Expand Down Expand Up @@ -68,7 +68,7 @@ pub async fn create(path: Path<String>, data: Bytes, bearer: BearerAuth, request
}

#[get("/{article_id}")]
pub async fn fetch(path: Path<String>) -> impl Responder {
pub async fn fetch(path: Path<String>, auth: Option<BearerAuth>) -> impl Responder {

enum Res {
Internal(UnhandledError),
Expand All @@ -93,11 +93,12 @@ pub async fn fetch(path: Path<String>) -> impl Responder {
Err(e) => return Res::Internal(UnhandledError::new(e))
};

match content.visibility {
Some(x) if x != Visibility::Public => {
// Visibility::Restricted, Visibility::Publicは検証不要
if let (Visibility::Private, Some(auth)) = (content.visibility, auth) {
if is_wrong_token(auth.token()) {
return Res::General(GetArticleError::NoSuchArticleFoundById)
}
_ => {}
// now, private article can see from permitted user!
}

let u = content.updated_at;
Expand Down Expand Up @@ -197,3 +198,36 @@ pub async fn remove(path: Path<String>, bearer: BearerAuth) -> impl Responder {

EndpointRepresentationCompiler::from_value(res().await).into_plain_text()
}

#[put("/{article_id}/visibility")]
pub async fn update_visibility(path: Path<String>, payload: Json<UpdateVisibilityPayload>, bearer: BearerAuth) -> impl Responder {
let res = || async {
let article_id = ArticleId::new(path.into_inner());
let token = bearer.token();

if is_wrong_token(token) {
return Ok(Err(DeleteArticleError::InvalidBearerToken))
}

let exists = match x_get().exists(&article_id).await {
Ok(exists) => exists,
Err(err) => return Err(UnhandledError::new(err))
};

if !exists {
return Ok(Err(DeleteArticleError::NoSuchArticleFoundById))
}

let new_visibility = payload.visibility;
match x_get().change_visibility(&article_id, new_visibility).await {
Ok(_) => {
Ok(Ok(()))
}
Err(err) => {
Err(UnhandledError::new(err))
}
}
};

EndpointRepresentationCompiler::from_value(res().await).into_plain_text()
}
20 changes: 7 additions & 13 deletions packages/toy-blog/src/service/rest/api/list.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
use std::collections::HashSet;
use actix_web::{get, Responder};
use actix_web::web::Path;
use chrono::Datelike;
use crate::service::rest::exposed_representation_format::{ArticleIdCollectionResponseRepr, EndpointRepresentationCompiler, InternalErrorExposedRepr};

use toy_blog_endpoint_model::{AnnoDominiYear, Article, ArticleIdSet, ArticleIdSetMetadata, OneOriginTwoDigitsMonth, OwnedMetadata, Visibility};

use crate::service::rest::exposed_representation_format::{ArticleIdCollectionResponseRepr, EndpointRepresentationCompiler};
use crate::service::rest::repository::GLOBAL_FILE;
use toy_blog_endpoint_model::{
ArticleId, ArticleIdSet,
OwnedMetadata,
ArticleIdSetMetadata,
AnnoDominiYear,
OneOriginTwoDigitsMonth,
Article,
};

#[get("/article")]
#[allow(clippy::unused_async)]
pub async fn article_id_list() -> impl Responder {
// TODO: DBに切り替えたら、ヘッダーを受け取るようにし、DB上における最大値と最小値を確認して条件次第で304を返すようにする
let x = GLOBAL_FILE.get().expect("must be fully-initialized").entries();

let id = ArticleIdSet(x.iter().map(|x| &x.0).cloned().collect());
let id = ArticleIdSet(x.iter().filter(|x| x.1.visibility == Visibility::Public).map(|x| &x.0).cloned().collect());
let old_cre = x.iter().min_by_key(|x| x.1.created_at).map(|x| x.1.created_at);
let new_upd = x.iter().max_by_key(|x| x.1.updated_at).map(|x| x.1.updated_at);

Expand All @@ -41,7 +35,7 @@ pub async fn article_id_list() -> impl Responder {
pub async fn article_id_list_by_year(path: Path<AnnoDominiYear>) -> impl Responder {
let year = path.into_inner();
// TODO: DBに切り替えたら、ヘッダーを受け取るようにし、DB上における最大値と最小値を確認して条件次第で304を返すようにする
let f = |a: &Article| a.created_at.year() == year.into_inner() as i32;
let f = |a: &Article| a.created_at.year() == year.into_inner() as i32 && a.visibility == Visibility::Public;
let x = GLOBAL_FILE.get().expect("must be fully-initialized").entries();

let (matched_article_id_set, oldest_creation, most_recent_update) = (
Expand Down Expand Up @@ -80,7 +74,7 @@ pub async fn article_id_list_by_year_and_month(
};
let filter_month = article_created_month == month.into_inner();

filter_year && filter_month
filter_year && filter_month && a.visibility == Visibility::Public
};
// TODO: DBに切り替えたら、ヘッダーを受け取るようにし、DB上における最大値と最小値を確認して条件次第で304を返すようにする
let x = GLOBAL_FILE.get().expect("must be fully-initialized").entries();
Expand Down

0 comments on commit 239348d

Please sign in to comment.