Initial commit

This commit is contained in:
Dennis Brentjes 2022-03-20 10:55:30 +01:00
commit ee500203e2
14 changed files with 2344 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/target
.vscode
Rocket.toml
*.sqlite

2019
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

18
Cargo.toml Normal file
View File

@ -0,0 +1,18 @@
[package]
name = "gamenight"
version = "0.1.0"
authors = ["Dennis Brentjes <d.brentjes@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rocket = { version = "0.5.0-rc.1", features = ["default", "json"] }
libsqlite3-sys = { version = ">=0.8.0, <0.19.0", features = ["bundled"] }
rocket_sync_db_pools = { version = "0.1.0-rc.1", features = ["diesel_sqlite_pool"] }
diesel = { version = "1.4.8", features = ["sqlite"] }
diesel_migrations = "1.4.0"
rocket_dyn_templates = { version = "0.1.0-rc.1", features = ["handlebars"] }
chrono = "0.4.19"
serde = "1.0.136"

4
Rocket.toml.example Normal file
View File

@ -0,0 +1,4 @@
#Copy this file over to Rocket.toml after changing all relevant values.
[global.databases]
gamenight_database = { url = "gamenight.sqlite" }

View File

@ -0,0 +1,4 @@
-- This file should undo anything in `up.sql`
drop table gamenight;
drop table known_games;

View File

@ -0,0 +1,12 @@
-- Your SQL goes here
CREATE TABLE gamenight (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
game text TEXT NOT NULL,
datetime TEXT NOT NULL
);
CREATE TABLE known_games (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
game TEXT UNIQUE NOT NULL
);

30
readme.md Normal file
View File

@ -0,0 +1,30 @@
# Gamenight
Een online tooltje voor het organiseren van een \(board\)gamenight
Het doel, Je kan een GameNight organiseren, je geeft wanneer je zou willen spelen, je geeft aan waar je zin in hebt, welke games je hebt, en of je kan hosten. Mensen kunnen zich erbij klikken, hopelijk volgt er een spelletjes consensus, en go.
Ik wil iets maken wat georganiseerder is dan een mailthread en minder push bericht is dan een boargame appgroep.
Geplande features:
* Account systeem zodat het niet allemaal publiek is.
* Manier om een event toe te voegen.
* Manier voor owners om een event te verwijderen.
* Lijst aan upcoming events.
* Lijst aan archived events
* Manier om te koppelen aan je account welke games je hebt, zodat dit automatisch aangevult.wordt als je een party joined,
* manier om comments te plaatsen op een event.
* manier om een of meer spellen te selecten zodat mensen dit ook daadwerkelijk meenemen en niet iedereen alles hoeft mee te nemen #QOL
* manier om recurring game avonden te plannen.
Meta features:
* Api apart van de site ontwikkelen zodat je shit kan automagiseren, zelf push berichten kan fixen als je wil via de API.
* Een beetje sexy website bouwen zodat hij op zijn minst bruikbaar is op je mobiel.
# Mee devven?
Graag!
Belangrijkste devding dat je moet weten is dat je diesel migrations kan genereren en invulling kan geven, deze database migraties worden automatisch uitgevoerd als je de binary daarna start, of je kan ze handmatig uitvoeren met de diesel executable zelf. Ik weet niet zeker of je diesel nog handmatig moet installeren, maar ik denk het wel `cargo install diesel`
database migration genereren: `diesel migration generate <descriptive name>`

58
src/api.rs Normal file
View File

@ -0,0 +1,58 @@
use crate::schema;
use rocket::form::Form;
use rocket::serde::json::{Json, json, Value};
use rocket::http::Status;
use rocket::request::{self, Request, FromRequest};
use rocket::outcome::Outcome::{Success, Failure};
use rocket::response::{Redirect, Flash};
pub struct Referer(String);
#[derive(Debug)]
pub enum ReferrerError {
Missing,
MoreThanOne
}
#[derive(Debug, Responder)]
pub enum ApiResponse {
Status(Status),
Redirect(Redirect),
Value(Value),
Flash(Flash<Redirect>)
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for Referer {
type Error = ReferrerError;
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
let referers : Vec<_> = req.headers().get("Referer").collect();
match referers.len() {
0 => Failure((Status::BadRequest, ReferrerError::Missing)),
1 => Success(Referer(referers[0].to_string())),
_ => Failure((Status::BadRequest, ReferrerError::MoreThanOne)),
}
}
}
#[get("/gamenights")]
pub async fn gamenights(conn: schema::DbConn) -> ApiResponse {
let gamenights = schema::get_all_gamenights(conn).await;
ApiResponse::Value(json!(gamenights))
}
#[post("/gamenight", format = "application/json", data = "<gamenight_json>")]
pub async fn gamenight_post_json(conn: schema::DbConn, gamenight_json: Json<schema::GameNightNoId>) -> ApiResponse {
schema::insert_gamenight(conn, gamenight_json.into_inner()).await;
ApiResponse::Status(Status::Accepted)
}
#[post("/gamenight", format = "application/x-www-form-urlencoded", data = "<gamenight_form>")]
pub async fn gamenight_post_form(referer: Option<Referer>, conn: schema::DbConn, gamenight_form: Form<schema::GameNightNoId>) -> ApiResponse {
schema::insert_gamenight(conn, gamenight_form.into_inner()).await;
match referer {
None => ApiResponse::Status(Status::Accepted),
Some(referer) => ApiResponse::Flash(Flash::success(Redirect::to(referer.0), "Added Gamenight."))
}
}

20
src/main.rs Normal file
View File

@ -0,0 +1,20 @@
#[macro_use] extern crate rocket;
#[macro_use] extern crate diesel_migrations;
#[macro_use] extern crate diesel;
use rocket::fairing::AdHoc;
use rocket_dyn_templates::Template;
mod api;
pub mod schema;
mod site;
#[launch]
fn rocket() -> _ {
rocket::build()
.attach(schema::DbConn::fairing())
.attach(Template::fairing())
.attach(AdHoc::on_ignite("Run Migrations", schema::run_migrations))
.mount("/", routes![site::index, site::gamenights, site::add_game_night])
.mount("/api", routes![api::gamenights, api::gamenight_post_form, api::gamenight_post_json])
}

83
src/schema.rs Normal file
View File

@ -0,0 +1,83 @@
use rocket_sync_db_pools::database;
use serde::{Serialize, Deserialize};
use rocket::{Rocket, Build};
use diesel::RunQueryDsl;
#[database("gamenight_database")]
pub struct DbConn(diesel::SqliteConnection);
table! {
gamenight (id) {
id -> Integer,
game -> Text,
datetime -> Text,
}
}
table! {
known_games (game) {
id -> Integer,
game -> Text,
}
}
allow_tables_to_appear_in_same_query!(
gamenight,
known_games,
);
pub async fn get_all_gamenights(conn: DbConn) -> Vec::<GameNight> {
conn.run(|c| {
gamenight::table.load::<GameNight>(c).unwrap()
}).await
}
pub async fn insert_gamenight(conn: DbConn, new_gamenight: GameNightNoId) -> () {
conn.run(|c| {
diesel::insert_into(gamenight::table)
.values(new_gamenight)
.execute(c)
.unwrap()
}).await;
}
pub async fn run_migrations(rocket: Rocket<Build>) -> Rocket<Build> {
// This macro from `diesel_migrations` defines an `embedded_migrations`
// module containing a function named `run`. This allows the example to be
// run and tested without any outside setup of the database.
embed_migrations!();
let conn = DbConn::get_one(&rocket).await.expect("database connection");
conn.run(|c| embedded_migrations::run(c)).await.expect("can run migrations");
rocket
}
#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)]
#[table_name="known_games"]
pub struct GameNoId {
pub game : String,
}
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
pub struct Game {
pub id: i32,
pub game : String,
}
#[derive(Serialize, Deserialize, Debug, FromForm, Insertable)]
#[table_name="gamenight"]
pub struct GameNightNoId {
pub game : String,
pub datetime : String,
}
#[derive(Serialize, Deserialize, Debug, FromForm, Queryable)]
pub struct GameNight {
pub id: i32,
pub game : String,
pub datetime : String,
}

57
src/site.rs Normal file
View File

@ -0,0 +1,57 @@
use serde::{Serialize, Deserialize};
use rocket_dyn_templates::Template;
use rocket::response::{Redirect};
use rocket::request::{FlashMessage};
use crate::schema;
#[derive(Serialize, Deserialize, Debug)]
struct FlashData {
has_data: bool,
kind: String,
message: String
}
#[derive(Serialize, Deserialize, Debug)]
struct GameNightsData {
gamenights: Vec::<schema::GameNight>,
flash: FlashData
}
#[get("/gamenights")]
pub async fn gamenights(conn: schema::DbConn) -> Template {
let gamenights = schema::get_all_gamenights(conn).await;
let data = GameNightsData {
gamenights: gamenights,
flash: FlashData { has_data: false, message: "".to_string(), kind: "".to_string() }
};
Template::render("gamenights", &data)
}
#[get("/")]
pub async fn index() -> Redirect {
Redirect::to(uri!(gamenights))
}
#[derive(Serialize, Deserialize, Debug)]
struct GameNightAddData {
post_url: String,
flash : FlashData
}
#[get("/gamenight/add")]
pub async fn add_game_night(flash: Option<FlashMessage<'_>>) -> Template {
let flash_data = match flash {
None => FlashData { has_data: false, message: "".to_string(), kind: "".to_string() },
Some(flash) => FlashData { has_data: true, message: flash.message().to_string(), kind: flash.kind().to_string() }
};
let data = GameNightAddData {
post_url: "/api/gamenight".to_string(),
flash: flash_data
};
Template::render("gamenight_add", &data)
}

5
templates/flash.html.hbs Normal file
View File

@ -0,0 +1,5 @@
{{#if has_data}}
<div>
<p>{{kind}}: {{message}}</p>
</div>
{{/if}}

View File

@ -0,0 +1,16 @@
<html>
<head>
</head>
<body>
{{> flash flash }}
<form action="{{post_url}}" method="post">
<label for="game">Game:</label><br>
<input type="text" id="game" name="game"><br>
<label for="datetime">Wanneer:</label><br>
<input type="text" id="datetime" name="datetime">
<input type="submit" value="Submit">
</form>
</body>
</html>

View File

@ -0,0 +1,14 @@
<html>
<head>
</head>
<body>
{{> flash flash }}
{{#each gamenights}}
<div>
<span>game: {{this.game}}</span>
<span>when: {{this.datetime}}</span>
</div>
{{/each}}
</body>
</html>