diff options
author | Ilion Beyst <ilion.beyst@gmail.com> | 2022-07-24 16:45:29 +0200 |
---|---|---|
committer | Ilion Beyst <ilion.beyst@gmail.com> | 2022-07-24 16:45:29 +0200 |
commit | ccfe86729e3a454e3fdf529abd7063ceb8fa859f (patch) | |
tree | 0c60dfbc76c1e2c2ba6c71a5201fde18690969f5 | |
parent | 33664eff2c93136658b7f863c95e1bfda91141ee (diff) | |
download | planetwars.dev-ccfe86729e3a454e3fdf529abd7063ceb8fa859f.tar.xz planetwars.dev-ccfe86729e3a454e3fdf529abd7063ceb8fa859f.zip |
add bot detail page
-rw-r--r-- | planetwars-server/src/db/users.rs | 6 | ||||
-rw-r--r-- | planetwars-server/src/lib.rs | 4 | ||||
-rw-r--r-- | planetwars-server/src/routes/bots.rs | 23 | ||||
-rw-r--r-- | web/pw-server/src/lib/components/Leaderboard.svelte | 14 | ||||
-rw-r--r-- | web/pw-server/src/lib/components/navbar/UserControls.svelte | 4 | ||||
-rw-r--r-- | web/pw-server/src/routes/bots/[bot_id].svelte | 74 | ||||
-rw-r--r-- | web/pw-server/src/routes/bots/[bot_name].svelte | 139 | ||||
-rw-r--r-- | web/pw-server/src/routes/users/[user_name].svelte | 25 |
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> |