feat: fetch and store users and yell about new users
This commit is contained in:
parent
f78379902f
commit
2dc4545258
2 changed files with 188 additions and 0 deletions
6
migrations/20221115201707_admin_users.sql
Normal file
6
migrations/20221115201707_admin_users.sql
Normal 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
182
src/main.rs
Normal 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(())
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue