feat: fetch and store users and yell about new users

This commit is contained in:
Aravinth Manivannan 2022-11-16 02:15:31 +05:30
parent f78379902f
commit 2dc4545258
Signed by: realaravinth
GPG Key ID: AD9F0F08E855ED88
2 changed files with 188 additions and 0 deletions

View File

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS admin_users(
is_admin BOOLEAN NOT NULL,
ID INTEGER PRIMARY KEY NOT NULL,
login TEXT NOT NULL UNIQUE,
created TEXT NOT NULL
);

182
src/main.rs Normal file
View File

@ -0,0 +1,182 @@
/*
* Admin Tools - Gitea admin tools to deal with spammers.
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use std::str::FromStr;
use std::env;
use std::error::Error;
use tracing::*;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use sqlx::sqlite::SqlitePool;
use sqlx::ConnectOptions;
use url::Url;
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)]
pub struct User {
id: i64,
login: String,
created: String,
is_admin: bool,
}
pub struct Gitea {
base_url: Url,
admin_auth: String,
client: Client,
}
type MyResult<T> = Result<T, Box<dyn Error>>;
impl Gitea {
async fn is_admin(&self) -> MyResult<bool> {
let mut url = self.base_url.clone();
url.set_path("/api/v1/user");
let user: User = self
.client
.get(url)
.bearer_auth(&self.admin_auth)
.send()
.await?
.json()
.await?;
Ok(user.is_admin)
}
pub fn new(base_url: Url, admin_auth: String) -> Self {
Self {
base_url,
admin_auth,
client: Client::new(),
}
}
pub fn from_env() -> MyResult<Self> {
let admin_auth = env::var("AUTH")?;
let base_url = env::var("GITEA_URL")?;
let base_url = Url::parse(&base_url)?;
Ok(Self::new(base_url, admin_auth))
}
pub async fn list_all_users(&self, page: usize, limit: usize) -> MyResult<Vec<User>> {
let mut url = self.base_url.clone();
url.set_path("/api/v1/admin/users");
url.set_query(Some(&format!("page={page}&limit={limit}")));
let u: Vec<User> = self
.client
.get(url)
.bearer_auth(&self.admin_auth)
.send()
.await?
.json()
.await?;
Ok(u)
}
}
#[tokio::main]
async fn main() -> MyResult<()> {
if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", "info");
}
pretty_env_logger::init();
let gitea = Gitea::from_env()?;
info!("Starting run. Gitea instance: {}", &gitea.base_url);
if !gitea.is_admin().await? {
error!("User isn't admin");
}
let db = DB::from_env().await?;
let mut page = 0;
let limit = 50;
let mut new_users = 0;
loop {
let users = gitea.list_all_users(page, limit).await?;
if users.len() == 0 {
break;
}
page += 1;
for u in users.iter() {
if !db.user_exists(&u.login).await? {
warn!("New user {} created on {}", u.login, u.created);
db.add_user(u).await?;
new_users += 1;
}
}
}
if new_users > 0 {
info!("New users: {new_users}");
} else {
info!("No new users");
}
Ok(())
}
pub struct DB {
db: SqlitePool,
}
impl DB {
async fn new(url: &str) -> MyResult<Self> {
let mut connect_options = sqlx::sqlite::SqliteConnectOptions::from_str(url)?;
connect_options.disable_statement_logging();
let pool = sqlx::sqlite::SqlitePoolOptions::new()
.connect_with(connect_options)
.await?;
Ok(Self { db: pool })
}
async fn from_env() -> MyResult<Self> {
let db_url = env::var("DATABASE_URL")?;
Self::new(&db_url).await
}
async fn user_exists(&self, login: &str) -> MyResult<bool> {
match sqlx::query!("SELECT ID FROM admin_users WHERE login = $1", login)
.fetch_one(&self.db)
.await
{
Ok(_) => Ok(true),
Err(sqlx::Error::RowNotFound) => Ok(false),
Err(e) => Err(e.into()),
}
}
async fn add_user(&self, user: &User) -> MyResult<()> {
sqlx::query!(
"INSERT INTO admin_users (ID, login, is_admin, created) VALUES
($1, $2, $3, $4);",
user.id,
user.login,
user.is_admin,
user.created
)
.execute(&self.db)
.await?;
Ok(())
}
}