commit 5be90e38ef14a2134b181bb9d8e12e8088e8719b
parent 4b95f59b077a512fa1f78c4d5e506666eb3259e7
Author: Silas Brack <silasbrack@gmail.com>
Date: Sat, 13 Jun 2026 15:31:12 +0200
feat: epoch integer timestamps, drop chrono
All timestamps are now epoch seconds (i64) instead of text strings.
Unambiguous (always UTC), smaller (8 bytes vs 19), no parsing.
time_ago() and hot_score() use integer subtraction instead of
chrono date parsing. Removes chrono dependency (3 transitive deps).
Migration 0005 converts all existing text timestamps via
strftime('%s', column).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diffstat:
9 files changed, 238 insertions(+), 257 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -15,15 +15,6 @@ dependencies = [
]
[[package]]
-name = "android_system_properties"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
-dependencies = [
- "libc",
-]
-
-[[package]]
name = "argon2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -206,48 +197,18 @@ dependencies = [
]
[[package]]
-name = "bumpalo"
-version = "3.20.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
-
-[[package]]
name = "bytes"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33"
[[package]]
-name = "cc"
-version = "1.2.61"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d"
-dependencies = [
- "find-msvc-tools",
- "shlex",
-]
-
-[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
-name = "chrono"
-version = "0.4.44"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
-dependencies = [
- "iana-time-zone",
- "js-sys",
- "num-traits",
- "serde",
- "wasm-bindgen",
- "windows-link",
-]
-
-[[package]]
name = "cookie"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -259,12 +220,6 @@ dependencies = [
]
[[package]]
-name = "core-foundation-sys"
-version = "0.8.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
-
-[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -316,12 +271,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
-name = "find-msvc-tools"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582"
-
-[[package]]
name = "form_urlencoded"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -498,46 +447,12 @@ dependencies = [
]
[[package]]
-name = "iana-time-zone"
-version = "0.1.65"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
-dependencies = [
- "android_system_properties",
- "core-foundation-sys",
- "iana-time-zone-haiku",
- "js-sys",
- "log",
- "wasm-bindgen",
- "windows-core",
-]
-
-[[package]]
-name = "iana-time-zone-haiku"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
-dependencies = [
- "cc",
-]
-
-[[package]]
name = "itoa"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
[[package]]
-name = "js-sys"
-version = "0.3.95"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca"
-dependencies = [
- "once_cell",
- "wasm-bindgen",
-]
-
-[[package]]
name = "libc"
version = "0.2.186"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -826,12 +741,6 @@ dependencies = [
]
[[package]]
-name = "shlex"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
-
-[[package]]
name = "simple-web-app"
version = "0.1.0"
dependencies = [
@@ -839,7 +748,6 @@ dependencies = [
"askama",
"axum",
"axum-extra",
- "chrono",
"rand",
"rusqlite",
"serde",
@@ -1058,110 +966,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
-name = "wasm-bindgen"
-version = "0.2.118"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
-dependencies = [
- "cfg-if",
- "once_cell",
- "rustversion",
- "wasm-bindgen-macro",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.118"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.118"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
-dependencies = [
- "bumpalo",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.118"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
-dependencies = [
- "unicode-ident",
-]
-
-[[package]]
-name = "windows-core"
-version = "0.62.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
-dependencies = [
- "windows-implement",
- "windows-interface",
- "windows-link",
- "windows-result",
- "windows-strings",
-]
-
-[[package]]
-name = "windows-implement"
-version = "0.60.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "windows-interface"
-version = "0.59.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
-name = "windows-result"
-version = "0.4.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
-name = "windows-strings"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
-dependencies = [
- "windows-link",
-]
-
-[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
@@ -7,7 +7,6 @@ edition = "2021"
axum = "0.8"
axum-extra = { version = "0.10", features = ["cookie"] }
askama = "0.12"
-chrono = { version = "0.4", features = ["serde"] }
tokio = { version = "1", features = ["macros", "rt-multi-thread", "net"] }
serde = { version = "1", features = ["derive"] }
tokio-stream = { version = "0.1", features = ["sync"] }
diff --git a/migrations/0005_epoch_timestamps.sql b/migrations/0005_epoch_timestamps.sql
@@ -0,0 +1,203 @@
+-- Convert all timestamp columns from TEXT to INTEGER (epoch seconds).
+-- Epoch integers are unambiguous (always UTC), smaller, and don't
+-- need parsing.
+
+PRAGMA foreign_keys=OFF;
+BEGIN IMMEDIATE;
+
+-- Drop triggers and indices (same as migration 0003)
+DROP TRIGGER IF EXISTS story_fts_insert;
+DROP TRIGGER IF EXISTS story_fts_update;
+DROP TRIGGER IF EXISTS story_fts_delete;
+DROP TRIGGER IF EXISTS comment_count_insert;
+DROP TRIGGER IF EXISTS comment_count_delete;
+DROP TRIGGER IF EXISTS vote_insert_story;
+DROP TRIGGER IF EXISTS vote_delete_story;
+DROP TRIGGER IF EXISTS vote_insert_comment;
+DROP TRIGGER IF EXISTS vote_delete_comment;
+
+DROP INDEX IF EXISTS idx_story_published;
+DROP INDEX IF EXISTS idx_story_score;
+DROP INDEX IF EXISTS idx_story_created_by;
+DROP INDEX IF EXISTS idx_comment_story_id;
+DROP INDEX IF EXISTS idx_comment_parent_id;
+DROP INDEX IF EXISTS idx_comment_created_by;
+DROP INDEX IF EXISTS idx_vote_user_target;
+DROP INDEX IF EXISTS idx_story_category_news_item;
+DROP INDEX IF EXISTS idx_story_category_category;
+DROP INDEX IF EXISTS idx_session_user_id;
+
+DROP TABLE IF EXISTS story_fts;
+
+-- Recreate tables with INTEGER timestamps
+
+ALTER TABLE user RENAME TO user_old;
+CREATE TABLE user (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ email TEXT UNIQUE NOT NULL,
+ password_hash TEXT NOT NULL,
+ username TEXT UNIQUE NOT NULL,
+ about TEXT,
+ karma INTEGER DEFAULT 0,
+ created_at INTEGER NOT NULL,
+ email_verified INTEGER NOT NULL DEFAULT 0,
+ verification_token TEXT
+) STRICT;
+INSERT INTO user (id, email, password_hash, username, about, karma, created_at, email_verified, verification_token)
+SELECT id, email, password_hash, username, about, karma,
+ CAST(strftime('%s', created_at) AS INTEGER),
+ email_verified, verification_token
+FROM user_old;
+DROP TABLE user_old;
+
+ALTER TABLE category RENAME TO category_old;
+CREATE TABLE category (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT
+) STRICT;
+INSERT INTO category SELECT * FROM category_old;
+DROP TABLE category_old;
+
+ALTER TABLE story RENAME TO story_old;
+CREATE TABLE story (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ url TEXT,
+ title TEXT NOT NULL,
+ text TEXT NOT NULL DEFAULT '',
+ published INTEGER NOT NULL,
+ author TEXT,
+ language TEXT NOT NULL DEFAULT 'en',
+ created_by INTEGER REFERENCES user(id),
+ score INTEGER NOT NULL DEFAULT 0,
+ comment_count INTEGER NOT NULL DEFAULT 0,
+ domain TEXT
+) STRICT;
+INSERT INTO story (id, url, title, text, published, author, language, created_by, score, comment_count, domain)
+SELECT id, url, title, text,
+ CAST(strftime('%s', published) AS INTEGER),
+ author, language, created_by, score, comment_count, domain
+FROM story_old;
+DROP TABLE story_old;
+
+ALTER TABLE comment RENAME TO comment_old;
+CREATE TABLE comment (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ story_id INTEGER NOT NULL REFERENCES story(id),
+ parent_id INTEGER,
+ path TEXT NOT NULL DEFAULT '',
+ depth INTEGER NOT NULL DEFAULT 0,
+ text TEXT NOT NULL DEFAULT '',
+ score INTEGER NOT NULL DEFAULT 0,
+ created_at INTEGER NOT NULL,
+ created_by INTEGER NOT NULL REFERENCES user(id)
+) STRICT;
+INSERT INTO comment (id, story_id, parent_id, path, depth, text, score, created_at, created_by)
+SELECT id, story_id, parent_id, path, depth, text, score,
+ CAST(strftime('%s', created_at) AS INTEGER),
+ created_by
+FROM comment_old;
+DROP TABLE comment_old;
+
+ALTER TABLE vote RENAME TO vote_old;
+CREATE TABLE vote (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL REFERENCES user(id),
+ target_type INTEGER NOT NULL,
+ target_id INTEGER NOT NULL,
+ created_at INTEGER NOT NULL,
+ UNIQUE(user_id, target_type, target_id)
+) STRICT;
+INSERT INTO vote (id, user_id, target_type, target_id, created_at)
+SELECT id, user_id, target_type, target_id,
+ CAST(strftime('%s', created_at) AS INTEGER)
+FROM vote_old;
+DROP TABLE vote_old;
+
+ALTER TABLE story_category RENAME TO story_category_old;
+CREATE TABLE story_category (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ news_item_id INTEGER NOT NULL REFERENCES story(id),
+ category_id INTEGER NOT NULL REFERENCES category(id)
+) STRICT;
+INSERT INTO story_category SELECT * FROM story_category_old;
+DROP TABLE story_category_old;
+
+ALTER TABLE session RENAME TO session_old;
+CREATE TABLE session (
+ token TEXT PRIMARY KEY NOT NULL,
+ user_id INTEGER NOT NULL REFERENCES user(id),
+ created_at INTEGER NOT NULL
+) STRICT;
+INSERT INTO session (token, user_id, created_at)
+SELECT token, user_id,
+ CAST(strftime('%s', created_at) AS INTEGER)
+FROM session_old;
+DROP TABLE session_old;
+
+-- Recreate indices
+CREATE INDEX idx_story_published ON story(published DESC);
+CREATE INDEX idx_story_score ON story(score DESC);
+CREATE INDEX idx_story_created_by ON story(created_by);
+CREATE INDEX idx_comment_story_id ON comment(story_id);
+CREATE INDEX idx_comment_parent_id ON comment(parent_id);
+CREATE INDEX idx_comment_created_by ON comment(created_by);
+CREATE INDEX idx_vote_user_target ON vote(user_id, target_type, target_id);
+CREATE INDEX idx_story_category_news_item ON story_category(news_item_id);
+CREATE INDEX idx_story_category_category ON story_category(category_id);
+CREATE INDEX idx_session_user_id ON session(user_id);
+
+-- Recreate FTS
+CREATE VIRTUAL TABLE story_fts USING fts5(
+ title, text,
+ content='story',
+ content_rowid='id',
+ tokenize='porter unicode61'
+);
+INSERT INTO story_fts(rowid, title, text) SELECT id, title, text FROM story;
+
+-- Recreate triggers
+CREATE TRIGGER story_fts_insert AFTER INSERT ON story BEGIN
+ INSERT INTO story_fts(rowid, title, text) VALUES (new.id, new.title, new.text);
+END;
+
+CREATE TRIGGER story_fts_update AFTER UPDATE ON story BEGIN
+ INSERT INTO story_fts(story_fts, rowid, title, text) VALUES ('delete', old.id, old.title, old.text);
+ INSERT INTO story_fts(rowid, title, text) VALUES (new.id, new.title, new.text);
+END;
+
+CREATE TRIGGER story_fts_delete AFTER DELETE ON story BEGIN
+ INSERT INTO story_fts(story_fts, rowid, title, text) VALUES ('delete', old.id, old.title, old.text);
+END;
+
+CREATE TRIGGER comment_count_insert AFTER INSERT ON comment BEGIN
+ UPDATE story SET comment_count = comment_count + 1 WHERE id = new.story_id;
+END;
+
+CREATE TRIGGER comment_count_delete AFTER DELETE ON comment BEGIN
+ UPDATE story SET comment_count = comment_count - 1 WHERE id = old.story_id;
+END;
+
+CREATE TRIGGER vote_insert_story AFTER INSERT ON vote WHEN new.target_type = 1 BEGIN
+ UPDATE story SET score = score + 1 WHERE id = new.target_id;
+ UPDATE user SET karma = karma + 1 WHERE id = (SELECT created_by FROM story WHERE id = new.target_id);
+END;
+
+CREATE TRIGGER vote_delete_story AFTER DELETE ON vote WHEN old.target_type = 1 BEGIN
+ UPDATE story SET score = score - 1 WHERE id = old.target_id;
+ UPDATE user SET karma = karma - 1 WHERE id = (SELECT created_by FROM story WHERE id = old.target_id);
+END;
+
+CREATE TRIGGER vote_insert_comment AFTER INSERT ON vote WHEN new.target_type = 2 BEGIN
+ UPDATE comment SET score = score + 1 WHERE id = new.target_id;
+ UPDATE user SET karma = karma + 1 WHERE id = (SELECT created_by FROM comment WHERE id = new.target_id);
+END;
+
+CREATE TRIGGER vote_delete_comment AFTER DELETE ON vote WHEN old.target_type = 2 BEGIN
+ UPDATE comment SET score = score - 1 WHERE id = old.target_id;
+ UPDATE user SET karma = karma - 1 WHERE id = (SELECT created_by FROM comment WHERE id = old.target_id);
+END;
+
+UPDATE migration_version SET version = 5;
+COMMIT;
+
+PRAGMA foreign_keys=ON;
diff --git a/src/database.rs b/src/database.rs
@@ -22,7 +22,7 @@ pub struct Story {
pub domain: Option<String>,
pub score: i64,
pub comment_count: i64,
- pub published: String,
+ pub published: i64,
pub created_by: Option<String>,
pub author: Option<String>,
pub language: String,
@@ -37,7 +37,7 @@ pub struct Comment {
pub depth: i64,
pub text: String,
pub score: i64,
- pub created_at: String,
+ pub created_at: i64,
pub created_by: String,
}
@@ -55,7 +55,7 @@ pub struct User {
pub username: String,
pub about: Option<String>,
pub karma: i64,
- pub created_at: String,
+ pub created_at: i64,
pub email_verified: bool,
pub verification_token: Option<String>,
}
@@ -79,7 +79,7 @@ impl From<User> for UserProfile {
username: u.username,
about: u.about,
karma: u.karma,
- created_at: u.created_at,
+ created_at: time_ago(u.created_at),
}
}
}
@@ -113,7 +113,7 @@ pub struct StoryWithMeta {
pub domain: Option<String>,
pub score: i64,
pub comment_count: i64,
- pub published: String,
+ pub published: i64,
pub created_by: Option<String>,
pub tags: Vec<Category>,
pub author_username: Option<String>,
@@ -160,7 +160,7 @@ pub struct CommentWithMeta {
pub indent_px: i64,
pub text: String,
pub score: i64,
- pub created_at: String,
+ pub created_at: i64,
pub created_by: String,
pub author_username: Option<String>,
pub time_ago: String,
@@ -325,8 +325,8 @@ impl Database {
.map_err(|e| AppError::Database(e.to_string()))?;
stories.sort_by(|a, b| {
- let score_a = hot_score(a.score, &a.published);
- let score_b = hot_score(b.score, &b.published);
+ let score_a = hot_score(a.score, a.published);
+ let score_b = hot_score(b.score, b.published);
score_b.partial_cmp(&score_a).unwrap_or(std::cmp::Ordering::Equal)
});
@@ -359,7 +359,7 @@ impl Database {
language: &str,
) -> Result<Story, AppError> {
let conn = self.write_conn()?;
- let published = chrono::Utc::now().format("%Y-%m-%d %H:%M:%S").to_string();
+ let published = now_epoch();
conn.execute(
"INSERT INTO story (title, url, text, domain, published, language, created_by)
@@ -949,66 +949,34 @@ impl Database {
// Helper functions
// ==========================================================================
-/// Calculate hot score for ranking (HN-style algorithm)
-pub fn hot_score(points: i64, published: &str) -> f64 {
- let age_hours = hours_since(published);
- (points as f64) / (age_hours + 2.0).powf(1.8)
+pub fn now_epoch() -> i64 {
+ std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .unwrap()
+ .as_secs() as i64
}
-/// Calculate hours since a timestamp
-fn hours_since(timestamp: &str) -> f64 {
- use chrono::{DateTime, Utc};
-
- let published = timestamp
- .parse::<DateTime<Utc>>()
- .or_else(|_| {
- chrono::NaiveDateTime::parse_from_str(timestamp, "%Y-%m-%d %H:%M:%S")
- .map(|dt| dt.and_utc())
- })
- .unwrap_or_else(|_| Utc::now());
-
- let now = Utc::now();
- let duration = now.signed_duration_since(published);
- duration.num_minutes() as f64 / 60.0
+pub fn hot_score(points: i64, published: i64) -> f64 {
+ let age_hours = (now_epoch() - published) as f64 / 3600.0;
+ (points as f64) / (age_hours + 2.0).powf(1.8)
}
-/// Format a timestamp as "X ago" string
-pub fn time_ago(timestamp: &str) -> String {
- use chrono::{DateTime, Utc};
-
- let published = timestamp
- .parse::<DateTime<Utc>>()
- .or_else(|_| {
- chrono::NaiveDateTime::parse_from_str(timestamp, "%Y-%m-%d %H:%M:%S")
- .map(|dt| dt.and_utc())
- })
- .unwrap_or_else(|_| Utc::now());
-
- let now = Utc::now();
- let duration = now.signed_duration_since(published);
+pub fn time_ago(timestamp: i64) -> String {
+ let elapsed = now_epoch() - timestamp;
- let minutes = duration.num_minutes();
- let hours = duration.num_hours();
- let days = duration.num_days();
+ let days = elapsed / 86400;
+ let hours = elapsed / 3600;
+ let minutes = elapsed / 60;
if days > 0 {
- if days == 1 {
- "1 day ago".to_string()
- } else {
- format!("{} days ago", days)
- }
+ if days == 1 { "1 day ago".to_string() }
+ else { format!("{days} days ago") }
} else if hours > 0 {
- if hours == 1 {
- "1 hour ago".to_string()
- } else {
- format!("{} hours ago", hours)
- }
+ if hours == 1 { "1 hour ago".to_string() }
+ else { format!("{hours} hours ago") }
} else if minutes > 0 {
- if minutes == 1 {
- "1 minute ago".to_string()
- } else {
- format!("{} minutes ago", minutes)
- }
+ if minutes == 1 { "1 minute ago".to_string() }
+ else { format!("{minutes} minutes ago") }
} else {
"just now".to_string()
}
diff --git a/src/handlers/feed.rs b/src/handlers/feed.rs
@@ -90,7 +90,7 @@ async fn fetch_feed(
.unwrap_or_default();
let author_username = story_meta.and_then(|m| m.author_username.clone());
- let time_ago = database::time_ago(&story.published);
+ let time_ago = database::time_ago(story.published);
let user_voted = voted_set.contains(&story.id);
let rank = base_rank + idx + 1;
diff --git a/src/handlers/story.rs b/src/handlers/story.rs
@@ -50,7 +50,7 @@ async fn fetch_enriched_comments(
for comment in comments {
let uid: i64 = comment.created_by.parse().unwrap_or(0);
let comment_author = author_map.get(&uid).cloned();
- let comment_time_ago = database::time_ago(&comment.created_at);
+ let comment_time_ago = database::time_ago(comment.created_at);
let comment_voted = voted_comment_ids.contains(&comment.id);
enriched.push(CommentWithMeta::from_comment(
@@ -102,7 +102,7 @@ pub async fn show_story(
Ok((story, tags, author_username, story_voted))
}).await?;
- let time_ago = database::time_ago(&story.published);
+ let time_ago = database::time_ago(story.published);
let story_with_meta = StoryWithMeta::from_story(story, tags, author_username, time_ago, story_voted, 0);
let enriched_comments = fetch_enriched_comments(&state, user_id, id).await?;
diff --git a/src/handlers/tag.rs b/src/handlers/tag.rs
@@ -69,7 +69,7 @@ pub async fn show_tag(
.unwrap_or_default();
let author_username = story_meta.and_then(|m| m.author_username.clone());
- let time_ago = database::time_ago(&story.published);
+ let time_ago = database::time_ago(story.published);
let user_voted = voted_ids.contains(&story.id);
let rank = base_rank + idx + 1;
diff --git a/src/handlers/user.rs b/src/handlers/user.rs
@@ -67,7 +67,7 @@ pub async fn show_user(
let tags = story_meta
.map(|m| m.tags.iter().map(|name| database::Category { id: 0, name: Some(name.clone()) }).collect())
.unwrap_or_default();
- let time_ago = database::time_ago(&story.published);
+ let time_ago = database::time_ago(story.published);
let user_voted = voted_ids.contains(&story.id);
submissions.push(StoryWithMeta::from_story(
@@ -84,7 +84,7 @@ pub async fn show_user(
let user_comments = db.get_comments_by_user(user_id_num, 30, 0)?;
for comment in user_comments {
- let time_ago = database::time_ago(&comment.created_at);
+ let time_ago = database::time_ago(comment.created_at);
comments.push(CommentWithMeta::from_comment(
comment,
Some(profile.username.clone()),
diff --git a/src/migrations.rs b/src/migrations.rs
@@ -5,6 +5,7 @@ const MIGRATIONS: &[&str] = &[
include_str!("../migrations/0002_email_verification.sql"),
include_str!("../migrations/0003_strict_tables.sql"),
include_str!("../migrations/0004_sessions.sql"),
+ include_str!("../migrations/0005_epoch_timestamps.sql"),
];
pub fn run(conn: &Connection) -> Result<(), String> {