1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +03:00

Schedule jobs through the repository

This commit is contained in:
Quentin Gliech
2023-03-31 15:51:09 +02:00
parent cdd535ddc4
commit 1f748f7d1e
18 changed files with 305 additions and 84 deletions

146
crates/storage/src/job.rs Normal file
View File

@ -0,0 +1,146 @@
// Copyright 2023 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Repository to schedule persistent jobs.
pub use apalis_core::job::{Job, JobId};
use async_trait::async_trait;
use serde::Serialize;
use serde_json::Value;
use crate::repository_impl;
/// A job submission to be scheduled through the repository.
pub struct JobSubmission {
name: &'static str,
payload: Value,
}
impl JobSubmission {
/// Create a new job submission out of a [`Job`].
///
/// # Panics
///
/// Panics if the job cannot be serialized.
#[must_use]
pub fn new<J: Job + Serialize>(job: J) -> Self {
Self {
name: J::NAME,
payload: serde_json::to_value(job).expect("failed to serialize job"),
}
}
/// The name of the job.
#[must_use]
pub fn name(&self) -> &'static str {
self.name
}
/// The payload of the job.
#[must_use]
pub fn payload(&self) -> &Value {
&self.payload
}
}
/// A [`JobRepository`] is used to schedule jobs to be executed by a worker.
#[async_trait]
pub trait JobRepository: Send + Sync {
/// The error type returned by the repository.
type Error;
/// Schedule a job submission to be executed at a later time.
///
/// # Parameters
///
/// * `submission` - The job to schedule.
///
/// # Errors
///
/// Returns [`Self::Error`] if the underlying repository fails
async fn schedule_submission(
&mut self,
submission: JobSubmission,
) -> Result<JobId, Self::Error>;
}
repository_impl!(JobRepository:
async fn schedule_submission(&mut self, submission: JobSubmission) -> Result<JobId, Self::Error>;
);
/// An extension trait for [`JobRepository`] to schedule jobs directly.
#[async_trait]
pub trait JobRepositoryExt {
/// The error type returned by the repository.
type Error;
/// Schedule a job to be executed at a later time.
///
/// # Parameters
///
/// * `job` - The job to schedule.
///
/// # Errors
///
/// Returns [`Self::Error`] if the underlying repository fails
async fn schedule_job<J: Job + Serialize>(&mut self, job: J) -> Result<JobId, Self::Error>;
}
#[async_trait]
impl<T> JobRepositoryExt for T
where
T: JobRepository + ?Sized,
{
type Error = T::Error;
async fn schedule_job<J: Job + Serialize>(&mut self, job: J) -> Result<JobId, Self::Error> {
self.schedule_submission(JobSubmission::new(job)).await
}
}
mod jobs {
// XXX: Move this somewhere else?
use apalis_core::job::Job;
use mas_data_model::UserEmail;
use serde::{Deserialize, Serialize};
use ulid::Ulid;
/// A job to verify an email address.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct VerifyEmailJob {
user_email_id: Ulid,
}
impl VerifyEmailJob {
/// Create a new job to verify an email address.
#[must_use]
pub fn new(user_email: &UserEmail) -> Self {
Self {
user_email_id: user_email.id,
}
}
/// The ID of the email address to verify.
#[must_use]
pub fn user_email_id(&self) -> Ulid {
self.user_email_id
}
}
impl Job for VerifyEmailJob {
const NAME: &'static str = "verify-email";
}
}
pub use self::jobs::VerifyEmailJob;

View File

@ -150,6 +150,7 @@ pub(crate) mod repository;
mod utils;
pub mod compat;
pub mod job;
pub mod oauth2;
pub mod upstream_oauth2;
pub mod user;

View File

@ -20,6 +20,7 @@ use crate::{
CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository,
CompatSsoLoginRepository,
},
job::JobRepository,
oauth2::{
OAuth2AccessTokenRepository, OAuth2AuthorizationGrantRepository, OAuth2ClientRepository,
OAuth2RefreshTokenRepository, OAuth2SessionRepository,
@ -192,6 +193,9 @@ pub trait RepositoryAccess: Send {
fn compat_refresh_token<'c>(
&'c mut self,
) -> Box<dyn CompatRefreshTokenRepository<Error = Self::Error> + 'c>;
/// Get a [`JobRepository`]
fn job<'c>(&'c mut self) -> Box<dyn JobRepository<Error = Self::Error> + 'c>;
}
/// Implementations of the [`RepositoryAccess`], [`RepositoryTransaction`] and
@ -205,6 +209,7 @@ mod impls {
CompatAccessTokenRepository, CompatRefreshTokenRepository, CompatSessionRepository,
CompatSsoLoginRepository,
},
job::JobRepository,
oauth2::{
OAuth2AccessTokenRepository, OAuth2AuthorizationGrantRepository,
OAuth2ClientRepository, OAuth2RefreshTokenRepository, OAuth2SessionRepository,
@ -373,6 +378,10 @@ mod impls {
&mut self.mapper,
))
}
fn job<'c>(&'c mut self) -> Box<dyn JobRepository<Error = Self::Error> + 'c> {
Box::new(MapErr::new(self.inner.job(), &mut self.mapper))
}
}
impl<R: RepositoryAccess + ?Sized> RepositoryAccess for Box<R> {
@ -469,5 +478,9 @@ mod impls {
) -> Box<dyn CompatRefreshTokenRepository<Error = Self::Error> + 'c> {
(**self).compat_refresh_token()
}
fn job<'c>(&'c mut self) -> Box<dyn JobRepository<Error = Self::Error> + 'c> {
(**self).job()
}
}
}