feat(service): expand service layer with new domain operations

- Add IM service modules: audit, channel roles, custom emojis, forum
  tags, integrations, invitations, repo links, slash commands, stages,
  voice, webhooks
- Add PR service modules: review requests, templates
- Add repo service modules: contributors, release assets, git extras
  (archive, branch rename, commit extras, diff/merge, tag, tree)
- Add user service: social (follow/block)
- Add internal auth service
- Update existing service modules with expanded functionality
- Remove deleted IM modules: articles, delivery trace, drafts,
  follows, messages, polls, presence, reactions, threads
This commit is contained in:
zhenyi
2026-06-10 18:49:32 +08:00
parent cec6dce955
commit 420dedbc1e
100 changed files with 3797 additions and 3839 deletions
+17 -25
View File
@@ -192,13 +192,13 @@ impl AuthService {
let Some(totp_key) = context.get::<String>(Self::TOTP_KEY).ok().flatten() else {
return Ok(false);
};
let Some(user_uid) = self.ctx.cache.get::<Uuid>(&totp_key) else {
let Some(user_uid) = self.ctx.cache.get::<Uuid>(&totp_key).await else {
context.remove(Self::TOTP_KEY);
return Ok(false);
};
if user_uid != expected_user_uid {
context.remove(Self::TOTP_KEY);
let _ = self.ctx.cache.delete(&totp_key);
let _ = self.ctx.cache.delete(&totp_key).await;
tracing::warn!(expected_user_uid = %expected_user_uid, pending_user_uid = %user_uid, "2FA pending user mismatch");
return Ok(false);
}
@@ -206,7 +206,7 @@ impl AuthService {
let verified = self.auth_2fa_verify(user_uid, code).await?;
if verified {
context.remove(Self::TOTP_KEY);
let _ = self.ctx.cache.delete(&totp_key);
let _ = self.ctx.cache.delete(&totp_key).await;
}
Ok(verified)
}
@@ -349,7 +349,7 @@ impl AuthService {
async fn verify_user_password(&self, user_uid: Uuid, password: &str) -> Result<(), AppError> {
let row = sqlx::query("SELECT password_hash FROM user_password WHERE user_id = $1")
.bind(user_uid)
.fetch_optional(self.ctx.db.reader())
.fetch_optional(self.ctx.db.writer())
.await
.map_err(AppError::Database)?
.ok_or(AppError::UserNotFound)?;
@@ -368,7 +368,7 @@ impl AuthService {
FROM user_2fa WHERE user_id = $1",
)
.bind(user_uid)
.fetch_optional(self.ctx.db.reader())
.fetch_optional(self.ctx.db.writer())
.await
.map_err(AppError::Database)
}
@@ -384,26 +384,18 @@ impl AuthService {
}
let hashed_code = self.hash_backup_code(code)?;
let mut backup_codes: Vec<String> = two_fa
.backup_codes
.split('.')
.filter(|c| !c.is_empty())
.map(ToOwned::to_owned)
.collect();
if backup_codes
.iter()
.any(|stored| constant_time_eq(stored, &hashed_code))
{
backup_codes.retain(|stored| stored != &hashed_code);
sqlx::query(
"UPDATE user_2fa SET backup_codes = $1, updated_at = $2 WHERE user_id = $3",
)
.bind(backup_codes.join("."))
.bind(chrono::Utc::now())
.bind(two_fa.user_id)
.execute(self.ctx.db.writer())
.await
.map_err(AppError::Database)?;
let result = sqlx::query(
"UPDATE user_2fa SET backup_codes = regexp_replace(backup_codes, $1, ''), updated_at = $2 \
WHERE user_id = $3 AND backup_codes LIKE '%' || $1 || '%'",
)
.bind(&hashed_code)
.bind(chrono::Utc::now())
.bind(two_fa.user_id)
.execute(self.ctx.db.writer())
.await
.map_err(AppError::Database)?;
if result.rows_affected() > 0 {
return Ok(true);
}
Ok(false)