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 = [
[[package]]
name = "aho-corasick"
version = "0.7.15"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
......@@ -1280,6 +1280,7 @@ dependencies = [
"futures",
"magic-crypt",
"rand 0.8.4",
"regex",
"rustls 0.18.1",
"serde",
"serde_json",
......@@ -1381,9 +1382,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.3.4"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "mime"
......@@ -1872,9 +1873,9 @@ dependencies = [
[[package]]
name = "regex"
version = "1.4.6"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759"
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
dependencies = [
"aho-corasick",
"memchr",
......@@ -1883,9 +1884,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.6.25"
version = "0.6.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
[[package]]
name = "reqwest"
......
......@@ -22,4 +22,5 @@ chrono = "0.4"
rand = "0.8"
dotenv = "0.15"
time = "0.2.7"
regex = "1.5"
tokio = { version = "0.2", features = ["full"] }
......@@ -13,6 +13,9 @@ class Article {
this.body = "";
this.locale = "";
this.display_priority_index = 1;
this.metadata = {
description: "",
};
}
}
......
......@@ -19,6 +19,13 @@ class CreateArticleForm {
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) {
this.state.output[field] = e.target.value;
}
......@@ -347,6 +354,12 @@ class CreateArticleForm {
placeholder: "Article 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_images_inputs(),
{ tag: "input", type: "submit" }
......
......@@ -8,6 +8,6 @@ db.auth(adminname, adminpwd);
articles = db.getCollection("articles");
articles.update({},
{ $set: { "display_priority_index": NumberInt(1) } },
{ $set: { "metadata": { "description": "" } } },
{ upsert: false, multi: true }
);
\ No newline at end of file
......@@ -4,20 +4,22 @@
<head>
<meta charset="utf-8" />
<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="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 -->
<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: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:type" content="website" />
<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_studio_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="og:locale" content="fr_FR"/>
<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_studio_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="og:locale" content="fr_FR" />
<meta property="og:site_name" content="Kuadrado Software" />
<!-- English translation not ready yet -->
......@@ -26,7 +28,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<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>
<!-- 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 -->
......
......@@ -9,6 +9,7 @@ pub struct Env {
pub db_name: String,
pub db_port: String,
pub server_host: String,
pub server_protocol: String,
pub crypt_key: String,
pub default_admin_username: String,
pub default_admin_password: String,
......@@ -50,6 +51,7 @@ impl Env {
db_name: env::var("DATABASE_NAME").expect("DATABASE_NAME 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_protocol: env::var("SERVER_PROTOCOL").expect("SERVER_PROTOCOL is not defined"),
crypt_key: env::var("CRYPT_KEY").expect("CRYPT_KEY is not defined."),
default_admin_username: env::var("DEFAULT_ADMIN_USERNAME")
.expect("DEFAULT_ADMIN_USERNAME is not defined"),
......@@ -72,6 +74,7 @@ impl Env {
db_name: env::var("DATABASE_NAME").expect("DATABASE_NAME 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_protocol: env::var("SERVER_PROTOCOL").expect("SERVER_PROTOCOL is not defined"),
crypt_key: env::var("CRYPT_KEY").expect("CRYPT_KEY is not defined."),
default_admin_username: env::var("DEFAULT_ADMIN_USERNAME")
.expect("DEFAULT_ADMIN_USERNAME is not defined"),
......
#[cfg(test)]
use chrono::Utc;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use wither::{
bson::{doc, oid::ObjectId, DateTime},
prelude::Model,
......@@ -12,6 +13,14 @@ pub struct ArticleDetail {
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)]
#[model(index(keys = r#"doc!{"title": 1}"#, options = r#"doc!{"unique": true}"#))]
pub struct Article {
......@@ -26,6 +35,7 @@ pub struct Article {
pub category: String,
pub locale: String,
pub display_priority_index: i8,
pub metadata: ArticleMetadata,
}
impl Article {
......@@ -51,6 +61,12 @@ impl Article {
category: "testing".to_string(),
locale: "fr".to_string(),
display_priority_index: 1,
metadata: ArticleMetadata {
description: "A test article".to_string(),
view_uri: None,
static_resource_path: None,
slug: None,
},
}
}
}
use crate::{
middleware::AuthenticatedAdminMiddleware, model::Article, static_view::create_static_view,
middleware::AuthenticatedAdminMiddleware,
model::Article,
static_view::{create_static_view, delete_static_view},
AppState,
};
use actix_web::{
......@@ -9,6 +11,7 @@ use actix_web::{
};
use chrono::Utc;
use futures::stream::StreamExt;
use regex::Regex;
use serde::{Deserialize, Serialize};
use wither::{
bson::{doc, oid::ObjectId, Bson, DateTime},
......@@ -25,6 +28,37 @@ fn get_collection(app_state: &AppState) -> Collection<Article> {
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> {
match Article::find_one(&app_state.db, doc! {"_id":&id}, None).await {
Ok(art) => {
......@@ -33,6 +67,20 @@ async fn generate_article_view(app_state: &AppState, id: &ObjectId) -> Result<()
}
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!(
"
<html lang='{}'>
......@@ -40,20 +88,30 @@ async fn generate_article_view(app_state: &AppState, id: &ObjectId) -> Result<()
<meta charset='UTF-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<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='{}'>
<link rel='icon' type='image/svg+xml' href='/favicon.svg' />
<title>{}</title>
<link href='/style/style.css' rel='stylesheet' />
</head>
<body>
<div>{}<div>
</body>
<script type='text/javascript' src='/games/view.js'></script>
</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(
let mut article_data = article_data.into_inner();
article_data.date = Some(DateTime(Utc::now()));
create_article_static_view_metadata(&app_state, &mut article_data);
match get_collection(&app_state)
.insert_one(article_data, None)
.await
......@@ -118,11 +178,27 @@ pub async fn update_article(
let mut article_data = article_data.into_inner();
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)
.find_one_and_replace(doc! {"_id": &article_id}, article_data, None)
.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(),
}
}
......@@ -146,11 +222,23 @@ pub async fn delete_article(
}
};
match get_collection(&app_state)
.find_one_and_delete(doc! {"_id": &article_id}, None)
.await
{
Ok(_) => HttpResponse::Accepted().body("Article was deleted"),
let articles = get_collection(&app_state);
match articles.find_one(doc! {"_id": &article_id}, None).await {
Ok(article) => match article {
Some(art) => {
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)),
}
}
......
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::path::PathBuf;
pub fn create_static_view(
app_state: &AppState,
......@@ -38,3 +39,24 @@ pub fn create_static_view(
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