basic user profile pages

This commit is contained in:
Ilion Beyst 2022-07-24 15:15:09 +02:00
parent 4a582e8079
commit 33664eff2c
7 changed files with 105 additions and 11 deletions

View file

@ -57,14 +57,14 @@ pub fn create_user(credentials: &Credentials, conn: &PgConnection) -> QueryResul
.get_result::<User>(conn) .get_result::<User>(conn)
} }
pub fn find_user(username: &str, db_conn: &PgConnection) -> QueryResult<User> { pub fn find_user_by_name(username: &str, db_conn: &PgConnection) -> QueryResult<User> {
users::table users::table
.filter(users::username.eq(username)) .filter(users::username.eq(username))
.first::<User>(db_conn) .first::<User>(db_conn)
} }
pub fn authenticate_user(credentials: &Credentials, db_conn: &PgConnection) -> Option<User> { pub fn authenticate_user(credentials: &Credentials, db_conn: &PgConnection) -> Option<User> {
find_user(credentials.username, db_conn) find_user_by_name(credentials.username, db_conn)
.optional() .optional()
.unwrap() .unwrap()
.and_then(|user| { .and_then(|user| {

View file

@ -119,11 +119,11 @@ pub fn api() -> Router {
.route("/register", post(routes::users::register)) .route("/register", post(routes::users::register))
.route("/login", post(routes::users::login)) .route("/login", post(routes::users::login))
.route("/users/me", get(routes::users::current_user)) .route("/users/me", get(routes::users::current_user))
.route("/users/:user/bots", get(routes::bots::get_user_bots))
.route( .route(
"/bots", "/bots",
get(routes::bots::list_bots).post(routes::bots::create_bot), get(routes::bots::list_bots).post(routes::bots::create_bot),
) )
.route("/bots/my_bots", get(routes::bots::get_my_bots))
.route("/bots/:bot_id", get(routes::bots::get_bot)) .route("/bots/:bot_id", get(routes::bots::get_bot))
.route( .route(
"/bots/:bot_id/upload", "/bots/:bot_id/upload",

View file

@ -12,6 +12,7 @@ use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use thiserror; use thiserror;
use crate::db;
use crate::db::bots::{self, BotVersion}; use crate::db::bots::{self, BotVersion};
use crate::db::ratings::{self, RankedBot}; use crate::db::ratings::{self, RankedBot};
use crate::db::users::User; use crate::db::users::User;
@ -158,11 +159,13 @@ pub async fn get_bot(
}))) })))
} }
pub async fn get_my_bots( pub async fn get_user_bots(
conn: DatabaseConnection, conn: DatabaseConnection,
user: User, Path(user_name): Path<String>,
) -> Result<Json<Vec<Bot>>, StatusCode> { ) -> Result<Json<Vec<Bot>>, StatusCode> {
bots::find_bots_by_owner(user.id, &conn) let user =
db::users::find_user_by_name(&user_name, &conn).map_err(|_| StatusCode::NOT_FOUND)?;
db::bots::find_bots_by_owner(user.id, &conn)
.map(Json) .map(Json)
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
} }

View file

@ -89,7 +89,7 @@ impl RegistrationParams {
errors.push("password must be at least 8 characters".to_string()); errors.push("password must be at least 8 characters".to_string());
} }
if users::find_user(&self.username, &conn).is_ok() { if users::find_user_by_name(&self.username, &conn).is_ok() {
errors.push("username is already taken".to_string()); errors.push("username is already taken".to_string());
} }

View file

@ -44,7 +44,8 @@
<td class="leaderboard-bot">{entry["bot"]["name"]}</td> <td class="leaderboard-bot">{entry["bot"]["name"]}</td>
<td class="leaderboard-author"> <td class="leaderboard-author">
{#if entry["author"]} {#if entry["author"]}
{entry["author"]["username"]} <!-- TODO: remove duplication -->
<a href="/users/{entry["author"]["username"]}">{entry["author"]["username"]}</a>
{/if} {/if}
</td> </td>
</tr> </tr>
@ -69,4 +70,9 @@
.leaderboard-rank { .leaderboard-rank {
color: #333; color: #333;
} }
.leaderboard-author a{
text-decoration: none;
color: black;
}
</style> </style>

View file

@ -36,9 +36,9 @@
<div class="user-controls"> <div class="user-controls">
{#if $currentUser} {#if $currentUser}
<div class="current-user-name"> <a class="current-user-name" href="/users/{$currentUser["username"]}">
{$currentUser["username"]} {$currentUser["username"]}
</div> </a>
<div class="sign-out" on:click={signOut}>Sign out</div> <div class="sign-out" on:click={signOut}>Sign out</div>
{:else} {:else}
<a class="account-href" href="login">Sign in</a> <a class="account-href" href="login">Sign in</a>
@ -61,6 +61,7 @@
.current-user-name { .current-user-name {
@include navbar-item; @include navbar-item;
text-decoration: none;
color: #fff; color: #fff;
} }

View file

@ -0,0 +1,84 @@
<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`);
return {
props: {
userName,
bots: await userBotsResponse.json(),
},
};
// return {
// status: matchDataResponse.status,
// error: new Error("failed to load match"),
// };
}
</script>
<script lang="ts">
export let userName: string;
export let bots: object[];
</script>
<div class="container">
<div class="header">
<h1 class="user-name">{userName}</h1>
</div>
<h2>Bots</h2>
<ul class="bot-list">
{#each bots as bot}
<li class="bot">
<span class="bot-name">{bot['name']}</span>
</li>
{/each}
</ul>
</div>
<style lang="scss">
.container {
min-width: 600px;
max-width: 800px;
margin: 50px auto;
}
.header {
margin-bottom: 60px;
border-bottom: 1px solid black;
}
.user-name {
margin-bottom: .5em;
}
.bot-list {
list-style: none;
padding: 0;
}
$border-color: #d0d7de;
.bot {
display: block;
padding: 24px 0;
border-bottom: 1px solid $border-color;
}
.bot-name {
font-size: 20px;
font-weight: 400;
}
.bot:first-child {
border-top: 1px solid $border-color;
}
</style>