Skip to content

Commit 30ea9f2

Browse files
committed
fix seeding + better projects page
1 parent 5702215 commit 30ea9f2

File tree

8 files changed

+83
-64
lines changed

8 files changed

+83
-64
lines changed

frontend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "rustytime",
33
"private": false,
4-
"version": "0.2.1",
4+
"version": "0.2.2",
55
"license": "AGPL-3.0",
66
"type": "module",
77
"repository": {

frontend/src/routes/projects/+page.svelte

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55
import LucideGithub from '~icons/lucide/github';
66
import LucideExternalLink from '~icons/lucide/external-link';
77
import StatCard from '$lib/components/ui/StatCard.svelte';
8-
import { formatDuration, formatRelativeTime, creationDateFormatter } from '$lib/utils/time';
8+
import { formatRelativeTime, creationDateFormatter } from '$lib/utils/time';
99
1010
interface Props {
1111
data: PageData;
1212
}
1313
1414
type EnhancedProject = Project & {
1515
createdAtFormatted: string;
16-
createdAtRelative: string | null;
16+
lastUpdated: string | null;
1717
repoLabel: string | null;
1818
};
1919
@@ -35,32 +35,45 @@
3535
}
3636
};
3737
38-
const formattedProjects = $derived.by((): EnhancedProject[] => {
38+
const sortedProjects = $derived.by((): Project[] => {
3939
if (!projectsData?.projects) {
4040
return [];
4141
}
4242
43-
return projectsData.projects.map((project) => {
43+
return [...projectsData.projects].sort((a, b) => {
44+
const dateA = a.updated_at ? new Date(a.updated_at).getTime() : 0;
45+
const dateB = b.updated_at ? new Date(b.updated_at).getTime() : 0;
46+
return dateB - dateA;
47+
});
48+
});
49+
50+
const formattedProjects = $derived.by((): EnhancedProject[] => {
51+
if (!sortedProjects.length) {
52+
return [];
53+
}
54+
55+
return sortedProjects.map((project) => {
4456
const createdDate = project.created_at ? new Date(project.created_at) : null;
45-
const isValidDate = createdDate && !Number.isNaN(createdDate.getTime());
57+
const updatedDate = project.updated_at ? new Date(project.updated_at) : null;
58+
const isCreatedAtValid = createdDate && !Number.isNaN(createdDate.getTime());
59+
const isUpdatedAtValid = updatedDate && !Number.isNaN(updatedDate.getTime());
4660
4761
return {
4862
...project,
4963
createdAtFormatted:
50-
isValidDate && createdDate ? creationDateFormatter.format(createdDate) : 'Unknown',
51-
createdAtRelative: isValidDate && createdDate ? formatRelativeTime(createdDate) : null,
64+
isCreatedAtValid && createdDate ? creationDateFormatter.format(createdDate) : 'Unknown',
65+
lastUpdated: isUpdatedAtValid && updatedDate ? formatRelativeTime(updatedDate) : null,
5266
repoLabel: project.repo_url ? formatRepoLabel(project.repo_url) : null
5367
} satisfies EnhancedProject;
5468
});
5569
});
5670
57-
const totalTrackedSeconds = $derived(
58-
formattedProjects.reduce(
59-
(accumulator, project) => accumulator + (project.total_seconds ?? 0),
60-
0
61-
)
62-
);
63-
const totalTrackedTime = $derived(formatDuration(totalTrackedSeconds));
71+
const lastUpdatedProject = $derived.by<EnhancedProject | null>(() => formattedProjects[0] ?? null);
72+
const lastUpdatedProjectLabel = $derived.by<string>(() => {
73+
const project = formattedProjects[0];
74+
return project?.lastUpdated ? `Updated ${project.lastUpdated}` : 'Awaiting activity';
75+
});
76+
6477
const projectCount = $derived(formattedProjects.length);
6578
const repoCount = $derived(
6679
formattedProjects.filter((project) => Boolean(project.repo_url)).length
@@ -79,13 +92,20 @@
7992
<!-- Project Statistics -->
8093
{#if formattedProjects.length}
8194
<div class="grid grid-cols-1 gap-4 mb-8 sm:grid-cols-2">
82-
<StatCard title="Total tracked time" value={totalTrackedTime} />
83-
8495
<StatCard
8596
title="Total projects"
8697
value={projectCount}
87-
subvalue="({repoCount} with repositories)"
98+
subvalue="{repoCount} with repositories"
8899
/>
100+
101+
{#if lastUpdatedProject}
102+
<StatCard
103+
title="Last updated project"
104+
value={lastUpdatedProject.name ?? 'Unknown project'}
105+
subvalue={lastUpdatedProjectLabel}
106+
/>
107+
{/if}
108+
89109
</div>
90110

91111
<!-- Project List -->
@@ -125,8 +145,8 @@
125145
<span class="text-sm font-semibold text-ctp-subtext0"
126146
>{project.createdAtFormatted}</span
127147
>
128-
{#if project.createdAtRelative}
129-
<span class="text-xs text-ctp-overlay1">{project.createdAtRelative}</span>
148+
{#if project.lastUpdated}
149+
<span class="text-xs text-ctp-overlay1">Last updated {project.lastUpdated}</span>
130150
{/if}
131151
</div>
132152
</div>

rustytime/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rustytime/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[package]
22
name = "rustytime-server"
33
description = "🕒 blazingly fast time tracking for developers"
4-
version = "0.5.1"
4+
version = "0.5.2"
55
edition = "2024"
66
authors = ["ImShyMike"]
77
readme = "../README.md"

rustytime/src/db/seed.rs

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#![cfg(feature = "seed")]
22

3+
use crate::db::connection::DbPool;
4+
use crate::handlers::user::store_heartbeats_in_db;
35
use crate::models::heartbeat::{NewHeartbeat, SourceType};
46
use crate::models::user::User;
5-
use crate::schema::heartbeats;
67
use chrono::Utc;
7-
use diesel::prelude::*;
88
use ipnetwork::{IpNetwork, Ipv4Network};
9-
use rand::Rng;
109
use rand::prelude::IndexedRandom;
10+
use rand::Rng;
1111
use std::net::Ipv4Addr;
1212
use tracing::{info, warn};
1313

@@ -29,41 +29,38 @@ struct HeartbeatParams<'a> {
2929
user_agent: &'a str,
3030
}
3131

32-
pub fn seed_database(conn: &mut PgConnection) -> Result<(), Box<dyn std::error::Error>> {
32+
pub async fn seed_database(pool: &DbPool) -> Result<(), Box<dyn std::error::Error>> {
3333
info!("🔄 Starting database seeding...");
3434

35-
let Ok(user) = create_dummy_user(conn) else {
36-
warn!("⚠️ Dummy user already exists, skipping seeding.");
37-
return Ok(());
35+
let user = {
36+
let mut conn = pool.get()?;
37+
38+
if let Some(existing_user) = User::find_by_github_id(&mut conn, -1)? {
39+
warn!("⚠️ Dummy user already exists (id: {}), skipping seeding.", existing_user.id);
40+
return Ok(());
41+
}
42+
43+
User::create_or_update(
44+
&mut conn,
45+
-1,
46+
"Test User",
47+
"https://avatars.githubusercontent.com/u/999999",
48+
)?
3849
};
50+
3951
info!(
4052
"✅ Created dummy user: {} (API Key: {})",
4153
user.name, user.api_key
4254
);
4355

44-
generate_random_heartbeats(conn, user.id, TOTAL_HEARTBEATS)?;
56+
generate_random_heartbeats(pool, user.id, TOTAL_HEARTBEATS).await?;
4557

4658
info!("✅ Database seeding completed successfully!");
4759
Ok(())
4860
}
49-
50-
fn create_dummy_user(conn: &mut PgConnection) -> Result<User, Box<dyn std::error::Error>> {
51-
if User::find_by_github_id(conn, -1)?.is_some() {
52-
return Err("Dummy user already exists".into());
53-
}
54-
55-
let user = User::create_or_update(
56-
conn,
57-
-1,
58-
"Test User",
59-
"https://avatars.githubusercontent.com/u/999999",
60-
)?;
61-
62-
Ok(user)
63-
}
64-
65-
fn generate_random_heartbeats(
66-
conn: &mut PgConnection,
61+
62+
async fn generate_random_heartbeats(
63+
pool: &DbPool,
6764
user_id: i32,
6865
count: usize,
6966
) -> Result<(), Box<dyn std::error::Error>> {
@@ -81,18 +78,25 @@ fn generate_random_heartbeats(
8178
user_agent: USER_AGENT,
8279
};
8380

84-
let mut heartbeats = Vec::with_capacity(count);
81+
let mut batch = Vec::with_capacity(BATCH_SIZE);
82+
8583
for _ in 0..count {
8684
let heartbeat = generate_random_heartbeat(&mut rng, user_id, &params);
87-
heartbeats.push(heartbeat);
85+
batch.push(heartbeat);
86+
87+
if batch.len() == BATCH_SIZE {
88+
let current_batch: Vec<_> = batch.drain(..).collect();
89+
store_heartbeats_in_db(pool, current_batch).await?;
90+
}
8891
}
8992

90-
for batch in heartbeats.chunks(BATCH_SIZE) {
91-
diesel::insert_into(heartbeats::table)
92-
.values(batch)
93-
.execute(conn)?;
93+
if !batch.is_empty() {
94+
let current_batch: Vec<_> = batch.drain(..).collect();
95+
store_heartbeats_in_db(pool, current_batch).await?;
9496
}
9597

98+
info!("✅ Inserted {} heartbeats into the database", count);
99+
96100
Ok(())
97101
}
98102

@@ -137,7 +141,7 @@ fn generate_random_heartbeat<R: Rng>(
137141
is_write: Some(rng.random_bool(0.5)),
138142
editor: Some("vscode".to_string()),
139143
operating_system: Some("linux".to_string()),
140-
machine: "test-machine".to_string(),
144+
machine: Some("test-machine".to_string()),
141145
user_agent: params.user_agent.to_string(),
142146
lines: Some(rng.random_range(1..=1000)),
143147
project_root_count: None,
@@ -147,5 +151,6 @@ fn generate_random_heartbeat<R: Rng>(
147151
lineno: Some(rng.random_range(1..=100)),
148152
cursorpos: Some(rng.random_range(0..500)),
149153
source_type: SourceType::SEEDING.to_string().into(),
154+
project_id: None,
150155
}
151156
}

rustytime/src/handlers/projects.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ pub async fn projects_dashboard(
6060
created_at: proj.created_at,
6161
updated_at: proj.updated_at,
6262
total_seconds: time,
63-
human_readable_total: human_readable_duration(time, TimeFormat::HourMinute)
64-
.human_readable,
63+
human_readable_total: human_readable_duration(time, TimeFormat::NoDays).human_readable,
6564
})
6665
.collect();
6766

rustytime/src/handlers/user.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ pub async fn get_statusbar_today(
209209
}
210210

211211
/// Store heartbeats in the database
212-
async fn store_heartbeats_in_db(
212+
pub async fn store_heartbeats_in_db(
213213
pool: &DbPool,
214214
new_heartbeats: Vec<NewHeartbeat>,
215215
) -> Result<Vec<Heartbeat>, diesel::result::Error> {

rustytime/src/main.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,7 @@ async fn main() {
5050
// seed database if enabled
5151
#[cfg(feature = "seed")]
5252
{
53-
let mut conn = pool.get().unwrap_or_else(|e| {
54-
error!("❌ Failed to get database connection for seeding: {}", e);
55-
std::process::exit(1);
56-
});
57-
58-
let result = db::seed::seed_database(&mut conn);
53+
let result = db::seed::seed_database(&pool).await;
5954
match result {
6055
Ok(_) => info!("✅ Database seeding completed"),
6156
Err(e) => {

0 commit comments

Comments
 (0)