Skip to main content

vector_core/db/
profiles.rs

1//! Profile database operations — read/write SlimProfile to SQLite.
2
3use crate::profile::{SlimProfile, Status};
4
5/// Load all profiles from the database.
6pub fn get_all_profiles() -> Result<Vec<SlimProfile>, String> {
7    let conn = super::get_db_connection_guard_static()?;
8
9    let mut stmt = conn.prepare(
10        "SELECT npub, name, display_name, nickname, lud06, lud16, banner, avatar, \
11         about, website, nip05, status_content, status_url, bot, avatar_cached, \
12         banner_cached, is_blocked, status_emoji_tags FROM profiles"
13    ).map_err(|e| format!("Failed to prepare statement: {}", e))?;
14
15    let profiles = stmt.query_map([], |row| {
16        Ok(SlimProfile {
17            id: row.get(0)?,
18            name: row.get(1)?,
19            display_name: row.get(2)?,
20            nickname: row.get(3)?,
21            lud06: row.get(4)?,
22            lud16: row.get(5)?,
23            banner: row.get(6)?,
24            avatar: row.get(7)?,
25            about: row.get(8)?,
26            website: row.get(9)?,
27            nip05: row.get(10)?,
28            status: Status {
29                title: row.get(11)?,
30                purpose: String::new(),
31                url: row.get(12)?,
32                emoji_tags: {
33                    let json: String = row.get(17)?;
34                    if json.is_empty() { Vec::new() }
35                    else { serde_json::from_str(&json).unwrap_or_default() }
36                },
37            },
38            last_updated: 0,
39            mine: false,
40            bot: row.get::<_, i32>(13)? != 0,
41            avatar_cached: {
42                let p: String = row.get(14)?;
43                if !p.is_empty() && !std::path::Path::new(&p).exists() { String::new() } else { p }
44            },
45            banner_cached: {
46                let p: String = row.get(15)?;
47                if !p.is_empty() && !std::path::Path::new(&p).exists() { String::new() } else { p }
48            },
49            is_blocked: row.get::<_, i32>(16).unwrap_or(0) != 0,
50        })
51    })
52    .map_err(|e| format!("Failed to query profiles: {}", e))?
53    .collect::<Result<Vec<_>, _>>()
54    .map_err(|e| format!("Failed to collect profiles: {}", e))?;
55
56    Ok(profiles)
57}
58
59/// Upsert a profile to the database (INSERT ... ON CONFLICT DO UPDATE).
60pub fn set_profile(profile: &SlimProfile) -> Result<(), String> {
61    let conn = super::get_write_connection_guard_static()?;
62
63    // Tags ride the row so a fresh boot renders status emojis without
64    // waiting on (or even needing) a relay answer.
65    let status_emoji_tags = if profile.status.emoji_tags.is_empty() {
66        String::new()
67    } else {
68        serde_json::to_string(&profile.status.emoji_tags).unwrap_or_default()
69    };
70
71    conn.execute(
72        "INSERT INTO profiles (npub, name, display_name, nickname, lud06, lud16, banner, avatar, \
73         about, website, nip05, status_content, status_url, bot, avatar_cached, banner_cached, is_blocked, \
74         status_emoji_tags) \
75         VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18) \
76         ON CONFLICT(npub) DO UPDATE SET \
77            name = excluded.name, display_name = excluded.display_name, \
78            nickname = excluded.nickname, lud06 = excluded.lud06, lud16 = excluded.lud16, \
79            banner = excluded.banner, avatar = excluded.avatar, about = excluded.about, \
80            website = excluded.website, nip05 = excluded.nip05, \
81            status_content = excluded.status_content, status_url = excluded.status_url, \
82            bot = excluded.bot, avatar_cached = excluded.avatar_cached, \
83            banner_cached = excluded.banner_cached, is_blocked = excluded.is_blocked, \
84            status_emoji_tags = excluded.status_emoji_tags",
85        rusqlite::params![
86            profile.id,
87            profile.name,
88            profile.display_name,
89            profile.nickname,
90            profile.lud06,
91            profile.lud16,
92            profile.banner,
93            profile.avatar,
94            profile.about,
95            profile.website,
96            profile.nip05,
97            profile.status.title,
98            profile.status.url,
99            profile.bot as i32,
100            profile.avatar_cached,
101            profile.banner_cached,
102            profile.is_blocked as i32,
103            status_emoji_tags,
104        ],
105    ).map_err(|e| format!("Failed to insert profile: {}", e))?;
106
107    Ok(())
108}