about summary refs log tree commit diff
path: root/knowing/src/db.rs
diff options
context:
space:
mode:
authorgennyble <gen@nyble.dev>2026-09-25 03:56:11 -0500
committergennyble <gen@nyble.dev>2026-09-25 03:56:11 -0500
commitba592de54cd3dce52eb2096dabbe748107f5db2b (patch)
tree4a23377e9788630e09c669e9ed18aecd27673ee7 /knowing/src/db.rs
parentf4d8e8d03c1b80d5c59ee85631d5a78048892a32 (diff)
splorm auth
Diffstat (limited to 'knowing/src/db.rs')
-rw-r--r--knowing/src/db.rs200
1 files changed, 200 insertions, 0 deletions
diff --git a/knowing/src/db.rs b/knowing/src/db.rs
new file mode 100644
index 0000000..593baad
--- /dev/null
+++ b/knowing/src/db.rs
@@ -0,0 +1,200 @@
+#![allow(async_fn_in_trait)]
+#![allow(unused)]
+
+use crate::Error;
+use somethings::{
+	DatabaseError, DatabaseHandle,
+	auth::{AnonymousUser, HashedPassword, Session, SessionId, User, UserAuth, UserId},
+	params,
+};
+
+pub trait AuthDatabase {
+	async fn auth_create_tables(&self) -> Result<(), DatabaseError>;
+
+	async fn insert_user(&self, id: &UserId, name: &str, hash: HashedPassword)
+	-> Result<(), Error>;
+	async fn insert_anonymous_user(&self, id: &UserId) -> Result<(), Error>;
+
+	async fn user_exists(&self, id: &UserId) -> Result<bool, Error>;
+	async fn get_user_by_id(&self, id: &UserId) -> Result<User, Error>;
+	async fn get_anonymous_user_by_id(&self, id: &UserId) -> Result<AnonymousUser, Error>;
+
+	async fn get_userauth_by_name(&self, name: &str) -> Result<UserAuth, Error>;
+
+	async fn insert_session(&self, id: &SessionId, uid: &UserId) -> Result<(), Error>;
+
+	async fn get_session_by_id(&self, id: &SessionId) -> Result<Session, Error>;
+
+	async fn delete_session_by_id(&self, id: &SessionId) -> Result<(), Error>;
+}
+
+impl AuthDatabase for DatabaseHandle {
+	async fn auth_create_tables(&self) -> Result<(), DatabaseError> {
+		let lock = self.lock().await;
+		lock.create_table("users", CREATE_TABLE_USERS)?;
+		lock.create_table("sessions", CREATE_TABLE_SESSIONS)
+	}
+
+	async fn insert_user(
+		&self,
+		id: &UserId,
+		name: &str,
+		hash: HashedPassword,
+	) -> Result<(), Error> {
+		self.lock().await.insert(
+			"INSERT INTO users(id, name, hash) VALUES(?1, ?2, ?3)",
+			params![id, name, hash.to_string()],
+		)?;
+
+		Ok(())
+	}
+
+	async fn insert_anonymous_user(&self, id: &UserId) -> Result<(), Error> {
+		self.lock()
+			.await
+			.insert("INSERT INTO users(id) VALUES(?1)", params![id])?;
+
+		Ok(())
+	}
+
+	async fn user_exists(&self, id: &UserId) -> Result<bool, Error> {
+		match get_dbuser_by_id(self, id).await {
+			Ok(_) => Ok(true),
+			Err(Error::DatabaseError(DatabaseError::NoRows)) => Ok(false),
+			Err(e) => Err(e),
+		}
+	}
+
+	async fn get_user_by_id(&self, id: &UserId) -> Result<User, Error> {
+		get_dbuser_by_id(&self, id).await?.try_into()
+	}
+
+	async fn get_anonymous_user_by_id(&self, id: &UserId) -> Result<AnonymousUser, Error> {
+		let dbuser = get_dbuser_by_id(&self, id).await?;
+		match dbuser.name {
+			Some(_) => Err(Error::UserIsRegistered),
+			None => Ok(AnonymousUser::new(dbuser.id)),
+		}
+	}
+
+	async fn get_userauth_by_name(&self, name: &str) -> Result<UserAuth, Error> {
+		get_dbuser_by_name(&self, name).await?.try_into()
+	}
+
+	async fn insert_session(&self, id: &SessionId, uid: &UserId) -> Result<(), Error> {
+		self.lock().await.insert(
+			"INSERT INTO sessions(id, user_id) VALUES(?1, ?2)",
+			params![id, uid],
+		)?;
+
+		Ok(())
+	}
+
+	async fn get_session_by_id(&self, id: &SessionId) -> Result<Session, Error> {
+		let sql = "SELECT sessions.id, users.id, users.name \
+			FROM sessions \
+			INNER JOIN users ON sessions.user_id = users.id \
+			WHERE sessions.id = ?1";
+
+		self.lock()
+			.await
+			.query_one(sql, params![id], |row| {
+				Ok(Session::new(
+					row.get(0)?,
+					User::new(row.get(1)?, row.get(2)?),
+				))
+			})
+			.map_err(|dbe| Error::DatabaseError(dbe))
+	}
+
+	async fn delete_session_by_id(&self, id: &SessionId) -> Result<(), Error> {
+		self.lock()
+			.await
+			.execute("DELETE FROM session WHERE id = ?1", params![id])?;
+		Ok(())
+	}
+}
+
+async fn get_dbuser_by_id(db: &DatabaseHandle, id: &UserId) -> Result<DbUser, Error> {
+	db.lock()
+		.await
+		.query_one(
+			"SELECT id, name, hash FROM users WHERE id = ?1",
+			params![id],
+			|row| {
+				Ok(DbUser {
+					id: row.get(0)?,
+					name: row.get(1)?,
+					hash: row.get(2)?,
+				})
+			},
+		)
+		.map_err(|e| Error::DatabaseError(e))
+}
+
+async fn get_dbuser_by_name(db: &DatabaseHandle, name: &str) -> Result<DbUser, Error> {
+	db.lock()
+		.await
+		.query_one(
+			"SELECT id, name, hash FROM users WHERE name = ?1",
+			params![name],
+			|row| {
+				Ok(DbUser {
+					id: row.get(0)?,
+					name: row.get(1)?,
+					hash: row.get(2)?,
+				})
+			},
+		)
+		.map_err(|e| Error::DatabaseError(e))
+}
+
+pub const CREATE_TABLE_USERS: &'static str = "\
+	CREATE TABLE IF NOT EXISTS users(
+		id TEXT NOT NULL PRIMARY KEY,
+		name TEXT,
+		hash TEXT,
+		created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
+	);";
+
+pub const CREATE_TABLE_SESSIONS: &'static str = "\
+	CREATE TABLE IF NOT EXISTS sessions(
+		id TEXT NOT NULL PRIMARY KEY,
+		user_id INTEGER NOT NULL,
+		created TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
+		FOREIGN KEY (user_id)
+			REFERENCES users(id)
+	);";
+
+struct DbUser {
+	id: UserId,
+	name: Option<String>,
+	hash: Option<String>,
+}
+
+impl TryInto<User> for DbUser {
+	type Error = Error;
+
+	fn try_into(self) -> Result<User, Self::Error> {
+		let DbUser { id, name, hash: _ } = self;
+		match name {
+			Some(name) => Ok(User::new(id, name)),
+			None => Err(Error::UserIsAnonymous),
+		}
+	}
+}
+
+impl TryInto<UserAuth> for DbUser {
+	type Error = Error;
+
+	fn try_into(self) -> Result<UserAuth, Self::Error> {
+		let DbUser { id, name, hash } = self;
+		match (name, hash) {
+			(Some(name), Some(hash)) => Ok(UserAuth::new(
+				User::new(id, name),
+				HashedPassword::new(hash),
+			)),
+			_ => Err(Error::UserIsAnonymous),
+		}
+	}
+}