Files
zep/spacetimedb/src/lib.rs
T
2026-04-17 15:58:26 -04:00

197 lines
6.4 KiB
Rust

use spacetimedb::{Identity, ReducerContext, Table};
mod reducers;
mod tables;
mod utils;
mod views;
pub use reducers::*;
pub use tables::*;
pub use utils::*;
pub use views::*;
pub const SYSTEM_IDENTITY: &str = "0000000000000000000000000000000000000000000000000000000000000000";
#[spacetimedb::reducer(init)]
pub fn init(ctx: &ReducerContext) {
let system_identity = Identity::from_hex(SYSTEM_IDENTITY).unwrap();
// Create system user if not exists
if ctx.db.user().identity().find(system_identity).is_none() {
ctx.db.user().insert(User {
identity: system_identity,
name: Some("Zep".to_string()),
online: true,
issuer: None,
subject: None,
anonymous: false,
avatar_id: None,
banner_id: None,
biography: Some("I am the Zep system assistant.".to_string()),
status: Some("Online".to_string()),
public_key: None,
});
}
if ctx
.db
.system_configuration()
.key()
.find("max_message_length".to_string())
.is_none()
{
ctx.db.system_configuration().insert(SystemConfiguration {
key: "max_message_length".to_string(),
value: "262144".to_string(),
});
}
if Table::iter(ctx.db.server()).next().is_none() {
let s = ctx.db.server().insert(Server {
id: 0,
name: "Zep".to_string(),
owner: None,
avatar_id: None,
channels: Vec::new(),
public: true,
});
let c1 = ctx.db.channel().insert(Channel {
id: 0,
server_id: s.id,
name: "general".to_string(),
kind: ChannelKind::Text,
});
let c2 = ctx.db.channel().insert(Channel {
id: 0,
server_id: s.id,
name: "Voice General".to_string(),
kind: ChannelKind::Voice,
});
let mut s = ctx.db.server().id().find(s.id).unwrap();
s.channels.push(ChannelMetadata {
id: c1.id,
name: c1.name,
kind: c1.kind,
});
s.channels.push(ChannelMetadata {
id: c2.id,
name: c2.name,
kind: c2.kind,
});
ctx.db.server().id().update(s.clone());
// Grant access to system user
sync_server_access(&ctx.db, system_identity, s.id);
}
}
#[spacetimedb::reducer(client_connected)]
pub fn on_connect(ctx: &ReducerContext) {
log::info!("on_connect START: identity={}", ctx.sender().to_hex());
// Extract potential name from OIDC if available
let mut initial_name = None;
let mut is_anon = true;
if let Some(jwt) = ctx.sender_auth().jwt() {
let sub = jwt.subject();
let issuer = jwt.issuer();
// Use first 8 chars of sub if it's a long string/UUID
initial_name = Some(if sub.len() > 12 { sub[..8].to_string() } else { sub.to_string() });
is_anon = issuer.contains("localhost");
}
if let Some(mut user) = ctx.db.user().identity().find(ctx.sender()) {
user.online = true;
// Update name from OIDC if current user has no name
if user.name.is_none() && initial_name.is_some() {
user.name = initial_name;
}
user.anonymous = is_anon;
ctx.db.user().identity().update(user);
} else {
ctx.db.user().insert(User {
identity: ctx.sender(),
name: initial_name,
online: true,
issuer: None,
subject: None,
anonymous: is_anon,
avatar_id: None,
banner_id: None,
biography: None,
status: None,
public_key: None,
});
// Minimal auto-join
join_server(ctx, 1);
// System Welcome DM
let system_identity = Identity::from_hex(SYSTEM_IDENTITY).unwrap();
let channel_id = internal_open_direct_message(&ctx.db, system_identity, ctx.sender());
let welcome_text = "Welcome to Zep! We're glad to have you here.\n\nZep is a decentralized, private, and fast chat service built on SpacetimeDB. You can join servers, create channels, and message friends directly—all with the security and performance of a modern relational backend.";
internal_send_message(
&ctx.db,
system_identity,
channel_id,
welcome_text.to_string(),
ctx.timestamp,
None,
vec![],
false,
);
}
// High Performance: Sync all channel access for this user
for member in ctx.db.server_member().identity().filter(ctx.sender()) {
sync_server_access(&ctx.db, ctx.sender(), member.server_id);
}
sync_server_member_info(&ctx.db, ctx.sender());
log::info!("on_connect END: identity={}", ctx.sender().to_hex());
}
#[spacetimedb::reducer(client_disconnected)]
pub fn on_disconnect(ctx: &ReducerContext) {
log::info!("on_disconnect: identity={}", ctx.sender().to_hex());
if let Some(mut user) = ctx.db.user().identity().find(ctx.sender()) {
user.online = false;
ctx.db.user().identity().update(user);
}
sync_server_member_info(&ctx.db, ctx.sender());
if let Some(ta) = ctx.db.typing_activity().identity().find(ctx.sender()) {
ctx.db.typing_activity().delete(ta);
}
clear_user_presence(&ctx.db, ctx.sender());
}
#[spacetimedb::reducer]
pub fn update_auth_info(ctx: &ReducerContext) {
log::info!("update_auth_info: identity={}", ctx.sender().to_hex());
if let Some(mut user) = ctx.db.user().identity().find(ctx.sender()) {
if let Some(jwt) = ctx.sender_auth().jwt() {
let sub = jwt.subject();
let issuer = jwt.issuer();
user.issuer = Some(issuer.to_string());
user.subject = Some(sub.to_string());
// Flag as anonymous if issuer is localhost
user.anonymous = issuer.contains("localhost");
// Also update name if they don't have a custom one yet
if user.name.is_none() {
user.name = Some(if sub.len() > 12 { sub[..8].to_string() } else { sub.to_string() });
}
log::info!("update_auth_info: updated user with OIDC info (anon={})", user.anonymous);
ctx.db.user().identity().update(user);
sync_server_member_info(&ctx.db, ctx.sender());
}
}
}