feat: show user avatars on /users page
All checks were successful
CI / Check / Test / Build (push) Successful in 24m46s
All checks were successful
CI / Check / Test / Build (push) Successful in 24m46s
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT u.id AS \"id!: String\",\n u.email AS \"email!: String\",\n COUNT(DISTINCT r.movie_id) AS \"total_movies!: i64\",\n AVG(CAST(r.rating AS REAL)) AS avg_rating\n FROM users u\n LEFT JOIN reviews r ON r.user_id = u.id AND r.remote_actor_url IS NULL\n GROUP BY u.id, u.email\n ORDER BY u.email ASC",
|
"query": "SELECT u.id AS \"id!: String\",\n u.email AS \"email!: String\",\n COUNT(DISTINCT r.movie_id) AS \"total_movies!: i64\",\n AVG(CAST(r.rating AS REAL)) AS avg_rating,\n u.avatar_path\n FROM users u\n LEFT JOIN reviews r ON r.user_id = u.id AND r.remote_actor_url IS NULL\n GROUP BY u.id, u.email, u.avatar_path\n ORDER BY u.email ASC",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -22,6 +22,11 @@
|
|||||||
"name": "avg_rating",
|
"name": "avg_rating",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Float"
|
"type_info": "Float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "avatar_path",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -31,8 +36,9 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
true
|
true
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "2c7353f34c4748d4d4be6abbf343fa7ea30eeb985c4bfd12b0fc3997d1ba03bb"
|
"hash": "771236034946abb52809203ba9a1259900e343174cf3f435fc18d9e689deb187"
|
||||||
}
|
}
|
||||||
@@ -229,6 +229,7 @@ pub(crate) struct UserSummaryRow {
|
|||||||
pub email: String,
|
pub email: String,
|
||||||
pub total_movies: i64,
|
pub total_movies: i64,
|
||||||
pub avg_rating: Option<f64>,
|
pub avg_rating: Option<f64>,
|
||||||
|
pub avatar_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserSummaryRow {
|
impl UserSummaryRow {
|
||||||
@@ -238,6 +239,7 @@ impl UserSummaryRow {
|
|||||||
Email::new(self.email)?,
|
Email::new(self.email)?,
|
||||||
self.total_movies,
|
self.total_movies,
|
||||||
self.avg_rating,
|
self.avg_rating,
|
||||||
|
self.avatar_path,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -270,10 +270,11 @@ impl UserRepository for PostgresUserRepository {
|
|||||||
sqlx::query_as::<_, UserSummaryRow>(
|
sqlx::query_as::<_, UserSummaryRow>(
|
||||||
r#"SELECT u.id, u.email,
|
r#"SELECT u.id, u.email,
|
||||||
COUNT(DISTINCT r.movie_id) AS total_movies,
|
COUNT(DISTINCT r.movie_id) AS total_movies,
|
||||||
AVG(r.rating::float) AS avg_rating
|
AVG(r.rating::float) AS avg_rating,
|
||||||
|
u.avatar_path
|
||||||
FROM users u
|
FROM users u
|
||||||
LEFT JOIN reviews r ON r.user_id = u.id AND r.remote_actor_url IS NULL
|
LEFT JOIN reviews r ON r.user_id = u.id AND r.remote_actor_url IS NULL
|
||||||
GROUP BY u.id, u.email
|
GROUP BY u.id, u.email, u.avatar_path
|
||||||
ORDER BY u.email ASC"#,
|
ORDER BY u.email ASC"#,
|
||||||
)
|
)
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ pub(crate) struct UserSummaryRow {
|
|||||||
pub email: String,
|
pub email: String,
|
||||||
pub total_movies: i64,
|
pub total_movies: i64,
|
||||||
pub avg_rating: Option<f64>,
|
pub avg_rating: Option<f64>,
|
||||||
|
pub avatar_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserSummaryRow {
|
impl UserSummaryRow {
|
||||||
@@ -249,6 +250,7 @@ impl UserSummaryRow {
|
|||||||
Email::new(self.email)?,
|
Email::new(self.email)?,
|
||||||
self.total_movies,
|
self.total_movies,
|
||||||
self.avg_rating,
|
self.avg_rating,
|
||||||
|
self.avatar_path,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -229,10 +229,11 @@ impl UserRepository for SqliteUserRepository {
|
|||||||
r#"SELECT u.id AS "id!: String",
|
r#"SELECT u.id AS "id!: String",
|
||||||
u.email AS "email!: String",
|
u.email AS "email!: String",
|
||||||
COUNT(DISTINCT r.movie_id) AS "total_movies!: i64",
|
COUNT(DISTINCT r.movie_id) AS "total_movies!: i64",
|
||||||
AVG(CAST(r.rating AS REAL)) AS avg_rating
|
AVG(CAST(r.rating AS REAL)) AS avg_rating,
|
||||||
|
u.avatar_path
|
||||||
FROM users u
|
FROM users u
|
||||||
LEFT JOIN reviews r ON r.user_id = u.id AND r.remote_actor_url IS NULL
|
LEFT JOIN reviews r ON r.user_id = u.id AND r.remote_actor_url IS NULL
|
||||||
GROUP BY u.id, u.email
|
GROUP BY u.id, u.email, u.avatar_path
|
||||||
ORDER BY u.email ASC"#
|
ORDER BY u.email ASC"#
|
||||||
)
|
)
|
||||||
.fetch_all(&self.pool)
|
.fetch_all(&self.pool)
|
||||||
|
|||||||
@@ -170,6 +170,7 @@ struct UserSummaryView {
|
|||||||
initial: char,
|
initial: char,
|
||||||
avg_rating_display: String,
|
avg_rating_display: String,
|
||||||
total_movies: i64,
|
total_movies: i64,
|
||||||
|
avatar_url: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Template)]
|
#[derive(Template)]
|
||||||
@@ -495,6 +496,7 @@ impl HtmlRenderer for AskamaHtmlRenderer {
|
|||||||
initial,
|
initial,
|
||||||
avg_rating_display,
|
avg_rating_display,
|
||||||
total_movies: u.total_movies,
|
total_movies: u.total_movies,
|
||||||
|
avatar_url: u.avatar_path.as_ref().map(|p| format!("/images/{}", p)),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
@@ -12,7 +12,11 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% for user in users %}
|
{% for user in users %}
|
||||||
<div class="user-row">
|
<div class="user-row">
|
||||||
|
{% if let Some(url) = &user.avatar_url %}
|
||||||
|
<img class="user-avatar user-avatar-img" src="{{ url }}" alt="{{ user.initial }}" style="width:40px;height:40px;border-radius:50%;object-fit:cover;">
|
||||||
|
{% else %}
|
||||||
<div class="user-avatar">{{ user.initial }}</div>
|
<div class="user-avatar">{{ user.initial }}</div>
|
||||||
|
{% endif %}
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<div class="user-name">{{ user.display_name }}</div>
|
<div class="user-name">{{ user.display_name }}</div>
|
||||||
<div class="user-meta">{{ user.total_movies }} movies · avg {{ user.avg_rating_display }}★</div>
|
<div class="user-meta">{{ user.total_movies }} movies · avg {{ user.avg_rating_display }}★</div>
|
||||||
|
|||||||
@@ -461,15 +461,23 @@ pub struct UserSummary {
|
|||||||
email: Email,
|
email: Email,
|
||||||
pub total_movies: i64,
|
pub total_movies: i64,
|
||||||
pub avg_rating: Option<f64>,
|
pub avg_rating: Option<f64>,
|
||||||
|
pub avatar_path: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserSummary {
|
impl UserSummary {
|
||||||
pub fn new(user_id: UserId, email: Email, total_movies: i64, avg_rating: Option<f64>) -> Self {
|
pub fn new(
|
||||||
|
user_id: UserId,
|
||||||
|
email: Email,
|
||||||
|
total_movies: i64,
|
||||||
|
avg_rating: Option<f64>,
|
||||||
|
avatar_path: Option<String>,
|
||||||
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
user_id,
|
user_id,
|
||||||
email,
|
email,
|
||||||
total_movies,
|
total_movies,
|
||||||
avg_rating,
|
avg_rating,
|
||||||
|
avatar_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn email(&self) -> &str {
|
pub fn email(&self) -> &str {
|
||||||
|
|||||||
Reference in New Issue
Block a user