From e7f4cf0c0f1647c3c8e3e9f469ba8cd1324c26bc Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 10 Sep 2022 02:11:48 +0100 Subject: [PATCH] Add crate releases heatmap to user page --- chartered-frontend/package-lock.json | 11 +++++++++++ chartered-frontend/package.json | 3 ++- chartered-db/src/users.rs | 43 +++++++++++++++++++++++++++++++++++++++++++ chartered-frontend/src/app.pcss | 4 ++++ chartered-frontend/src/components/Heatmap.svelte | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ chartered-web/src/endpoints/web_api/users/heatmap.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ chartered-web/src/endpoints/web_api/users/mod.rs | 2 ++ chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 8 files changed, 212 insertions(+), 25 deletions(-) diff --git a/chartered-frontend/package-lock.json b/chartered-frontend/package-lock.json index 3effc35..70cc92b 100644 --- a/chartered-frontend/package-lock.json +++ a/chartered-frontend/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.1", "dependencies": { "lodash": "^4.17.21", + "svelte-heatmap": "^1.0.2", "svelte-markdown": "^0.2.3" }, "devDependencies": { @@ -3129,6 +3130,11 @@ "svelte": "^3.24.0" } }, + "node_modules/svelte-heatmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/svelte-heatmap/-/svelte-heatmap-1.0.2.tgz", + "integrity": "sha512-qVsJj/Y/FQCjYJ2A7QU/v87FjGFjWqrTIb+qPumrudzH5w6D5WoubHF7Q2g38naJry4ADO1I0K29UuC86t5lJw==" + }, "node_modules/svelte-hmr": { "version": "0.14.12", "resolved": "https://registry.npmjs.org/svelte-hmr/-/svelte-hmr-0.14.12.tgz", @@ -5593,6 +5599,11 @@ "svelte-preprocess": "^4.0.0", "typescript": "*" } + }, + "svelte-heatmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/svelte-heatmap/-/svelte-heatmap-1.0.2.tgz", + "integrity": "sha512-qVsJj/Y/FQCjYJ2A7QU/v87FjGFjWqrTIb+qPumrudzH5w6D5WoubHF7Q2g38naJry4ADO1I0K29UuC86t5lJw==" }, "svelte-hmr": { "version": "0.14.12", diff --git a/chartered-frontend/package.json b/chartered-frontend/package.json index b178879..79fec28 100644 --- a/chartered-frontend/package.json +++ a/chartered-frontend/package.json @@ -14,8 +14,8 @@ }, "devDependencies": { "@playwright/test": "^1.25.0", - "@sveltejs/kit": "next", "@sveltejs/adapter-static": "^1.0.0-next.42", + "@sveltejs/kit": "next", "@tailwindcss/forms": "^0.5.3", "@tailwindcss/typography": "^0.5.7", "@types/feather-icons": "^4.7.0", @@ -43,6 +43,7 @@ "type": "module", "dependencies": { "lodash": "^4.17.21", + "svelte-heatmap": "^1.0.2", "svelte-markdown": "^0.2.3" } } diff --git a/chartered-db/src/users.rs b/chartered-db/src/users.rs index 994e3ed..9ec1969 100644 --- a/chartered-db/src/users.rs +++ a/chartered-db/src/users.rs @@ -5,9 +5,13 @@ uuid::SqlUuid, ConnectionPool, Error, Result, }; -use diesel::result::DatabaseErrorKind; +use chrono::{Duration, NaiveDateTime, Utc}; use diesel::{ - insert_into, prelude::*, result::Error as DieselError, Associations, Identifiable, Queryable, + dsl::sql, + insert_into, + prelude::*, + result::{DatabaseErrorKind, Error as DieselError}, + Associations, Identifiable, Queryable, }; use rand::{thread_rng, Rng}; use std::sync::Arc; @@ -313,6 +317,41 @@ .as_ref() .or(self.name.as_ref()) .unwrap_or(&self.username) + } + + pub async fn published_versions_by_date( + &self, + conn: ConnectionPool, + ) -> Result> { + let id = self.id; + + tokio::task::spawn_blocking(move || { + use crate::schema::crate_versions::dsl::{created_at, user_id}; + + let conn = conn.get()?; + + let date_statement = if cfg!(feature = "sqlite") { + "strftime('%Y-%m-%d 00:00:00', created_at)" + } else { + "DATE_TRUNC('day', created_at)" + }; + + let selected = crate::schema::crate_versions::table + .group_by(sql::(date_statement)) + .select(( + sql::(date_statement), + sql::("count(*)"), + )) + .filter( + user_id + .eq(id) + .and(created_at.gt((Utc::now() - Duration::days(365)).naive_utc())), + ) + .load(&conn)?; + + Ok(selected) + }) + .await? } } diff --git a/chartered-frontend/src/app.pcss b/chartered-frontend/src/app.pcss index 7e4634c..590601d 100644 --- a/chartered-frontend/src/app.pcss +++ a/chartered-frontend/src/app.pcss @@ -6,6 +6,10 @@ @apply bg-blue-100 dark:bg-slate-800 text-black dark:text-slate-400; } +.heatmap svg { + @apply overflow-visible mt-2; +} + @layer components { .card { @apply block p-6 bg-white rounded-lg border border-gray-200 dark:border-gray-700 shadow-md dark:bg-transparent; diff --git a/chartered-frontend/src/components/Heatmap.svelte b/chartered-frontend/src/components/Heatmap.svelte new file mode 100644 index 0000000..0b6a45c 100644 --- /dev/null +++ a/chartered-frontend/src/components/Heatmap.svelte @@ -1,0 +1,61 @@ + + +
+
+ +
+ + +
+ +
+
+ + diff --git a/chartered-web/src/endpoints/web_api/users/heatmap.rs b/chartered-web/src/endpoints/web_api/users/heatmap.rs new file mode 100644 index 0000000..c97ac4a 100644 --- /dev/null +++ a/chartered-web/src/endpoints/web_api/users/heatmap.rs @@ -1,0 +1,47 @@ +use axum::{extract, Json}; +use chartered_db::users::User; +use chartered_db::ConnectionPool; +use chrono::NaiveDate; +use std::collections::HashMap; +use thiserror::Error; + +pub async fn handle( + extract::Path((_session_key, uuid)): extract::Path<(String, chartered_db::uuid::Uuid)>, + extract::Extension(db): extract::Extension, +) -> Result, Error> { + let user = User::find_by_uuid(db.clone(), uuid) + .await? + .ok_or(Error::NotFound)?; + + let res = user + .published_versions_by_date(db) + .await? + .into_iter() + .map(|(k, v)| (k.date(), v)) + .collect(); + + Ok(Json(res)) +} + +pub type Response = HashMap; + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Database(#[from] chartered_db::Error), + #[error("User doesn't exist")] + NotFound, +} + +impl Error { + pub fn status_code(&self) -> axum::http::StatusCode { + use axum::http::StatusCode; + + match self { + Self::Database(_) => StatusCode::INTERNAL_SERVER_ERROR, + Self::NotFound => StatusCode::NOT_FOUND, + } + } +} + +define_error_response!(Error); diff --git a/chartered-web/src/endpoints/web_api/users/mod.rs b/chartered-web/src/endpoints/web_api/users/mod.rs index 271d873..eca9e4b 100644 --- a/chartered-web/src/endpoints/web_api/users/mod.rs +++ a/chartered-web/src/endpoints/web_api/users/mod.rs @@ -1,3 +1,4 @@ +mod heatmap; mod info; mod search; @@ -7,4 +8,5 @@ Router::new() .route("/search", get(search::handle)) .route("/info/:uuid", get(info::handle)) + .route("/info/:uuid/heatmap", get(heatmap::handle)) } diff --git a/chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte b/chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte index d0e39ea..6f374c2 100644 --- a/chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte +++ a/chartered-frontend/src/routes/(authed)/users/[uuid]/+page.svelte @@ -1,31 +1,41 @@ -{#await userPromise} -
-
- -
-
-{:then user} -
-
-
+
+
+
+ {#await userPromise} +
+ +
+ {:then user}

{user.displayName}

@@ -55,9 +65,15 @@ Email {/if} -
+ {/await} +
-
+
+ {#await userPromise} +
+ +
+ {:then user} {#if user.picture_url} {user.displayName} {:else} @@ -67,7 +83,13 @@
{/if} -
+ {/await}
-
-{/await} + + + +
+
+ +
+
-- rgit 0.1.3