feat: auth hardening + codebase quality sweep
Refresh tokens: RefreshToken entity, PostgresRefreshTokenRepository, login returns refresh token, POST /auth/refresh (rotation), POST /auth/logout, JWT expiry 24h→1h, configurable via with_expiry(). Route protection: require_auth middleware on protected routes, public routes split (register, login, refresh, sharing/access). Authorization: caller_id added to ReadAssetFileQuery, ReadDerivativeQuery, GetStackQuery, DeleteStackCommand with ownership checks. Admin-only gates on processing, storage, sidecar, duplicates handlers. Quality fixes: visibility filtering bypass in search(), unwrap panics in date parsing, DRY auth header parsing, centralized parsers module, email validation via email_address crate, value objects (Username, MimeType, RelativePath), domain events (UserCreated, UserDeleted, AlbumCreated, TagCreated, DuplicateDetected), postgres error mapping for constraint violations, OptionExt::or_not_found helper, in_memory_repo! macro, GetStackQuery moved to queries, album add_entry 200→201.
This commit is contained in:
@@ -154,6 +154,59 @@ impl JobRepository for PostgresJobRepository {
|
||||
Ok(row.map(Into::into))
|
||||
}
|
||||
|
||||
async fn find_all(
|
||||
&self,
|
||||
status: Option<&str>,
|
||||
limit: u32,
|
||||
offset: u32,
|
||||
) -> Result<Vec<Job>, DomainError> {
|
||||
let rows = match status {
|
||||
Some(s) => sqlx::query_as::<_, JobRow>(
|
||||
"SELECT job_id, job_type, target_asset_id, batch_id, status, priority,
|
||||
payload, result_data, retry_count, max_retries, created_at,
|
||||
started_at, completed_at, error_message
|
||||
FROM jobs WHERE status = $1
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $2 OFFSET $3",
|
||||
)
|
||||
.bind(s)
|
||||
.bind(limit as i64)
|
||||
.bind(offset as i64)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_pg()?,
|
||||
None => sqlx::query_as::<_, JobRow>(
|
||||
"SELECT job_id, job_type, target_asset_id, batch_id, status, priority,
|
||||
payload, result_data, retry_count, max_retries, created_at,
|
||||
started_at, completed_at, error_message
|
||||
FROM jobs
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1 OFFSET $2",
|
||||
)
|
||||
.bind(limit as i64)
|
||||
.bind(offset as i64)
|
||||
.fetch_all(&self.pool)
|
||||
.await
|
||||
.map_pg()?,
|
||||
};
|
||||
Ok(rows.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
||||
async fn count(&self, status: Option<&str>) -> Result<u64, DomainError> {
|
||||
let count: (i64,) = match status {
|
||||
Some(s) => sqlx::query_as("SELECT COUNT(*) FROM jobs WHERE status = $1")
|
||||
.bind(s)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_pg()?,
|
||||
None => sqlx::query_as("SELECT COUNT(*) FROM jobs")
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_pg()?,
|
||||
};
|
||||
Ok(count.0 as u64)
|
||||
}
|
||||
|
||||
async fn find_by_batch(&self, batch_id: &SystemId) -> Result<Vec<Job>, DomainError> {
|
||||
let rows = sqlx::query_as::<_, JobRow>(
|
||||
"SELECT job_id, job_type, target_asset_id, batch_id, status, priority,
|
||||
|
||||
Reference in New Issue
Block a user