Browse Source

Merge remote-tracking branch 'origin/master'

# Conflicts:
#	.idea/DigiPoll.iml
#	.idea/misc.xml
#	.idea/workspace.xml
#	src/webserver/sites.rs
integrate_codes_to_fields
anghenfil 2 years ago
parent
commit
2248728b99
  1. 18
      .idea/DigiPoll.iml
  2. 10
      .idea/inspectionProfiles/Project_Default.xml
  3. 11
      Cargo.toml
  4. 2
      LICENSE
  5. 19
      README.md
  6. BIN
      data/favicon/android-chrome-192x192.png
  7. BIN
      data/favicon/android-chrome-384x384.png
  8. BIN
      data/favicon/apple-touch-icon.png
  9. 9
      data/favicon/browserconfig.xml
  10. BIN
      data/favicon/favicon-16x16.png
  11. BIN
      data/favicon/favicon-32x32.png
  12. BIN
      data/favicon/favicon.ico
  13. BIN
      data/favicon/mstile-150x150.png
  14. 40
      data/favicon/safari-pinned-tab.svg
  15. 19
      data/favicon/site.webmanifest
  16. 9
      database-planning.txt
  17. 10
      src/database/code_management.rs
  18. 2
      src/database/mod.rs
  19. 4
      src/database/tests.rs
  20. 123
      src/db_system/document.rs
  21. 2
      src/db_system/mod.rs
  22. 83
      src/db_system/storage.rs
  23. 5
      src/filecache/cache.rs
  24. 2
      src/filecache/cache_error.rs
  25. 26
      src/main.rs
  26. 0
      src/survey_manager/cache_generator.rs
  27. 0
      src/survey_manager/import.rs
  28. 30
      src/survey_manager/mod.rs
  29. 119
      src/utils/poll_generator.rs
  30. 11
      src/webserver/delivery.rs
  31. 2
      src/webserver/formdata.rs
  32. 6
      src/webserver/mod.rs
  33. 2
      src/webserver/session_manager.rs
  34. 32
      src/webserver/site_index.rs
  35. 68
      src/webserver/site_poll_get.rs
  36. 16
      src/webserver/site_poll_post.rs
  37. 148
      src/webserver/site_poll_post_old.rs
  38. 14
      src/webserver/templates.rs
  39. 6
      style/global.css

18
.idea/DigiPoll.iml

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../DigiPoll\examples" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../DigiPoll\tests" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../DigiPoll\benches" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

10
.idea/inspectionProfiles/Project_Default.xml

@ -0,0 +1,10 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<Languages>
<language minSize="48" name="Rust" />
</Languages>
</inspection_tool>
</profile>
</component>

11
Cargo.toml

@ -1,12 +1,13 @@
[package]
name = "Surveyz"
version = "0.1.3"
version = "0.1.5"
authors = ["anghenfil <ares@anghenfil.de>"]
[dependencies]
rocket = "0.4.0"
rocket_contrib = "0.4.0"
rocket = "0.4.1"
rocket_contrib = "0.4.1"
serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] }
lazy_static = "1.2.0"
rand = "0.6.5"
lazy_static = "1.3.0"
rand = "0.6.5"
mongodb = "0.3.12"

2
LICENSE

@ -1,6 +1,6 @@
MIT License
Copyright (c) <year> <copyright holders>
Copyright (c) 2019 Keanu Dölle
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

19
README.md

@ -1,3 +1,18 @@
# DigiPoll
# Surveyz
Simple, lightweight online poll tool written in rust, using Rocket webserver framework.
## Development status
Surveyz is currently in alpha state. Nothing is stable and only a base functionality is implemented. Surveyz uses CouchDB as data storage.
Next steps:
* A lot of Refactoring (Clean-up)
* Check if each survey field exists
* Use mysql to save results
* Implement new question types
* Create easy-to-use webinterface with user management etc.
## Create a survey
1. Create a new directory inside "polls"
2. Create a file inside your directory called "poll.json" (See example)
Simple, lightweight online poll tool written in rust, using Rocket webserver framework

BIN
data/favicon/android-chrome-192x192.png

After

Width: 192  |  Height: 192  |  Size: 5.9 KiB

BIN
data/favicon/android-chrome-384x384.png

After

Width: 384  |  Height: 384  |  Size: 13 KiB

BIN
data/favicon/apple-touch-icon.png

After

Width: 180  |  Height: 180  |  Size: 5.4 KiB

9
data/favicon/browserconfig.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#da532c</TileColor>
</tile>
</msapplication>
</browserconfig>

BIN
data/favicon/favicon-16x16.png

After

Width: 16  |  Height: 16  |  Size: 634 B

BIN
data/favicon/favicon-32x32.png

After

Width: 32  |  Height: 32  |  Size: 1.0 KiB

BIN
data/favicon/favicon.ico

BIN
data/favicon/mstile-150x150.png

After

Width: 270  |  Height: 270  |  Size: 4.1 KiB

40
data/favicon/safari-pinned-tab.svg

@ -0,0 +1,40 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="498.000000pt" height="498.000000pt" viewBox="0 0 498.000000 498.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.11, written by Peter Selinger 2001-2013
</metadata>
<g transform="translate(0.000000,498.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M1930 4629 c-159 -20 -230 -33 -345 -65 -637 -171 -1168 -640 -1420
-1254 -64 -155 -104 -299 -140 -495 -22 -124 -26 -483 -7 -615 54 -353 171
-659 358 -932 235 -342 548 -600 929 -765 110 -48 266 -99 355 -117 14 -3 34
-7 45 -9 11 -2 36 -7 55 -11 19 -3 45 -8 58 -11 105 -20 460 -28 567 -12 28 4
67 10 87 13 32 4 128 23 208 40 70 15 279 93 385 143 230 110 412 239 595 421
305 304 521 714 596 1134 25 139 26 147 29 366 3 191 0 234 -29 428 -11 66
-73 285 -102 358 -25 63 -25 66 -8 79 11 7 87 66 171 131 83 66 238 186 345
269 106 82 195 152 198 155 3 3 31 25 63 49 32 24 54 47 50 52 -4 4 -97 82
-207 173 l-198 167 -47 -37 c-25 -21 -50 -42 -56 -48 -5 -6 -77 -67 -160 -136
-82 -68 -152 -127 -155 -130 -3 -3 -59 -50 -125 -105 -66 -55 -123 -104 -127
-110 -8 -11 -3 -17 -89 89 -108 136 -243 266 -384 371 -148 111 -286 186 -531
291 -38 17 -257 80 -299 87 -16 3 -57 10 -90 15 -33 6 -76 14 -95 17 -59 10
-411 13 -480 4z m437 -84 c26 -3 64 -8 83 -11 232 -29 521 -132 756 -271 203
-121 431 -325 578 -518 l37 -50 -28 -25 c-38 -33 -93 -80 -378 -320 -132 -111
-251 -212 -265 -224 -20 -17 -286 -241 -365 -308 -11 -9 -101 -85 -200 -168
-99 -84 -200 -169 -225 -190 -132 -111 -532 -448 -575 -485 -218 -186 -340
-284 -346 -278 -5 7 -81 285 -290 1073 -16 58 -32 118 -36 134 -8 27 -14 20
-158 -174 -83 -111 -155 -207 -161 -213 -5 -7 -31 -40 -56 -75 -52 -72 -55
-38 20 -272 23 -69 54 -165 70 -215 16 -49 91 -286 167 -525 76 -239 140 -437
142 -439 2 -3 51 33 143 106 8 6 53 41 100 77 47 37 99 77 117 91 95 74 740
575 933 725 124 96 232 180 240 187 8 7 186 145 395 308 209 162 516 401 682
530 166 129 307 235 312 235 10 0 69 -181 95 -290 45 -185 65 -494 45 -680
-27 -257 -97 -498 -210 -722 -274 -547 -765 -940 -1354 -1086 -44 -11 -102
-23 -130 -28 -27 -4 -57 -9 -65 -10 -88 -17 -371 -23 -497 -10 -691 70 -1292
477 -1621 1097 -124 233 -206 507 -229 769 -13 140 -6 412 12 500 3 14 8 41
11 60 12 79 59 252 96 354 219 596 714 1069 1318 1257 88 28 153 45 208 55 12
2 34 7 50 10 130 26 450 37 579 19z"/>
</g>
</svg>

19
data/favicon/site.webmanifest

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-384x384.png",
"sizes": "384x384",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

9
database-planning.txt

@ -0,0 +1,9 @@
Internal Database consists of Documents; 1 document per survey
Example Document:
[
id: 1
name: Example survey
]

10
src/database/code_management.rs

@ -1,7 +1,17 @@
use std::sync::RwLock;
use std::collections::HashMap;
use rocket::response::{status, content};
use rocket::http::{Status, RawStr};
use webserver::templates;
lazy_static! {
//HashMap<Code, PollID>
pub static ref CODE_MANAGER: RwLock<HashMap<String, String>> = RwLock::new(HashMap::new());
}
pub fn code_handling(code : &RawStr) -> Result<String, status::Custom<content::Html<String>>>{
match CODE_MANAGER.read().unwrap().get(&code.to_string()) {
Some(pollid) => Ok(pollid.clone()),
None => Err(status::Custom(Status::NotFound, content::Html(templates::get_error("Error 404: Invalid Code", "The Code you entered is invalid!", vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new())))),
}
}

2
src/database/mod.rs

@ -1,5 +1,5 @@
pub mod database;
pub mod code_management;
pub mod tests;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]

4
src/database/tests.rs

@ -0,0 +1,4 @@
#[cfg(test)]
mod tests{
}

123
src/db_system/document.rs

@ -0,0 +1,123 @@
//Data structures for serialisation
use std::string::ToString;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use std::result::Iter;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub enum DbType {
String(String),
I8(i8),
I16(i16),
I32(i32),
I64(i64),
I128(i128),
Bool(bool),
Char(char),
Document(Document)
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Document{
fields : HashMap<String, DbType>
}
impl Document{
pub fn new() -> Document{
Document{
fields: HashMap::new()
}
}
pub fn get(&self, key : &str) -> Option<&DbType>{
self.fields.get(key)
}
pub fn get_string(&self, key : &str) -> Option<String>{
match self.fields.get(key)?{
DbType::String(value) => Some(value.clone()),
_ => None
}
}
pub fn get_i8(&self, key : &str) -> Option<i8>{ //TODO: Check if clone is necessary here
match self.fields.get(key)?{
DbType::I8(value) => Some(value.clone()),
_ => None
}
}
pub fn get_i16(&self, key : &str) -> Option<i16>{
match self.fields.get(key)?{
DbType::I16(value) => Some(value.clone()),
_ => None
}
}
pub fn get_i32(&self, key : &str) -> Option<i32>{
match self.fields.get(key)?{
DbType::I32(value) => Some(value.clone()),
_ => None
}
}
pub fn get_i64(&self, key : &str) -> Option<i64>{
match self.fields.get(key)?{
DbType::I64(value) => Some(value.clone()),
_ => None
}
}
pub fn get_i128(&self, key : &str) -> Option<i128>{
match self.fields.get(key)?{
DbType::I128(value) => Some(value.clone()),
_ => None
}
}
pub fn get_bool(&self, key : &str) -> Option<bool>{
match self.fields.get(key)?{
DbType::Bool(value) => Some(value.clone()),
_ => None
}
}
pub fn get_char(&self, key : &str) -> Option<char>{
match self.fields.get(key)?{
DbType::Char(value) => Some(value.clone()),
_ => None
}
}
pub fn get_doc(&self, key : &str) -> Option<Document>{
match self.fields.get(key)?{
DbType::Document(value) => Some(value.clone()),
_ => None
}
}
pub fn insert(&mut self, key : String, value : DbType){
self.fields.insert(key, value);
}
}
#[test]
fn test_document(){ //TODO: Write full unit tests
let mut doc = Document::new();
doc.insert(String::from("test1"), DbType::String(String::from("Test")));
doc.fields.insert(String::from("test2"), DbType::I32(120));
match doc.get("test1").unwrap().clone(){
DbType::String(value) => println!("value:{}", value),
_ => {}
}
match doc.get("test2").unwrap().clone(){
DbType::I32(value) => println!("value:{}", value),
_ => {}
}
println!("value special: {}", doc.get_string("test1").unwrap());
}

2
src/db_system/mod.rs

@ -0,0 +1,2 @@
pub mod document; //data storage to save data
pub(crate) mod storage;

83
src/db_system/storage.rs

@ -0,0 +1,83 @@
use db_system::document::{Document, DbType};
use std::sync::RwLock;
use std::collections::HashMap;
use serde::{Serialize, Deserialize};
use serde_json::{to_writer};
use std::fs::File;
use std::path::Path;
use std::io::{BufWriter, BufReader, Error, ErrorKind};
use std::error::Error as gerror;
lazy_static! {
pub static ref DOCUMENTS: RwLock<DocumentStorage> = RwLock::new(DocumentStorage{
documents: HashMap::new()
});
}
#[derive(Serialize, Deserialize, Debug)]
pub struct DocumentStorage{
pub documents : HashMap<String, Document>
}
impl DocumentStorage{
pub fn new() -> DocumentStorage{
DocumentStorage{
documents: HashMap::new()
}
}
pub fn get(&self, id : &str) -> Option<&Document>{
match self.documents.get(id) {
Some(document) => Some(document),
_ => None
}
}
pub fn add(&mut self, id : String, document : Document){
self.documents.insert(id, document);
}
pub fn remove(&mut self, id : &str){
self.documents.remove(id);
}
pub fn load(&mut self) -> Option<std::io::Error>{
let mut input = File::open(Path::new("data/database.save"));
match input{
Ok(file) => {
let imported_documents = serde_json::from_reader(BufReader::new(file)).unwrap();
self.documents = imported_documents;
None
}
Err(error) => {Some(error)} //TODO error handling
}
}
pub fn save(&self) -> Option<std::io::Error>{
let mut output = File::create(Path::new("data/database.save"));
match output{
Ok(file)
=> {
match serde_json::to_writer(BufWriter::new(file), &self.documents){
Ok(_) => None,
Err(error) => Some(Error::new(ErrorKind::Other, format!("Serialization not successful: {}", error.description())))
}
}
Err(error) => {
Some(error)
}//TODO: CRITICAL WARNING, FALLBACK SAVE?
}
}
}
#[test]
pub fn testsave(){
let mut testdoc1 = Document::new();
testdoc1.insert(String::from("Test1"), DbType::String(String::from("Test1 passt")));
testdoc1.insert(String::from("Test2"), DbType::String(String::from("Test2 passt")));
let mut testdoc2 = Document::new();
testdoc2.insert(String::from("Test3"), DbType::Document(testdoc1));
testdoc2.insert(String::from("Test4"), DbType::String(String::from("Test4 passt")));
DOCUMENTS.write().unwrap().add(String::from("testdoc2"), testdoc2);
DOCUMENTS.read().unwrap().save();
}

5
src/filecache/cache.rs

@ -16,7 +16,10 @@ impl Cache{
}
pub fn refresh_all(&mut self){
for var in self.cache.iter_mut() {
var.1.refresh();
match var.1.refresh(){
Err(e) => eprintln!("Error while refreshing cache: {}", e),
_ => {}
}
}
}
pub fn add(&mut self, name : String, path : String, max_age : i32){

2
src/filecache/cache_error.rs

@ -1,8 +1,6 @@
use std::fmt;
use std::io;
type Result<T> = std::result::Result<T, CacheError>;
#[derive(Debug)]
pub enum CacheError{
FileNotFound,

26
src/main.rs

@ -8,15 +8,41 @@ extern crate rocket_contrib;
extern crate serde_json;
extern crate serde;
extern crate rand;
extern crate mongodb;
use std::path::Path;
use std::fs::File;
use std::io::Write;
mod webserver;
mod filecache;
mod database;
mod utils;
mod db_system;
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
fn main(){
println!("Starting Surveyz version {}", VERSION);
println!("Checking for database");
if Path::new("data/database.save").is_file() {
println!("Existing database found.");
}else{
println!("No existing database found. Trying to create new database.");
match File::create("data/database.save"){
Ok(mut file) => {
match file.write_all(b"{}") {
Ok(_) => println!("Successfully created new database."),
Err(e) => panic!("Couldn't write into new database file. Error: {}", e)
}
}
Err(e) => panic!("Couldn't create new database. Error: {}", e)
}
}
println!("Trying to load database.");
match db_system::storage::DOCUMENTS.write().unwrap().load(){
Some(error) => panic!("Could not load internal database! Error was: {}", error),
None => println!("Successfully load database")
}
webserver::delivery::deliver();
}

0
src/database/database.rs → src/survey_manager/cache_generator.rs

0
src/survey_manager/import.rs

30
src/survey_manager/mod.rs

@ -0,0 +1,30 @@
pub mod code_management;
pub mod tests;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct QuestionBlockContainer {
pub id : i32,
pub polltype : i8,
pub active : bool,
pub active_from : i32,
pub active_to : i32,
pub questionblocks : Vec<QuestionBlock>
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Question{
pub identifier : String,
pub question : String,
pub optional : bool,
pub questiontype : i8,
#[serde(default)]
pub selection_options : Vec<String>
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct QuestionBlock{
pub title : String,
pub description : String,
pub questions : Vec<Question>
}

119
src/utils/poll_generator.rs

@ -1,119 +0,0 @@
use std::fs::File;
use std::io::BufReader;
use std::fs;
use database;
use webserver;
use std::fs::OpenOptions;
use std::io::Write;
pub fn generate_poll(pollid : String) {
let file = File::open(format!("data/polls/{}/poll.json", pollid)).unwrap();
let reader = BufReader::new(file);
let poll: database::Poll = serde_json::from_reader(reader).unwrap(); //Read json into structs
fs::remove_dir_all(format!("data/polls/{}/cache/", pollid)).ok(); //Clear cache
fs::create_dir_all(format!("data/polls/{}/cache/", pollid)); //Create cache
for i in 0..poll.questionblocks.len() {
fs::write(format!("data/polls/{}/cache/questionblock-{}.html", pollid, i), generate_questionblock(&poll.questionblocks[i], &pollid));
webserver::delivery::CACHE.write().unwrap().add(format!("{}/questionblock-{}", pollid, i), format!("data/polls/{}/cache/questionblock-{}.html", pollid, i), 0);
}
let mut buffer = OpenOptions::new().append(true).open(format!("data/polls/{}/result.csv", pollid)).unwrap();
buffer.write("\n".as_bytes());
}
pub fn generate_questionblock(questionblock : &database::QuestionBlock, pollid : &str) -> String{
let mut last_question_type : i8 = 0;
let questions : String = questionblock.questions.iter().map(|question| {
if question.optional {
webserver::delivery::CACHE.write().unwrap().add_value(format!("{}/q{}-optional", pollid, question.identifier), String::from("true")); //TODO: handle error if there is more then one question with identifier X
}else{
webserver::delivery::CACHE.write().unwrap().add_value(format!("{}/q{}-optional", pollid, question.identifier), String::from("false"));
}
let mut buffer = OpenOptions::new().append(true).open(format!("data/polls/{}/result.csv", pollid)).unwrap();
buffer.write(&format!("{},", question.identifier).as_bytes());
let generated = generate_question(question, last_question_type);
last_question_type = question.questiontype;
return generated;
}).collect();
return format!("<div class=\"questionblock\"><h1 class=\"blocktitle\">{}</h1><span class=\"blockdescription\">{}</span><br><br><br><div class\"questions\">{}</div></div>", questionblock.title, questionblock.description, questions);
}
pub fn generate_question(question : &database::Question, last_question_type : i8) -> String{
let required = if ! question.optional {
"required"
}else{
""
};
if question.questiontype == 10{
let mut temp = String::new();
if last_question_type != 10 {
temp = String::from("<div class=\"group\
\">
<div class=\"ml-auto\">
<span class=\"legend\">++</span>
<span class=\"legend\">+</span>
<span class=\"legend\">-</span>
<span class=\"legend\">--</span>
<span class=\"legend\">N/A</span>
</div>
</div>");
}
return format!("
{}
<div class=\"group d-flex\">
<p class=\"question\">{}</p>
<div class=\"checkboxes ml-auto\">
<input type=\"hidden\" name=\"q{}\" />
<label class=\"checkbox form-group\">
<input type=\"radio\" name=\"q{}\" value=\"N/A\" oninvalid=\"report_missing()\"{}>
<span class=\"default\"></span>
</label>
<label class=\"checkbox form-group\">
<input type=\"radio\" name=\"q{}\" value=\"1\">
<span class=\"default\"></span>
</label>
<label class=\"checkbox form-group\">
<input type=\"radio\" name=\"q{}\" value=\"2\">
<span class=\"default\"></span>
</label>
<label class=\"checkbox form-group\">
<input type=\"radio\" name=\"q{}\" value=\"3\">
<span class=\"default\"></span>
</label>
<label class=\"checkbox form-group\">
<input type=\"radio\" name=\"q{}\" value=\"4\">
<span class=\"default\"></span>
</label>
</div>
</div>
", temp, question.question, question.identifier, question.identifier, required, question.identifier, question.identifier, question.identifier, question.identifier)
}else if question.questiontype == 20 {
return format!("
<div>
<p>{}</p>
<div class=\"textfield\">
<textarea class=\"textfield\" name=\"q{}\" rows=\"5\" {}></textarea>
</div>
</div>", question.question, question.identifier, required);
}else if question.questiontype == 30 {
let options : String = question.selection_options.iter().map(|option|{
format!("<option value=\"{}\">{}</option>", option, option)
}).collect();
return format!("<div><p>{}</p><select name=\"q{}\" {}>
{}
</select></div>", question.question, question.identifier, required, options);
}else{
return String::from("<p>Error: Unkown Qustiontype.");
}
}

11
src/webserver/delivery.rs

@ -1,4 +1,6 @@
use webserver::sites;
use webserver::site_poll_get;
use webserver::site_poll_post;
use webserver::site_index;
use webserver::styles;
use filecache::cache::Cache;
use utils::poll_generator::generate_poll;
@ -25,11 +27,12 @@ pub fn deliver() {
code_management::CODE_MANAGER.write().unwrap().insert(String::from("FLADF"), String::from("poll-1"));
rocket::ignite()
.mount("/", routes![sites::index])
.mount("/", routes![site_index::index])
.mount("/", routes![styles::getcss])
.mount("/", routes![styles::getjs])
.mount("/", routes![sites::poll])
.mount("/", routes![sites::poll_post])
.mount("/", routes![site_poll_get::poll])
.mount("/", routes![site_poll_post::poll])
.mount("/images/", StaticFiles::from("data/logos"))
.mount("/favicon/", StaticFiles::from("data/favicon/"))
.launch();
}

2
src/webserver/formdata.rs

@ -10,7 +10,7 @@ impl<'f> FromForm<'f> for FormData {
// In practice, we'd use a more descriptive error type.
type Error = ();
fn from_form(items: &mut FormItems<'f>, strict: bool) -> Result<FormData, ()> {
fn from_form(items: &mut FormItems<'f>, strict: bool) -> Result<FormData, ()> { //TODO: use strict
let mut fields : HashMap<String, String> = HashMap::new();
for formitem in items {

6
src/webserver/mod.rs

@ -1,6 +1,8 @@
pub mod delivery;
mod sites;
mod site_index;
mod site_poll_post;
mod site_poll_get;
mod styles;
mod session_manager;
mod formdata;
mod templates;
pub(crate) mod templates;

2
src/webserver/session_manager.rs

@ -38,7 +38,7 @@ pub fn check_session_id(sessionid : &str) -> bool{
pub fn session_handling(sessionid : Option<&RawStr>, code : &RawStr) -> Result<String, status::Custom<content::Html<String>>> {
let mut session_ok = true;
let mut sessionid2 : String = String::new();
let sessionid2 : String;
if sessionid.is_none() {
sessionid2 = generate_session_id();

32
src/webserver/site_index.rs

@ -0,0 +1,32 @@
use rocket::response::content;
use rocket::http::RawStr;
use webserver::templates;
#[get("/?<error>")]
pub fn index(error: Option<&RawStr>) -> content::Html<String> {
let mut error_str = String::from("");
error_str = error.map(|error| {
if error.as_str() == "empty" {
error_str = String::from("Error: No code entered.");
} else if error.as_str() == "invalid" {
error_str = String::from("Error: Code invalid.");
} else {
error_str = String::from("Unexpected Error.");
}
format!("<div class=\"alert alert-danger\" role=\"alert\">{}</div>", String::from(error_str))
}).unwrap_or_else(|| String::from(""));
let template_content = format!("
<div class=\"mainframe text-center align-middle\">
<h1 class=join>Join Poll</h1>{}
<form action=\"poll\" method=\"get\">
<div class=\"form-row formgroup\">
<input class=\"code_input\" name=\"code\" size=\"6\" type=\"text\" placeholder=\"Code\">
<button type=\"submit\" class=\"btn go\">GO</button>
</div>
</form>
</div>", error_str);
content::Html(templates::get_main(&template_content, vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/dashboard.css".to_string()), Vec::new()))
}

68
src/webserver/site_poll_get.rs

@ -0,0 +1,68 @@
use rocket::response::content;
use rocket::http::RawStr;
use webserver;
use rocket::response::status;
use webserver::{session_manager, templates};
use rocket::http::Status;
use std::io::Write;
use database::code_management;
use std::path::Path;
#[get("/poll?<code>&<sessionid>")]
pub fn poll(code: &RawStr, sessionid: Option<&RawStr>) -> Result<content::Html<String>, status::Custom<content::Html<String>>> {
let sessionid: String = match session_manager::session_handling(sessionid, code) {
Ok(sessionid) => sessionid,
Err(e) => return Err(e)
};
let codetemp = code.to_string().clone();
let poll : String = if code_management::CODE_MANAGER.read().unwrap().contains_key(&codetemp) {
code_management::CODE_MANAGER.read().unwrap().get(&codetemp).unwrap().clone()
}else{
return Err(status::Custom(Status::BadRequest, content::Html(templates::get_error("Error 400: Invalid Code", "The Code your entered is invalid.", vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new()))))
};
let mut logo = String::new();
if Path::new(&format!("data/logos/{}.png", &poll)).exists(){
logo = format!("<img class=\"logo\" src=\"/images/{}.png\" alt=\"logo\">", &poll);
}
let mut step = session_manager::SESSIONS.read().unwrap().get(&sessionid).unwrap().step.clone();
if step == "0" { //First step, change step to first questionblock
step = String::from("questionblock-0"); //TODO: Check if at least one questionblock exists
}else{ //Step should look like "questionblock-x"
if !webserver::delivery::CACHE.read().unwrap().check_file(&format!("{}/{}", &poll, &step)) {
//Questionblock unknown (not in cache)
eprintln!("Error 500 occured: {}/{} not found in cache.", &poll, &step);
return Err(status::Custom(Status::InternalServerError, content::Html(templates::get_error("Error 500: Cache error", "Missing questionblock in cache.", vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new()))))
}
}
if session_manager::SESSIONS.read().unwrap().get(&sessionid).unwrap().complete {
let template_content = format!("{}
<div class=\"mainframe text-center align-middle\">
<h1>Completed!</h1>
<p>Thank you for your participation!</p>
</div>", logo);
return Ok(content::Html(templates::get_main(&template_content, vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new())))
}
let cache = webserver::delivery::CACHE.read().unwrap();
let content = cache.get_file(format!("{}/{}", &poll, step));
if content.is_ok() {
let template_content = format!("{}
<div class=\"mainframe text-center align-middle\">
<form action=\"/poll?code={}&sessionid={}\" method=\"post\">
{}
<button type=\"submit\" name=\"submit\" value=\"{}\" onclick=\"on_submit()\">Continue</button>
</form>
</div>", logo, code, sessionid, content.unwrap().content, step);
return Ok(content::Html(templates::get_main(&template_content, vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()),vec!("script/poll.js".to_string()))))
} else {
return Err(status::Custom(Status::NotFound, content::Html(templates::get_error("Error 404: Not found", "Couldn't find requested survey", vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new()))))
}
}

16
src/webserver/site_poll_post.rs

@ -0,0 +1,16 @@
use webserver::formdata::FormData;
use rocket::request::Form;
use rocket::response::{content, status};
use webserver::{session_manager, templates};
use database::code_management;
use rocket::http::{Status, RawStr};
#[post("/poll?<code>&<sessionid>", data = "<data>")]
pub fn poll(code: &RawStr, data: Form<FormData>, sessionid: Option<&RawStr>) -> Result<content::Html<String>, status::Custom<content::Html<String>>> {
let mut data_fields = data.fields.clone();
let sessionid: String = session_manager::session_handling(sessionid, code)?; //Aquire valid sessionid or report error
let pollid = code_management::code_handling(code)?; //Check pollid or report error
Err(status::Custom(Status::NotFound, content::Html(templates::get_error("Error 404: Not implemented yet.", "", vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new()))))
}

148
src/webserver/site_poll_post_old.rs

@ -0,0 +1,148 @@
use rocket::response::content;
use rocket::http::RawStr;
use rocket::request::Form;
use webserver;
use rocket::response::status;
use webserver::{session_manager, templates};
use rocket::http::Status;
use webserver::formdata::FormData;
use std::fs::OpenOptions;
use std::io::Write;
use database::code_management;
use std::path::Path;
#[post("/poll?<code>&<sessionid>", data = "<data>")]
pub fn poll(code: &RawStr, data: Form<FormData>, sessionid: Option<&RawStr>) -> Result<content::Html<String>, status::Custom<content::Html<String>>> {
let mut data_fields = data.fields.clone();
//TODO: Check: replaceble with let sessionid: String = match session_manager::session_handling(sessionid, code)?;
let sessionid: String = match session_manager::session_handling(sessionid, code) { //obtain valid session id or return error
Ok(sessionid) => sessionid,
Err(e) => return Err(e)
};
let codetemp = code.to_string().clone();
let poll: String = if code_management::CODE_MANAGER.read().unwrap().contains_key(&codetemp) {
code_management::CODE_MANAGER.read().unwrap().get(&codetemp).unwrap().clone()
} else {
return Err(status::Custom(Status::NotFound, content::Html(templates::get_error("Error 404: Invalid Code", "The Code you entered is invalid!", vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new()))))
};
//Obtain current step:
//TODO: handling if survey completed
let mut step = session_manager::SESSIONS.read().unwrap().get(&sessionid).unwrap().step.clone();
if step == "0" { //First step, change step to first questionblock
step = String::from("questionblock-0"); //TODO: Check if at least one questionblock exists
} else { //Step should look like "questionblock-x"
if !webserver::delivery::CACHE.read().unwrap().check_file(&format!("{}/{}", &poll, &step)) {
//Questionblock unkown (not in cache)
//TODO: Server error
}
}
let mut submitted: String;
let temp = data_fields.get("submit").cloned();
if temp.is_none() {
return Err(status::Custom(Status::BadRequest, content::Html(templates::get_error("Error 400", "Submitted data were invalid", vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new()))))
} else {
submitted = temp.unwrap();
}
data_fields.remove("submit");
let temp1 = step.clone();
let parts : Vec<&str>= temp1.split("-").collect();
let mut num1 : i32 = parts.last().unwrap().parse().unwrap(); //TODO: replace unwrap with error handling
let temp2 = submitted.clone();
let parts : Vec<&str>= temp2.split("-").collect();
let num2 : i32 = parts.last().unwrap().parse().unwrap(); //TODO: replace unwrap with error handling
let mut error = false;
if submitted.clone() != step{ //user submitted wrong step
if num2 > num1 { //Example: User submitted step 6 but should submit step 4
//do not accept step and display expected step (in example step 4)
error = true;
}else if num2 < num1{ //Example: user submitted step 3 again, but should be at step 5
//accept step and display next step (in example step 4)
num1 = num2;
}
}
for item in &data_fields {
if item.1 == "" {
if webserver::delivery::CACHE.read().unwrap().get_value(&format!("{}/{}-optional", &poll, item.0)).unwrap() == "false" { //TODO error handling instead of unwrap
//non optional field is empty!
error = true;
}
}
}
if !error {
//save data:
let data2 : Vec<(String, String)> = data_fields.iter().map(|item| (item.0.clone(), item.1.clone())).collect();
for item in data2.iter().rev() {
session_manager::SESSIONS.write().unwrap().get_mut(&sessionid).unwrap().data.insert(item.0.clone(), item.1.clone());
//session_manager::SESSIONS.write().unwrap().get_mut(&sessionid).unwrap().data.push((item.0.clone(), item.1.clone()));
}
//increase step
if webserver::delivery::CACHE.read().unwrap().check_file(&format!("{}/questionblock-{}", &poll, num1+1)){
step = format!("questionblock-{}", num1+1);
session_manager::SESSIONS.write().unwrap().get_mut(&sessionid).unwrap().step = step.clone();
}else{
session_manager::SESSIONS.write().unwrap().get_mut(&sessionid).unwrap().complete = true;
for item in session_manager::SESSIONS.read().unwrap().get(&sessionid).unwrap().data.iter() {
}
let data_string : String = session_manager::SESSIONS.read().unwrap().get(&sessionid).unwrap().data.iter().map(|item| format!("\"{}={}\",",item.0, item.1)).collect();
let mut buffer = OpenOptions::new().append(true).open(format!("data/polls/{}/result.csv", &poll)).unwrap();
buffer.write(format!("{}\n", data_string).as_bytes());
println!("{}", data_string);
}
}else{
//TODO: Error reporting if values missing
}
let mut logo = String::new();
if Path::new(&format!("data/logos/{}.png", &poll)).exists(){
logo = format!("<img class=\"logo\" src=\"/images/{}.png\" alt=\"logo\">", &poll);
}
if session_manager::SESSIONS.read().unwrap().get(&sessionid).unwrap().complete {
let template_content = format!("{}
<div class=\"mainframe text-center align-middle\">
<h1>Completed!</h1>
<p>Thank you for your participation!</p>
</div>", logo);
return Ok(content::Html(templates::get_main(&template_content, vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new())))
}
let cache = webserver::delivery::CACHE.read().unwrap();
let content = cache.get_file(format!("{}/{}", &poll, step));
if content.is_ok() {
let template_content = format!("{}
<div class=\"mainframe text-center align-middle\">
<form action=\"/poll?code={}&sessionid={}\" method=\"post\">
{}
<button type=\"submit\" name=\"submit\" value=\"{}\" onclick=\"on_submit()\">Continue</button>
</form>
</div>", logo, code, sessionid, content.unwrap().content, step);
return Ok(content::Html(templates::get_main(&template_content, vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()),vec!("script/poll.js".to_string()))))
} else {
return Err(status::Custom(Status::NotFound, content::Html(templates::get_error("Error 404: Not found", "Couldn't find requested survey", vec!("style/bootstrap.css".to_string(), "style/global.css".to_string(), "style/poll.css".to_string()), Vec::new()))))
}
//}
}

14
src/webserver/templates.rs

@ -1,3 +1,5 @@
use VERSION;
pub fn get_main(content : &str, stylesheets : Vec<String>, scripts : Vec<String>) -> String{ //main template
let stylesheets_list : String = stylesheets.iter().map(|stylesheet|{
format!("<link rel=\"stylesheet\" type=\"text/css\" href=\"{}\">", stylesheet)
@ -12,15 +14,23 @@ pub fn get_main(content : &str, stylesheets : Vec<String>, scripts : Vec<String>
<head>
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">
<link rel=\"apple-touch-icon\" sizes=\"180x180\" href=\"/favicon/apple-touch-icon.png\">
<link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/favicon/favicon-32x32.png\">
<link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/favicon/favicon-16x16.png\">
<link rel=\"manifest\" href=\"/favicon/site.webmanifest\">
<link rel=\"shortcut icon\" href=\"/favicon/favicon.ico\">
<meta name=\"msapplication-TileColor\" content=\"#da532c\">
<meta name=\"msapplication-config\" content=\"/favicon/browserconfig.xml\">
<meta name=\"theme-color\" content=\"#ffffff\">
<title>Surveyz</title>
{}
</head>
<body>{}{}
<div class=\"footer\">
<p>Powered by Surveyz - Project created by Keanu Dölle</p>
<p>Powered by Surveyz (Version {}) - Project created by Keanu Dölle</p>
</div>
</body>
</html>", stylesheets_list, content, scripts_list)
</html>", stylesheets_list, content, scripts_list, VERSION)
}
pub fn get_error(error : &str, description : &str, stylesheets : Vec<String>, scripts : Vec<String>) -> String {

6
style/global.css

@ -1,3 +1,9 @@
body{
height: 100%;
display: flex;
flex-direction: column;
}
@font-face{
font-family: 'Lora';
src: url('fonts/Lora/Lora-Bold.ttf') format('woff');

Loading…
Cancel
Save