183 lines
4.7 KiB
Rust
183 lines
4.7 KiB
Rust
/*
|
|
* 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(())
|
|
}
|
|
}
|