Pour tout problème contactez-nous par mail : support@froggit.fr | La FAQ :grey_question: | Rejoignez-nous sur le Chat :speech_balloon:

Skip to content
Snippets Groups Projects
Commit f3ba7f03 authored by Pierre Jarriges's avatar Pierre Jarriges
Browse files

create update delete / todo: tests- autodelete

parent 466d07b1
No related branches found
No related tags found
1 merge request!1Dev
...@@ -342,9 +342,9 @@ dependencies = [ ...@@ -342,9 +342,9 @@ dependencies = [
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "0.7.15" version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
...@@ -1280,6 +1280,7 @@ dependencies = [ ...@@ -1280,6 +1280,7 @@ dependencies = [
"futures", "futures",
"magic-crypt", "magic-crypt",
"rand 0.8.4", "rand 0.8.4",
"regex",
"rustls 0.18.1", "rustls 0.18.1",
"serde", "serde",
"serde_json", "serde_json",
...@@ -1381,9 +1382,9 @@ dependencies = [ ...@@ -1381,9 +1382,9 @@ dependencies = [
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.3.4" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "mime" name = "mime"
...@@ -1872,9 +1873,9 @@ dependencies = [ ...@@ -1872,9 +1873,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.4.6" version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
...@@ -1883,9 +1884,9 @@ dependencies = [ ...@@ -1883,9 +1884,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.25" version = "0.6.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
......
...@@ -22,4 +22,5 @@ chrono = "0.4" ...@@ -22,4 +22,5 @@ chrono = "0.4"
rand = "0.8" rand = "0.8"
dotenv = "0.15" dotenv = "0.15"
time = "0.2.7" time = "0.2.7"
regex = "1.5"
tokio = { version = "0.2", features = ["full"] } tokio = { version = "0.2", features = ["full"] }
...@@ -13,6 +13,9 @@ class Article { ...@@ -13,6 +13,9 @@ class Article {
this.body = ""; this.body = "";
this.locale = ""; this.locale = "";
this.display_priority_index = 1; this.display_priority_index = 1;
this.metadata = {
description: "",
};
} }
} }
......
...@@ -19,6 +19,13 @@ class CreateArticleForm { ...@@ -19,6 +19,13 @@ class CreateArticleForm {
this.refresh(); this.refresh();
} }
handle_change_metadata(field, e) {
const metadata = Object.assign(this.state.output.metadata, {
[field]: e.target.value,
});
this.state.output.metadata = metadata;
}
handle_text_input(field, e) { handle_text_input(field, e) {
this.state.output[field] = e.target.value; this.state.output[field] = e.target.value;
} }
...@@ -347,6 +354,12 @@ class CreateArticleForm { ...@@ -347,6 +354,12 @@ class CreateArticleForm {
placeholder: "Article body", placeholder: "Article body",
oninput: this.handle_text_input.bind(this, "body") oninput: this.handle_text_input.bind(this, "body")
}, },
{
tag: "input", type: "text",
value: this.state.output.metadata.description,
placeholder: "description",
oninput: this.handle_change_metadata.bind(this, "description"),
},
this.render_details_inputs(), this.render_details_inputs(),
this.render_images_inputs(), this.render_images_inputs(),
{ tag: "input", type: "submit" } { tag: "input", type: "submit" }
......
...@@ -8,6 +8,6 @@ db.auth(adminname, adminpwd); ...@@ -8,6 +8,6 @@ db.auth(adminname, adminpwd);
articles = db.getCollection("articles"); articles = db.getCollection("articles");
articles.update({}, articles.update({},
{ $set: { "display_priority_index": NumberInt(1) } }, { $set: { "metadata": { "description": "" } } },
{ upsert: false, multi: true } { upsert: false, multi: true }
); );
\ No newline at end of file
...@@ -4,20 +4,22 @@ ...@@ -4,20 +4,22 @@
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>Kuadrado Software | Jeux</title> <title>Kuadrado Software | Jeux</title>
<meta name="description" content="Création de jeux vidéos indépendants. Jeux web, PC et projets en cours de développement"/> <meta name="description"
content="Création de jeux vidéos indépendants. Jeux web, PC et projets en cours de développement" />
<meta name="author" content="Kuadrado Software" /> <meta name="author" content="Kuadrado Software" />
<meta name="image" content="https://kuadrado-software.fr/assets/images/game_controller.png"/> <meta name="image" content="https://kuadrado-software.fr/assets/images/game_controller.png" />
<!-- Open Graph Protocol meta data --> <!-- Open Graph Protocol meta data -->
<meta property="og:title" content="Kuadrado Software | Jeux"/> <meta property="og:title" content="Kuadrado Software | Jeux" />
<meta property="og:description" content="Création de jeux vidéos indépendants. Jeux web, PC et projets en cours de développement"/> <meta property="og:description"
content="Création de jeux vidéos indépendants. Jeux web, PC et projets en cours de développement" />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://kuadrado-software.fr/games"/> <meta property="og:url" content="https://kuadrado-software.fr/games" />
<meta property="og:image" content="https://kuadrado-software.fr/assets/images/game_controller.png"/> <meta property="og:image" content="https://kuadrado-software.fr/assets/images/game_controller.png" />
<meta property="og:image" content="https://kuadrado-software.fr/assets/images/game_studio_banner.png"/> <meta property="og:image" content="https://kuadrado-software.fr/assets/images/game_studio_banner.png" />
<meta property="og:image" content="https://kuadrado-software.fr/assets/images/popularization_banner.png"/> <meta property="og:image" content="https://kuadrado-software.fr/assets/images/popularization_banner.png" />
<meta property="twitter:image" content="https://kuadrado-software.fr/assets/images/game_controller.png"/> <meta property="twitter:image" content="https://kuadrado-software.fr/assets/images/game_controller.png" />
<meta property="og:locale" content="fr_FR"/> <meta property="og:locale" content="fr_FR" />
<meta property="og:site_name" content="Kuadrado Software" /> <meta property="og:site_name" content="Kuadrado Software" />
<!-- English translation not ready yet --> <!-- English translation not ready yet -->
...@@ -26,7 +28,8 @@ ...@@ -26,7 +28,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link href="/style/style.css" rel="stylesheet" /> <link href="/style/style.css" rel="stylesheet" />
<script type="application/ld+json">{"@context":"https://schema.org","type":"WebPage","description":"Création de jeux vidéos indépendants. Jeux web, PC et projets en cours de développement","image":["https://kuadrado-software.fr/assets/images/game_controller.svg","https://kuadrado-software.fr/assets/images/game_controller.png"],"keywords":"gamedev, pixelart, jeux vidéo, création, video games, indépendants, indie gamedev","name":"Kuadrado Software - Jeux","url":"https://kuadrado-software.fr/games"}</script> <script
type="application/ld+json">{"@context":"https://schema.org","type":"WebPage","description":"Création de jeux vidéos indépendants. Jeux web, PC et projets en cours de développement","image":["https://kuadrado-software.fr/assets/images/game_controller.svg","https://kuadrado-software.fr/assets/images/game_controller.png"],"keywords":"gamedev, pixelart, jeux vidéo, création, video games, indépendants, indie gamedev","name":"Kuadrado Software - Jeux","url":"https://kuadrado-software.fr/games"}</script>
</head> </head>
<!-- The vocab attribute defines the standard vocabulary used for RDFa standard. <!-- The vocab attribute defines the standard vocabulary used for RDFa standard.
The DOM may contain properties such as "typeof" and "property" accordinly to the schema.org vocabulary --> The DOM may contain properties such as "typeof" and "property" accordinly to the schema.org vocabulary -->
......
...@@ -9,6 +9,7 @@ pub struct Env { ...@@ -9,6 +9,7 @@ pub struct Env {
pub db_name: String, pub db_name: String,
pub db_port: String, pub db_port: String,
pub server_host: String, pub server_host: String,
pub server_protocol: String,
pub crypt_key: String, pub crypt_key: String,
pub default_admin_username: String, pub default_admin_username: String,
pub default_admin_password: String, pub default_admin_password: String,
...@@ -50,6 +51,7 @@ impl Env { ...@@ -50,6 +51,7 @@ impl Env {
db_name: env::var("DATABASE_NAME").expect("DATABASE_NAME is not defined."), db_name: env::var("DATABASE_NAME").expect("DATABASE_NAME is not defined."),
db_port: env::var("DB_PORT").expect("DB_PORT is not defined."), db_port: env::var("DB_PORT").expect("DB_PORT is not defined."),
server_host: env::var("SERVER_HOST").expect("SERVER_HOST is not defined"), server_host: env::var("SERVER_HOST").expect("SERVER_HOST is not defined"),
server_protocol: env::var("SERVER_PROTOCOL").expect("SERVER_PROTOCOL is not defined"),
crypt_key: env::var("CRYPT_KEY").expect("CRYPT_KEY is not defined."), crypt_key: env::var("CRYPT_KEY").expect("CRYPT_KEY is not defined."),
default_admin_username: env::var("DEFAULT_ADMIN_USERNAME") default_admin_username: env::var("DEFAULT_ADMIN_USERNAME")
.expect("DEFAULT_ADMIN_USERNAME is not defined"), .expect("DEFAULT_ADMIN_USERNAME is not defined"),
...@@ -72,6 +74,7 @@ impl Env { ...@@ -72,6 +74,7 @@ impl Env {
db_name: env::var("DATABASE_NAME").expect("DATABASE_NAME is not defined."), db_name: env::var("DATABASE_NAME").expect("DATABASE_NAME is not defined."),
db_port: env::var("DB_PORT").expect("DB_PORT is not defined."), db_port: env::var("DB_PORT").expect("DB_PORT is not defined."),
server_host: env::var("SERVER_HOST").expect("SERVER_HOST is not defined"), server_host: env::var("SERVER_HOST").expect("SERVER_HOST is not defined"),
server_protocol: env::var("SERVER_PROTOCOL").expect("SERVER_PROTOCOL is not defined"),
crypt_key: env::var("CRYPT_KEY").expect("CRYPT_KEY is not defined."), crypt_key: env::var("CRYPT_KEY").expect("CRYPT_KEY is not defined."),
default_admin_username: env::var("DEFAULT_ADMIN_USERNAME") default_admin_username: env::var("DEFAULT_ADMIN_USERNAME")
.expect("DEFAULT_ADMIN_USERNAME is not defined"), .expect("DEFAULT_ADMIN_USERNAME is not defined"),
......
#[cfg(test)] #[cfg(test)]
use chrono::Utc; use chrono::Utc;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use wither::{ use wither::{
bson::{doc, oid::ObjectId, DateTime}, bson::{doc, oid::ObjectId, DateTime},
prelude::Model, prelude::Model,
...@@ -12,6 +13,14 @@ pub struct ArticleDetail { ...@@ -12,6 +13,14 @@ pub struct ArticleDetail {
pub value: String, pub value: String,
} }
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ArticleMetadata {
pub description: String,
pub view_uri: Option<String>,
pub static_resource_path: Option<PathBuf>,
pub slug: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Model, Clone)] #[derive(Debug, Serialize, Deserialize, Model, Clone)]
#[model(index(keys = r#"doc!{"title": 1}"#, options = r#"doc!{"unique": true}"#))] #[model(index(keys = r#"doc!{"title": 1}"#, options = r#"doc!{"unique": true}"#))]
pub struct Article { pub struct Article {
...@@ -26,6 +35,7 @@ pub struct Article { ...@@ -26,6 +35,7 @@ pub struct Article {
pub category: String, pub category: String,
pub locale: String, pub locale: String,
pub display_priority_index: i8, pub display_priority_index: i8,
pub metadata: ArticleMetadata,
} }
impl Article { impl Article {
...@@ -51,6 +61,12 @@ impl Article { ...@@ -51,6 +61,12 @@ impl Article {
category: "testing".to_string(), category: "testing".to_string(),
locale: "fr".to_string(), locale: "fr".to_string(),
display_priority_index: 1, display_priority_index: 1,
metadata: ArticleMetadata {
description: "A test article".to_string(),
view_uri: None,
static_resource_path: None,
slug: None,
},
} }
} }
} }
use crate::{ use crate::{
middleware::AuthenticatedAdminMiddleware, model::Article, static_view::create_static_view, middleware::AuthenticatedAdminMiddleware,
model::Article,
static_view::{create_static_view, delete_static_view},
AppState, AppState,
}; };
use actix_web::{ use actix_web::{
...@@ -9,6 +11,7 @@ use actix_web::{ ...@@ -9,6 +11,7 @@ use actix_web::{
}; };
use chrono::Utc; use chrono::Utc;
use futures::stream::StreamExt; use futures::stream::StreamExt;
use regex::Regex;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use wither::{ use wither::{
bson::{doc, oid::ObjectId, Bson, DateTime}, bson::{doc, oid::ObjectId, Bson, DateTime},
...@@ -25,6 +28,37 @@ fn get_collection(app_state: &AppState) -> Collection<Article> { ...@@ -25,6 +28,37 @@ fn get_collection(app_state: &AppState) -> Collection<Article> {
app_state.db.collection_with_type::<Article>("articles") app_state.db.collection_with_type::<Article>("articles")
} }
fn slugify(s: &String) -> String {
let s = s.to_lowercase();
// Delete every character that is not a letter a number or a space
let re = Regex::new(r"[^a-z\d\s]").unwrap();
let slug = re.replace_all(&s, "");
let slug = slug.trim().to_string();
// Deduplicate whitespaces and replace them by hyhens
let re = Regex::new(r"[\s]+").unwrap();
let slug = re.replace_all(&slug, "-");
slug.to_string()
}
fn create_article_static_view_metadata(app_state: &AppState, art: &mut Article) {
let article_slug = slugify(&art.title);
art.metadata.slug = Some(article_slug.to_owned());
art.metadata.view_uri = Some(format!(
"{}://{}/{}/{}/",
&app_state.env.server_protocol, &app_state.env.server_host, &art.category, &article_slug,
));
art.metadata.static_resource_path = Some(
app_state
.env
.public_dir
.join(&art.category)
.join("view")
.join(&article_slug),
);
}
async fn generate_article_view(app_state: &AppState, id: &ObjectId) -> Result<(), String> { async fn generate_article_view(app_state: &AppState, id: &ObjectId) -> Result<(), String> {
match Article::find_one(&app_state.db, doc! {"_id":&id}, None).await { match Article::find_one(&app_state.db, doc! {"_id":&id}, None).await {
Ok(art) => { Ok(art) => {
...@@ -33,6 +67,20 @@ async fn generate_article_view(app_state: &AppState, id: &ObjectId) -> Result<() ...@@ -33,6 +67,20 @@ async fn generate_article_view(app_state: &AppState, id: &ObjectId) -> Result<()
} }
let art = art.unwrap(); let art = art.unwrap();
if art.metadata.view_uri.is_none() {
return Err(format!("Article {} doesn't have view uri", art.title));
}
let art_img_def = String::new();
let mut art_image_uri = art.images.iter().next().unwrap_or(&art_img_def).to_owned();
if !art_image_uri.is_empty() {
art_image_uri = format!("/assets/images/{}", art_image_uri);
}
let slug = art.metadata.slug.unwrap();
let html = format!( let html = format!(
" "
<html lang='{}'> <html lang='{}'>
...@@ -40,20 +88,30 @@ async fn generate_article_view(app_state: &AppState, id: &ObjectId) -> Result<() ...@@ -40,20 +88,30 @@ async fn generate_article_view(app_state: &AppState, id: &ObjectId) -> Result<()
<meta charset='UTF-8'> <meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'> <meta http-equiv='X-UA-Compatible' content='IE=edge'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'> <meta name='viewport' content='width=device-width, initial-scale=1.0'>
<meta name='author' content='Kuadrado Software' />
<meta name='image' content='{}'/>
<meta name='description' content='{}'> <meta name='description' content='{}'>
<link rel='icon' type='image/svg+xml' href='/favicon.svg' />
<title>{}</title> <title>{}</title>
<link href='/style/style.css' rel='stylesheet' />
</head> </head>
<body> <body>
<div>{}<div> <div>{}<div>
</body> </body>
<script type='text/javascript' src='/games/view.js'></script>
</html> </html>
", ",
art.locale, art.subtitle, art.title, art.body, art.locale, art_image_uri, art.metadata.description, art.title, art.body,
); );
create_static_view(&app_state, art.category, art.title, html) create_static_view(&app_state, art.category, slug, html)
}
Err(e) => {
return Err(format!(
"Database error searching for article with id {} : {}",
id, e
))
} }
Err(e) => return Err(format!("ERR {}", e)),
} }
} }
...@@ -71,6 +129,8 @@ pub async fn post_article( ...@@ -71,6 +129,8 @@ pub async fn post_article(
let mut article_data = article_data.into_inner(); let mut article_data = article_data.into_inner();
article_data.date = Some(DateTime(Utc::now())); article_data.date = Some(DateTime(Utc::now()));
create_article_static_view_metadata(&app_state, &mut article_data);
match get_collection(&app_state) match get_collection(&app_state)
.insert_one(article_data, None) .insert_one(article_data, None)
.await .await
...@@ -118,11 +178,27 @@ pub async fn update_article( ...@@ -118,11 +178,27 @@ pub async fn update_article(
let mut article_data = article_data.into_inner(); let mut article_data = article_data.into_inner();
article_data.date = Some(DateTime(Utc::now())); article_data.date = Some(DateTime(Utc::now()));
if let Err(e) = delete_static_view(&article_data.metadata.static_resource_path) {
return HttpResponse::InternalServerError()
.body(format!("Error removing previous static view : {}", e));
};
create_article_static_view_metadata(&app_state, &mut article_data);
match get_collection(&app_state) match get_collection(&app_state)
.find_one_and_replace(doc! {"_id": &article_id}, article_data, None) .find_one_and_replace(doc! {"_id": &article_id}, article_data, None)
.await .await
{ {
Ok(res) => HttpResponse::Ok().json(res.unwrap()), Ok(res) => match res {
Some(art) => {
if let Err(e) = generate_article_view(&app_state, &art.clone().id.unwrap()).await {
return HttpResponse::InternalServerError()
.body(format!("Error generating article static view {}", e));
};
HttpResponse::Ok().json(art)
}
None => HttpResponse::InternalServerError().finish(),
},
Err(_) => HttpResponse::InternalServerError().finish(), Err(_) => HttpResponse::InternalServerError().finish(),
} }
} }
...@@ -146,11 +222,23 @@ pub async fn delete_article( ...@@ -146,11 +222,23 @@ pub async fn delete_article(
} }
}; };
match get_collection(&app_state) let articles = get_collection(&app_state);
.find_one_and_delete(doc! {"_id": &article_id}, None) match articles.find_one(doc! {"_id": &article_id}, None).await {
.await Ok(article) => match article {
{ Some(art) => {
Ok(_) => HttpResponse::Accepted().body("Article was deleted"), if let Err(e) = delete_static_view(&art.metadata.static_resource_path) {
return HttpResponse::InternalServerError()
.body(format!("Error removing article static view {}", e));
};
match articles.delete_one(doc! {"_id": &article_id}, None).await {
Ok(_) => HttpResponse::Accepted().body("Article was deleted"),
Err(e) => HttpResponse::InternalServerError()
.body(format!("Error deleting article {}", e)),
}
}
None => HttpResponse::NotFound().body("Article was not found"),
},
Err(e) => HttpResponse::InternalServerError().body(&format!("{:?}", e)), Err(e) => HttpResponse::InternalServerError().body(&format!("{:?}", e)),
} }
} }
......
use crate::app_state::AppState; use crate::app_state::AppState;
use std::fs::{create_dir, create_dir_all, File}; use std::fs::{create_dir, create_dir_all, remove_dir, remove_file, File};
use std::io::Write; use std::io::Write;
use std::path::PathBuf;
pub fn create_static_view( pub fn create_static_view(
app_state: &AppState, app_state: &AppState,
...@@ -38,3 +39,24 @@ pub fn create_static_view( ...@@ -38,3 +39,24 @@ pub fn create_static_view(
Err(e) => Err(format!("Error creating {:?} : {}", f_path, e)), Err(e) => Err(format!("Error creating {:?} : {}", f_path, e)),
} }
} }
pub fn delete_static_view(path: &Option<PathBuf>) -> Result<(), String> {
if let Some(path) = path {
if path.exists() {
if let Err(e) = remove_file(path) {
return Err(format!("Error deleting static view at {:?} : {}", path, e));
};
let parent = path.parent().unwrap();
if let Err(e) = remove_dir(parent) {
return Err(format!(
"Error deleting static view directory at {:?} : {}",
parent, e
));
}
}
}
Ok(())
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment