Skip to content

Commit

Permalink
Added cron to update github profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
0xMimir committed Nov 22, 2023
1 parent 49a0f3c commit 2aaf33b
Show file tree
Hide file tree
Showing 11 changed files with 240 additions and 2 deletions.
18 changes: 18 additions & 0 deletions backend/api/src/jobs/github_project_info/contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
use error::Result;
use store::github_projects;

#[async_trait]
pub trait DbRepositoryContract {
///
/// Get github projects from db
///
async fn get_projects(&self) -> Result<Vec<github_projects::Model>>;
}

#[async_trait]
pub trait DbServiceContract {
///
/// Update github project in db
///
async fn update_project(&self, project: github_projects::ActiveModel) -> Result<()>;
}
94 changes: 94 additions & 0 deletions backend/api/src/jobs/github_project_info/domain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
use std::time::Duration;

use super::contract::{DbRepositoryContract, DbServiceContract};
use cronus::{Job, Schedule};
use error::Result;
use sdks::github::GithubContract;
use sea_orm::Set;
use store::github_projects::{ActiveModel, Model};
use tokio::time::sleep;

pub struct GithubProjectInfo<
Repository: DbRepositoryContract,
Service: DbServiceContract,
Github: GithubContract,
> {
repository: Repository,
service: Service,
github: Github,
}

impl<
Repository: DbRepositoryContract + Send + Sync + 'static,
Service: DbServiceContract + Send + Sync + 'static,
Github: GithubContract + Send + Sync + 'static,
> GithubProjectInfo<Repository, Service, Github>
{
///
/// Creates GithubRepositoriesCron
///
pub fn new(repository: Repository, service: Service, github: Github) -> Self {
Self {
repository,
service,
github,
}
}

///
/// Cron job that runs once a week
///
pub async fn cron_job(&self) -> Result<()> {
let projects = self.repository.get_projects().await?.into_iter();

for project in projects {
if let Err(error) = self.update_project(project).await {
error!("{}", error);
}
}
Ok(())
}

async fn update_project(&self, model: Model) -> Result<()> {
for _ in 0..5 {
match self.github.get_profile(&model.name).await {
Ok(profile) => {
let project = ActiveModel {
url: Set(profile.site),
followers: Set(profile.followers),
profile_type: Set(Some(profile.profile_type.to_string())),
..model.into()
};

self.service.update_project(project).await?;

break;
}
Err(error) => {
error!("{}", error);
}
};

sleep(Duration::from_secs(60)).await;
}

Ok(())
}
}

#[async_trait]
impl<Repository, Service, Github> Job for GithubProjectInfo<Repository, Service, Github>
where
Repository: DbRepositoryContract + Send + Sync + 'static,
Service: DbServiceContract + Send + Sync + 'static,
Github: GithubContract + Send + Sync + 'static,
{
fn schedule(&self) -> Schedule {
"0 0 0 * * Mon".parse().expect("Invalid schedule")
}
async fn job(&self) {
if let Err(error) = self.cron_job().await {
error!("{}", error);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod repository;
mod service;

pub use repository::PgRepository;
pub use service::PgService;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
use error::Result;
use sea_orm::{DatabaseConnection, EntityTrait, Order, QueryOrder};
use std::sync::Arc;

use super::super::contract::DbRepositoryContract;
use store::github_projects::{Column, Entity, Model};

pub struct PgRepository {
conn: Arc<DatabaseConnection>,
}

impl PgRepository {
pub fn new(conn: Arc<DatabaseConnection>) -> Self {
Self { conn }
}
}

#[async_trait]
impl DbRepositoryContract for PgRepository {
async fn get_projects(&self) -> Result<Vec<Model>> {
let projects = Entity::find()
.order_by(Column::Name, Order::Asc)
.all(self.conn.as_ref())
.await?;

Ok(projects)
}
}
27 changes: 27 additions & 0 deletions backend/api/src/jobs/github_project_info/infrastructure/service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use error::Result;
use sea_orm::{DatabaseConnection, EntityTrait};
use std::sync::Arc;

use super::super::contract::DbServiceContract;
use store::github_projects;

pub struct PgService {
conn: Arc<DatabaseConnection>,
}

impl PgService {
pub fn new(conn: Arc<DatabaseConnection>) -> Self {
Self { conn }
}
}

#[async_trait]
impl DbServiceContract for PgService {
async fn update_project(&self, project: github_projects::ActiveModel) -> Result<()> {
github_projects::Entity::update(project)
.exec(self.conn.as_ref())
.await?;

Ok(())
}
}
37 changes: 37 additions & 0 deletions backend/api/src/jobs/github_project_info/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
mod contract;
mod domain;
pub mod infrastructure;


use cronus::Cronus;
pub use domain::GithubProjectInfo;
use infrastructure::{PgRepository, PgService};
use sdks::github::Github;
use sea_orm::DatabaseConnection;
use std::sync::Arc;

///
/// Create and spawn github repositories job
///
pub fn setup(cron: &Cronus, sea_pool: Arc<DatabaseConnection>) {
let job = create_gr(sea_pool);
cron.add(job).expect("Error adding job");
}

///
/// Create GithubRepositoriesCron with default implementations
///
fn create_gr(
sea_pool: Arc<DatabaseConnection>,
) -> GithubProjectInfo<PgRepository, PgService, Github> {
let repository = PgRepository::new(sea_pool.clone());
let service = PgService::new(sea_pool);
let github_api_key = config::get("GITHUB_KEY").ok();

let github = match github_api_key {
Some(api_key) => Github::new_with_auth(api_key),
None => Github::default(),
};

GithubProjectInfo::new(repository, service, github)
}
2 changes: 2 additions & 0 deletions backend/api/src/jobs/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ pub mod github_repositories;
pub mod info;

mod github_issues;
mod github_project_info;

pub fn setup(sea_pool: Arc<DatabaseConnection>) -> Cronus {
let cron = Cronus::new();

info::setup(&cron, sea_pool.clone());
github_repositories::setup(&cron, sea_pool.clone());
github_project_info::setup(&cron, sea_pool.clone());
github_issues::setup(&cron, sea_pool);

cron
Expand Down
1 change: 1 addition & 0 deletions backend/libs/sdks/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ log.workspace = true
reqwest.workspace = true
serde.workspace = true
serde_json.workspace = true
strum = { version = "0.25.0", features = ["derive"] }

[dev-dependencies]
config.workspace = true
Expand Down
7 changes: 6 additions & 1 deletion backend/libs/sdks/src/github/contract.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use error::Result;

use super::data::{GithubIssue, GithubRepository, RateLimit};
use super::data::{GithubIssue, GithubRepository, RateLimit, ProfileInfo};

#[async_trait]
pub trait GithubContract {
Expand All @@ -23,4 +23,9 @@ pub trait GithubContract {
/// Get rate limit
///
async fn get_rate_limit(&self) -> Result<RateLimit>;

///
/// Get user profile
///
async fn get_profile(&self, username: &str) -> Result<ProfileInfo>;
}
16 changes: 16 additions & 0 deletions backend/libs/sdks/src/github/data.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use chrono::NaiveDateTime;
use error::Error;
use serde::{de::Error as DeError, Deserialize, Deserializer};
use strum::{Display, EnumString};

#[derive(Deserialize, Debug)]
pub struct GithubRepository {
Expand Down Expand Up @@ -76,3 +77,18 @@ pub struct RateLimit {
pub remaining: u64,
pub reset: i64,
}

#[derive(Deserialize, Debug)]
pub struct ProfileInfo {
#[serde(rename = "type")]
pub profile_type: ProfileType,
pub followers: i64, // u64 but postgres doesn't support unsigned numbers,
#[serde(rename = "blog")]
pub site: Option<String>,
}

#[derive(Deserialize, Debug, Display, EnumString)]
pub enum ProfileType {
User,
Organization,
}
7 changes: 6 additions & 1 deletion backend/libs/sdks/src/github/domain.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::{
data::{ErrorResponse, GithubIssue, GithubRepository, RateLimit, RateLimitResponse},
data::{ErrorResponse, GithubIssue, GithubRepository, RateLimit, RateLimitResponse, ProfileInfo},
GithubContract,
};
use error::{Error, Result};
Expand Down Expand Up @@ -83,6 +83,11 @@ impl GithubContract for Github {
let response: RateLimitResponse = self.get("https://api.github.com/rate_limit").await?;
Ok(response.rate)
}

async fn get_profile(&self, username: &str) -> Result<ProfileInfo>{
let url = format!("https://api.github.com/users/{username}");
self.get(url).await.map_err(|e| e.add_cause(username))
}
}

impl Github {
Expand Down

0 comments on commit 2aaf33b

Please sign in to comment.