Skip to content

Commit

Permalink
Support comments and guests
Browse files Browse the repository at this point in the history
  • Loading branch information
aquarhead committed Sep 30, 2024
1 parent 4fede9c commit 0859cc4
Show file tree
Hide file tree
Showing 5 changed files with 261 additions and 16 deletions.
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ pulldown-cmark = "0.12"
cron-parser = "0.9"
chrono = "0.4"
chrono-tz = { version = "0.10", features = ["serde"] }
urlencoding = "2"

[profile.release]
opt-level = "s"
Expand Down
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
Register for you next game.

- TODO: schedule next games
161 changes: 159 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ struct Game {
// see Team struct comment
players: HashMap<PlayerID, bool>,
guests: Vec<String>,
// TODO: comments??
#[serde(default)]
comments: Vec<String>,
}

struct AppCtx {
Expand Down Expand Up @@ -70,6 +71,9 @@ async fn main(req: Request, env: Env, _: Context) -> Result<Response> {
.post_async("/team/:teamkey/new_game", new_game)
.post_async("/team/:teamkey/player/:playerid/play", play)
.post_async("/team/:teamkey/player/:playerid/not_play", not_play)
.post_async("/team/:teamkey/comment", add_comment)
.post_async("/team/:teamkey/guest", add_guest)
.post_async("/team/:teamkey/guest/:guest/delete", delete_guest)
.run(req, env)
.await
}
Expand Down Expand Up @@ -433,7 +437,6 @@ async fn team(_: Request, ctx: RouteContext<AppCtx>) -> Result<Response> {
options.insert(Options::ENABLE_GFM);
let parser = Parser::new_ext(&ng.description, options);

// Write to String buffer.
let mut html_output = String::new();
pulldown_cmark::html::push_html(&mut html_output, parser);
html_output
Expand All @@ -443,6 +446,7 @@ async fn team(_: Request, ctx: RouteContext<AppCtx>) -> Result<Response> {
.render(mjctx! {
team_name => team.name,
key,
ng_key,
ng,
description,
playing_count,
Expand Down Expand Up @@ -484,6 +488,7 @@ async fn new_game(req: Request, ctx: RouteContext<AppCtx>) -> Result<Response> {
description,
players: team.players.iter().map(|(k, _)| (k.to_string(), false)).collect(),
guests: Vec::new(),
comments: Vec::new(),
};

let ng_key = random::hex_string();
Expand Down Expand Up @@ -612,3 +617,155 @@ async fn not_play(req: Request, ctx: RouteContext<AppCtx>) -> Result<Response> {
Response::error("game not found", 404)
}
}

async fn add_comment(req: Request, ctx: RouteContext<AppCtx>) -> Result<Response> {
let key = ctx.param("teamkey").unwrap();

let teams_kv = ctx.kv("teams")?;
let games_kv = ctx.kv("games")?;

let team: Team = {
let t = teams_kv.get(key).text().await?;
if t.is_none() {
return Response::error("team not found", 404);
}
let t = t.unwrap();
serde_json::from_str(&t).unwrap()
};

if let Some(ng_key) = team.next_game {
let mut ng: Game = {
let g = games_kv.get(&ng_key).text().await?;
if g.is_none() {
return Response::error("game does not exist anymore", 404);
}
let g = g.unwrap();
serde_json::from_str(&g).unwrap()
};

let mut r = req.clone_mut()?;
let f = r.form_data().await?;
let comment = f.get_field("comment").unwrap_or_default();
if comment.len() == 0 {
return Response::error("comment can't be empty", 400);
}

ng.comments.push(comment);

match games_kv
.put(&ng_key, serde_json::to_string(&ng).unwrap())?
.execute()
.await
{
Ok(_) => {
let mut team_link = req.url()?.clone();
team_link.set_path(&format!("/team/{}", key));

Response::redirect(team_link)
}
Err(_) => Response::error("failed to set play", 500),
}
} else {
Response::error("game not found", 404)
}
}

async fn add_guest(req: Request, ctx: RouteContext<AppCtx>) -> Result<Response> {
let key = ctx.param("teamkey").unwrap();

let teams_kv = ctx.kv("teams")?;
let games_kv = ctx.kv("games")?;

let team: Team = {
let t = teams_kv.get(key).text().await?;
if t.is_none() {
return Response::error("team not found", 404);
}
let t = t.unwrap();
serde_json::from_str(&t).unwrap()
};

if let Some(ng_key) = team.next_game {
let mut ng: Game = {
let g = games_kv.get(&ng_key).text().await?;
if g.is_none() {
return Response::error("game does not exist anymore", 404);
}
let g = g.unwrap();
serde_json::from_str(&g).unwrap()
};

let mut r = req.clone_mut()?;
let f = r.form_data().await?;
let guests = f.get_field("guest_name").unwrap_or_default();
if guests.len() == 0 {
return Response::error("guest_name can't be empty", 400);
}

guests.trim().split(',').for_each(|g| ng.guests.push(g.to_string()));

match games_kv
.put(&ng_key, serde_json::to_string(&ng).unwrap())?
.execute()
.await
{
Ok(_) => {
let mut team_link = req.url()?.clone();
team_link.set_path(&format!("/team/{}", key));

Response::redirect(team_link)
}
Err(_) => Response::error("failed to set play", 500),
}
} else {
Response::error("game not found", 404)
}
}

async fn delete_guest(req: Request, ctx: RouteContext<AppCtx>) -> Result<Response> {
let key = ctx.param("teamkey").unwrap();

let teams_kv = ctx.kv("teams")?;
let games_kv = ctx.kv("games")?;

let team: Team = {
let t = teams_kv.get(key).text().await?;
if t.is_none() {
return Response::error("team not found", 404);
}
let t = t.unwrap();
serde_json::from_str(&t).unwrap()
};

if let Some(ng_key) = team.next_game {
let mut ng: Game = {
let g = games_kv.get(&ng_key).text().await?;
if g.is_none() {
return Response::error("game does not exist anymore", 404);
}
let g = g.unwrap();
serde_json::from_str(&g).unwrap()
};

let g = urlencoding::decode(ctx.param("guest").unwrap()).unwrap().to_string();

// TODO: only delete 1 guest
ng.guests = ng.guests.into_iter().filter(|gg| *gg != g).collect();

match games_kv
.put(&ng_key, serde_json::to_string(&ng).unwrap())?
.execute()
.await
{
Ok(_) => {
let mut team_link = req.url()?.clone();
team_link.set_path(&format!("/team/{}", key));

Response::redirect(team_link)
}
Err(_) => Response::error("failed to set play", 500),
}
} else {
Response::error("game not found", 404)
}
}
106 changes: 94 additions & 12 deletions templates/team.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,54 @@ <h1 class="title">

<section class="section">
<div class="container is-max-desktop">
<div class="content is-large">
<blockquote class="py-1">
{{ description|safe }}
</blockquote>
</div>

<div class="content">
<h2 class="title">
{{ playing_count }} players playing!
</h2>
<div class="columns">
<div class="column">
<div class="box">
<h3 class="title">{{ playing_count }} playing!</h3>
{% if ng.guests %}
<p class="subtitle">including {{ ng.guests|length }} guests</p>
{% endif %}
<div class="content has-background-light px-3 py-3">
<!-- <blockquote class="py-3"> -->
{{ description|safe }}
<!-- </blockquote> -->
</div>
</div>
</div>
<div class="column">
<div class="content">
<h3 class="title">Comments</h3>
{% for comment in ng.comments %}
<div class="box">{{ comment }}</div>
{% endfor %}
<form action="/team/{{ key }}/comment" method="post">
<div class="field">
<div class="control">
<textarea name="comment" class="textarea" rows="2"></textarea>
</div>
</div>
<div class="field">
<div class="control">
<button type="submit" class="button is-info">
Add comment
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

<div class="container is-max-desktop">
<div class="container is-max-desktop mt-2">
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
<colgroup>
<col span="1" style="width: 60%;">
<col span="1" style="width: 40%;">
</colgroup>
<thead>
<tr>
<th></th>
<th>Player</th>
<th>Click to toggle</th>
</tr>
</thead>
Expand Down Expand Up @@ -66,6 +96,58 @@ <h2 class="title">
</tbody>
</table>
</div>

<div class="container is-max-desktop mt-4">
<div class="columns">
<div class="column is-half">
<form action="/team/{{ key }}/guest" method="post">
<div class="field has-addons">
<div class="control is-expanded">
<input type="text" name="guest_name" class="input" placeholder="Guest name">
</div>
<div class="control">
<button type="submit" class="button is-primary">
Add guest
</button>
</div>
</div>
</form>
</div>
</div>
</div>

{% if ng.guests %}
<div class="container is-max-desktop mt-2">
<table class="table is-bordered is-striped is-hoverable is-fullwidth">
<colgroup>
<col span="1" style="width: 60%;">
<col span="1" style="width: 40%;">
</colgroup>
<thead>
<tr>
<th>Guest</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for name in ng.guests %}
<tr>
<th class="is-two-thirds" scope="row">
{{ name }}
</th>
<td>
<form action="/team/{{ key }}/guest/{{ name }}/delete" method="post">
<button type="submit" class="button is-warning">
Delete
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</section>

{% else %}
Expand Down

0 comments on commit 0859cc4

Please sign in to comment.