aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIlion Beyst <ilion.beyst@gmail.com>2022-07-24 16:45:29 +0200
committerIlion Beyst <ilion.beyst@gmail.com>2022-07-24 16:45:29 +0200
commitccfe86729e3a454e3fdf529abd7063ceb8fa859f (patch)
tree0c60dfbc76c1e2c2ba6c71a5201fde18690969f5
parent33664eff2c93136658b7f863c95e1bfda91141ee (diff)
downloadplanetwars.dev-ccfe86729e3a454e3fdf529abd7063ceb8fa859f.tar.xz
planetwars.dev-ccfe86729e3a454e3fdf529abd7063ceb8fa859f.zip
add bot detail page
-rw-r--r--planetwars-server/src/db/users.rs6
-rw-r--r--planetwars-server/src/lib.rs4
-rw-r--r--planetwars-server/src/routes/bots.rs23
-rw-r--r--web/pw-server/src/lib/components/Leaderboard.svelte14
-rw-r--r--web/pw-server/src/lib/components/navbar/UserControls.svelte4
-rw-r--r--web/pw-server/src/routes/bots/[bot_id].svelte74
-rw-r--r--web/pw-server/src/routes/bots/[bot_name].svelte139
-rw-r--r--web/pw-server/src/routes/users/[user_name].svelte25
8 files changed, 185 insertions, 104 deletions
diff --git a/planetwars-server/src/db/users.rs b/planetwars-server/src/db/users.rs
index 1098204..ebb2268 100644
--- a/planetwars-server/src/db/users.rs
+++ b/planetwars-server/src/db/users.rs
@@ -57,6 +57,12 @@ pub fn create_user(credentials: &Credentials, conn: &PgConnection) -> QueryResul
.get_result::<User>(conn)
}
+pub fn find_user(user_id: i32, db_conn: &PgConnection) -> QueryResult<User> {
+ users::table
+ .filter(users::id.eq(user_id))
+ .first::<User>(db_conn)
+}
+
pub fn find_user_by_name(username: &str, db_conn: &PgConnection) -> QueryResult<User> {
users::table
.filter(users::username.eq(username))
diff --git a/planetwars-server/src/lib.rs b/planetwars-server/src/lib.rs
index ccf7cfc..3ad0c88 100644
--- a/planetwars-server/src/lib.rs
+++ b/planetwars-server/src/lib.rs
@@ -124,9 +124,9 @@ pub fn api() -> Router {
"/bots",
get(routes::bots::list_bots).post(routes::bots::create_bot),
)
- .route("/bots/:bot_id", get(routes::bots::get_bot))
+ .route("/bots/:bot_name", get(routes::bots::get_bot))
.route(
- "/bots/:bot_id/upload",
+ "/bots/:bot_name/upload",
post(routes::bots::upload_code_multipart),
)
.route("/matches", get(routes::matches::list_matches))
diff --git a/planetwars-server/src/routes/bots.rs b/planetwars-server/src/routes/bots.rs
index 896359c..8de479f 100644
--- a/planetwars-server/src/routes/bots.rs
+++ b/planetwars-server/src/routes/bots.rs
@@ -20,6 +20,8 @@ use crate::modules::bots::save_code_string;
use crate::{DatabaseConnection, GlobalConfig};
use bots::Bot;
+use super::users::UserData;
+
#[derive(Serialize, Deserialize, Debug)]
pub struct SaveBotParams {
pub bot_name: String,
@@ -148,14 +150,23 @@ pub async fn create_bot(
// TODO: handle errors
pub async fn get_bot(
conn: DatabaseConnection,
- Path(bot_id): Path<i32>,
+ Path(bot_name): Path<String>,
) -> Result<Json<JsonValue>, StatusCode> {
- let bot = bots::find_bot(bot_id, &conn).map_err(|_| StatusCode::NOT_FOUND)?;
- let bundles =
+ let bot = db::bots::find_bot_by_name(&bot_name, &conn).map_err(|_| StatusCode::NOT_FOUND)?;
+ let owner: Option<UserData> = match bot.owner_id {
+ Some(user_id) => {
+ let user = db::users::find_user(user_id, &conn)
+ .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
+ Some(user.into())
+ }
+ None => None,
+ };
+ let versions =
bots::find_bot_versions(bot.id, &conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
Ok(Json(json!({
"bot": bot,
- "bundles": bundles,
+ "owner": owner,
+ "versions": versions,
})))
}
@@ -187,13 +198,13 @@ pub async fn get_ranking(conn: DatabaseConnection) -> Result<Json<Vec<RankedBot>
pub async fn upload_code_multipart(
conn: DatabaseConnection,
user: User,
- Path(bot_id): Path<i32>,
+ Path(bot_name): Path<String>,
mut multipart: Multipart,
Extension(config): Extension<Arc<GlobalConfig>>,
) -> Result<Json<BotVersion>, StatusCode> {
let bots_dir = PathBuf::from(&config.bots_directory);
- let bot = bots::find_bot(bot_id, &conn).map_err(|_| StatusCode::NOT_FOUND)?;
+ let bot = bots::find_bot_by_name(&bot_name, &conn).map_err(|_| StatusCode::NOT_FOUND)?;
if Some(user.id) != bot.owner_id {
return Err(StatusCode::FORBIDDEN);
diff --git a/web/pw-server/src/lib/components/Leaderboard.svelte b/web/pw-server/src/lib/components/Leaderboard.svelte
index 8582198..d29d5d6 100644
--- a/web/pw-server/src/lib/components/Leaderboard.svelte
+++ b/web/pw-server/src/lib/components/Leaderboard.svelte
@@ -41,11 +41,17 @@
<td class="leaderboard-rating">
{formatRating(entry)}
</td>
- <td class="leaderboard-bot">{entry["bot"]["name"]}</td>
+ <td class="leaderboard-bot">
+ <a class="leaderboard-href" href="/bots/{entry['bot']['name']}"
+ >{entry["bot"]["name"]}
+ </a></td
+ >
<td class="leaderboard-author">
{#if entry["author"]}
- <!-- TODO: remove duplication -->
- <a href="/users/{entry["author"]["username"]}">{entry["author"]["username"]}</a>
+ <!-- TODO: remove duplication -->
+ <a class="leaderboard-href" href="/users/{entry['author']['username']}"
+ >{entry["author"]["username"]}</a
+ >
{/if}
</td>
</tr>
@@ -71,7 +77,7 @@
color: #333;
}
- .leaderboard-author a{
+ .leaderboard-href {
text-decoration: none;
color: black;
}
diff --git a/web/pw-server/src/lib/components/navbar/UserControls.svelte b/web/pw-server/src/lib/components/navbar/UserControls.svelte
index a9bd87b..5646982 100644
--- a/web/pw-server/src/lib/components/navbar/UserControls.svelte
+++ b/web/pw-server/src/lib/components/navbar/UserControls.svelte
@@ -36,8 +36,8 @@
<div class="user-controls">
{#if $currentUser}
- <a class="current-user-name" href="/users/{$currentUser["username"]}">
- {$currentUser["username"]}
+ <a class="current-user-name" href="/users/{$currentUser['username']}">
+ {$currentUser["username"]}
</a>
<div class="sign-out" on:click={signOut}>Sign out</div>
{:else}
diff --git a/web/pw-server/src/routes/bots/[bot_id].svelte b/web/pw-server/src/routes/bots/[bot_id].svelte
deleted file mode 100644
index 3eece10..0000000
--- a/web/pw-server/src/routes/bots/[bot_id].svelte
+++ /dev/null
@@ -1,74 +0,0 @@
-<script lang="ts" context="module">
- import { get_session_token } from "$lib/auth";
-
- export async function load({ page }) {
- const token = get_session_token();
- const res = await fetch(`/api/bots/${page.params["bot_id"]}`, {
- headers: {
- "Content-Type": "application/json",
- Authorization: `Bearer ${token}`,
- },
- });
-
- if (res.ok) {
- const data = await res.json();
- return {
- props: {
- bot: data["bot"],
- bundles: data["bundles"],
- },
- };
- }
-
- return {
- status: res.status,
- error: new Error("Could not load bot"),
- };
- }
-</script>
-
-<script lang="ts">
- import dayjs from "dayjs";
-
- export let bot: object;
- export let bundles: object[];
-
- let files;
-
- async function submitCode() {
- console.log("click");
- const token = get_session_token();
-
- const formData = new FormData();
- formData.append("File", files[0]);
-
- const res = await fetch(`/api/bots/${bot["id"]}/upload`, {
- method: "POST",
- headers: {
- // the content type header will be set by the browser
- Authorization: `Bearer ${token}`,
- },
- body: formData,
- });
-
- console.log(res.statusText);
- }
-</script>
-
-<div>
- {bot["name"]}
-</div>
-
-<div>Upload code</div>
-<form on:submit|preventDefault={submitCode}>
- <input type="file" bind:files />
- <button type="submit">Submit</button>
-</form>
-
-<ul>
- {#each bundles as bundle}
- <li>
- bundle created at {dayjs(bundle["created_at"]).format("YYYY-MM-DD HH:mm")}
- </li>
- {/each}
-</ul>
diff --git a/web/pw-server/src/routes/bots/[bot_name].svelte b/web/pw-server/src/routes/bots/[bot_name].svelte
new file mode 100644
index 0000000..9e9f016
--- /dev/null
+++ b/web/pw-server/src/routes/bots/[bot_name].svelte
@@ -0,0 +1,139 @@
+<script lang="ts" context="module">
+ import { get_session_token } from "$lib/auth";
+
+ export async function load({ params, fetch }) {
+ const token = get_session_token();
+ const res = await fetch(`/api/bots/${params["bot_name"]}`, {
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ if (res.ok) {
+ const { bot, owner, versions } = await res.json();
+ // sort most recent first
+ versions.sort((a: string, b: string) =>
+ dayjs(a["created_at"]).isAfter(b["created_at"]) ? -1 : 1
+ );
+ return {
+ props: {
+ bot,
+ owner,
+ versions,
+ },
+ };
+ }
+
+ return {
+ status: res.status,
+ error: new Error("Could not find bot"),
+ };
+ }
+</script>
+
+<script lang="ts">
+ import dayjs from "dayjs";
+
+ import { currentUser } from "$lib/stores/current_user";
+
+ export let bot: object;
+ export let owner: object;
+ export let versions: object[];
+
+ // function last_updated() {
+ // versions.sort()
+ // }
+
+ // let files;
+
+ // async function submitCode() {
+ // console.log("click");
+ // const token = get_session_token();
+
+ // const formData = new FormData();
+ // formData.append("File", files[0]);
+
+ // const res = await fetch(`/api/bots/${bot["id"]}/upload`, {
+ // method: "POST",
+ // headers: {
+ // // the content type header will be set by the browser
+ // Authorization: `Bearer ${token}`,
+ // },
+ // body: formData,
+ // });
+
+ // console.log(res.statusText);
+ // }
+</script>
+
+<!--
+<div>Upload code</div>
+<form on:submit|preventDefault={submitCode}>
+ <input type="file" bind:files />
+ <button type="submit">Submit</button>
+</form> -->
+
+<div class="container">
+ <div class="header">
+ <h1 class="bot-name">{bot["name"]}</h1>
+ {#if owner}
+ <a class="owner-name" href="/users/{owner['username']}">
+ {owner["username"]}
+ </a>
+ {/if}
+ </div>
+
+ {#if $currentUser && $currentUser["user_id"] === bot["owner_id"]}
+ <div>
+ <!-- TODO: can we avoid hardcoding the url? -->
+ Publish a new version by pushing a docker container to
+ <code>registry.planetwars.dev/{bot["name"]}:latest</code>, or using the web editor.
+ </div>
+ {/if}
+
+ <div class="versions">
+ <h4>Versions</h4>
+ <ul class="version-list">
+ {#each versions as version}
+ <li>
+ {dayjs(version["created_at"]).format("YYYY-MM-DD HH:mm")}
+ </li>
+ {/each}
+ </ul>
+ </div>
+</div>
+
+<style lang="scss">
+ .container {
+ width: 800px;
+ max-width: 80%;
+ margin: 50px auto;
+ }
+
+ .header {
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-end;
+ margin-bottom: 60px;
+ border-bottom: 1px solid black;
+ }
+
+ $header-space-above-line: 12px;
+
+ .bot-name {
+ font-size: 24pt;
+ margin-bottom: $header-space-above-line;
+ }
+
+ .owner-name {
+ font-size: 14pt;
+ text-decoration: none;
+ color: #333;
+ margin-bottom: $header-space-above-line;
+ }
+
+ .versions {
+ margin: 30px 0;
+ }
+</style>
diff --git a/web/pw-server/src/routes/users/[user_name].svelte b/web/pw-server/src/routes/users/[user_name].svelte
index fab3a96..a1801f4 100644
--- a/web/pw-server/src/routes/users/[user_name].svelte
+++ b/web/pw-server/src/routes/users/[user_name].svelte
@@ -1,12 +1,4 @@
<script lang="ts" context="module">
- function fetchJson(url: string): Promise<Response> {
- return fetch(url, {
- headers: {
- "Content-Type": "application/json",
- },
- });
- }
-
export async function load({ params, fetch }) {
const userName = params["user_name"];
const userBotsResponse = await fetch(`/api/users/${userName}/bots`);
@@ -36,17 +28,17 @@
<h2>Bots</h2>
<ul class="bot-list">
{#each bots as bot}
- <li class="bot">
- <span class="bot-name">{bot['name']}</span>
- </li>
+ <li class="bot">
+ <a class="bot-name" href="/bots/{bot['name']}">{bot["name"]}</a>
+ </li>
{/each}
</ul>
</div>
<style lang="scss">
.container {
- min-width: 600px;
- max-width: 800px;
+ width: 800px;
+ max-width: 80%;
margin: 50px auto;
}
@@ -56,7 +48,7 @@
}
.user-name {
- margin-bottom: .5em;
+ margin-bottom: 0.5em;
}
.bot-list {
@@ -75,10 +67,11 @@
.bot-name {
font-size: 20px;
font-weight: 400;
+ text-decoration: none;
+ color: black;
}
.bot:first-child {
border-top: 1px solid $border-color;
}
-
-</style> \ No newline at end of file
+</style>