/* * Copyright (C) 2022 Aravinth Manivannan * * 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 . */ use actix_identity::Identity; use actix_web::{web, HttpRequest, HttpResponse, Responder}; use serde::{Deserialize, Serialize}; use tracing::info; use url::Url; use super::get_auth_middleware; use crate::{errors::*, AppCtx}; pub mod routes { use crate::ctx::Ctx; pub struct Gitea { pub add_webhook: &'static str, pub view_webhook: &'static str, pub list_webhooks: &'static str, pub webhook: &'static str, } impl Gitea { pub const fn new() -> Self { Self { add_webhook: "/api/v1/gitea/webhook/add", list_webhooks: "/api/v1/gitea/webhook/add", view_webhook: "/api/v1/gitea/webhook/view/{auth_token}", webhook: "/api/v1/gitea/webhook/event/new", } } pub fn get_view(&self, auth_token: &str) -> String { self.view_webhook.replace("{auth_token}", auth_token) } pub fn get_webhook_url(&self, ctx: &Ctx, auth_token: &str) -> String { format!( "https://{}{}?auth={auth_token}", &ctx.settings.server.domain, self.webhook ) } } } #[derive(Serialize, Deserialize)] pub struct AddWebhook { pub gitea_url: Url, } #[actix_web_codegen_const_routes::post( path = "crate::V1_API_ROUTES.gitea.add_webhook", wrap = "get_auth_middleware()" )] #[tracing::instrument(name = "Add webhook" skip(id, ctx, payload))] async fn add_webhook( ctx: AppCtx, id: Identity, payload: web::Json, ) -> ServiceResult { info!( "Adding webhook for Gitea instance: {}", payload.gitea_url.as_str() ); let owner = id.identity().unwrap(); let payload = payload.into_inner(); let hook = ctx.db.new_webhook(payload.gitea_url, &owner).await?; Ok(HttpResponse::Ok().json(hook)) } #[actix_web_codegen_const_routes::get( path = "crate::V1_API_ROUTES.gitea.list_webhooks", wrap = "get_auth_middleware()" )] #[tracing::instrument(name = "Delete webhook" skip(id, ctx))] async fn list_webhooks(ctx: AppCtx, id: Identity) -> ServiceResult { let owner = id.identity().unwrap(); info!("Getting all webhooks created by {}", owner); let hooks = ctx.db.list_all_webhooks_with_owner(&owner).await?; Ok(HttpResponse::Ok().json(hooks)) } #[actix_web_codegen_const_routes::get( path = "crate::V1_API_ROUTES.gitea.view_webhook", wrap = "get_auth_middleware()" )] #[tracing::instrument(name = "Delete webhook" skip(id, ctx, path))] async fn view_webhook( ctx: AppCtx, id: Identity, path: web::Path, ) -> ServiceResult { let path = path.into_inner(); let owner = id.identity().unwrap(); info!("Gitting webhook webhook for Gitea instance: {}", path,); let hook = ctx.db.get_webhook_with_owner(&path, &owner).await?; Ok(HttpResponse::Ok().json(hook)) } #[derive(Serialize, Deserialize)] struct Auth { auth: String, } #[actix_web_codegen_const_routes::post(path = "crate::V1_API_ROUTES.gitea.webhook")] #[tracing::instrument(name = "Update ", skip(body, ctx, req, q))] async fn webhook( ctx: AppCtx, body: web::Bytes, req: HttpRequest, q: web::Query, ) -> ServiceResult { ctx.process_webhook(&body, &req, &q.auth).await?; Ok(HttpResponse::Ok()) } pub fn services(cfg: &mut web::ServiceConfig) { cfg.service(add_webhook); cfg.service(view_webhook); cfg.service(list_webhooks); cfg.service(webhook); } #[cfg(test)] mod tests { use actix_web::{http::StatusCode, test}; use crate::db::GiteaWebhook; use crate::tests; use crate::*; use super::*; #[actix_rt::test] async fn test_api_gitea_webhook() { const NAME: &str = "apigiteawebhookuser"; const PASSWORD: &str = "longpasswordasdfa2"; const EMAIL: &str = "apigiteawebhookuser@a.com"; let (_dir, ctx) = tests::get_ctx().await; let _ = ctx.delete_user(NAME, PASSWORD).await; let (_, signin_resp) = ctx.register_and_signin(NAME, EMAIL, PASSWORD).await; let cookies = get_cookie!(signin_resp); let app = get_app!(ctx).await; let payload = AddWebhook { gitea_url: Url::parse("https://git.batnsense.net").unwrap(), }; let add_webhook_resp = test::call_service( &app, post_request!(&payload, V1_API_ROUTES.gitea.add_webhook) .cookie(cookies.clone()) .to_request(), ) .await; check_status!(add_webhook_resp, StatusCode::OK); let response: GiteaWebhook = actix_web::test::read_body_json(add_webhook_resp).await; assert_eq!(response.gitea_url, payload.gitea_url); let view_webhook_resp = get_request!( &app, &V1_API_ROUTES.gitea.get_view(&response.auth_token), cookies.clone() ); check_status!(view_webhook_resp, StatusCode::OK); let hook: GiteaWebhook = actix_web::test::read_body_json(view_webhook_resp).await; assert_eq!(hook, response); let list_all_webhooks_resp = get_request!(&app, &V1_API_ROUTES.gitea.list_webhooks, cookies.clone()); check_status!(list_all_webhooks_resp, StatusCode::OK); let hooks: Vec = actix_web::test::read_body_json(list_all_webhooks_resp).await; assert_eq!(vec![hook], hooks); } }