Compare commits
No commits in common. "926cf3fe0867a8325a6ebf0de5bab2b8d22d137d" and "c86ca3467f57f1a1a03ba344177dc07fea85dd3e" have entirely different histories.
926cf3fe08
...
c86ca3467f
7
.github/workflows/coverage.yml
vendored
7
.github/workflows/coverage.yml
vendored
|
@ -62,13 +62,6 @@ jobs:
|
||||||
profile: minimal
|
profile: minimal
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
- uses: actions/setup-node@v2
|
|
||||||
with:
|
|
||||||
node-version: "16.x"
|
|
||||||
|
|
||||||
- name: download deps
|
|
||||||
run: make dev-env
|
|
||||||
|
|
||||||
- name: Apply migrations
|
- name: Apply migrations
|
||||||
run: make migrate
|
run: make migrate
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -4,6 +4,15 @@ allow_registration = true
|
||||||
source_code = "https://github.com/realaravinth/pages"
|
source_code = "https://github.com/realaravinth/pages"
|
||||||
support_email = "support@librepages.example.org"
|
support_email = "support@librepages.example.org"
|
||||||
|
|
||||||
|
# To deploy a website from a Git repository, please provide the following details:
|
||||||
|
# 1. branch: the branch in the Git repository which contains the website files
|
||||||
|
# 2. repo: the public readonly/clonable URL of the website repository
|
||||||
|
# 3. path: the directory where you'd like Pages to clone the specified repository
|
||||||
|
# 3. secret: a unique secret which is used to authenticate webhook calls
|
||||||
|
pages = [
|
||||||
|
{ branch = "gh-pages", domain="local.mcaptcha.org", repo = "https://github.com/mCaptcha/website/", path ="/tmp/pages/mcaptcha/website", secret = "faee1b650ac586068a54cb160bd6353c5e16be7c64b49113fe57726e5393" },
|
||||||
|
]
|
||||||
|
|
||||||
[server]
|
[server]
|
||||||
# The port at which you want Pages to listen to
|
# The port at which you want Pages to listen to
|
||||||
port = 7000
|
port = 7000
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
CREATE TABLE IF NOT EXISTS librepages_sites (
|
CREATE TABLE IF NOT EXISTS librepages_sites (
|
||||||
site_secret VARCHAR(32) NOT NULL UNIQUE,
|
site_secret VARCHAR(32) NOT NULL UNIQUE,
|
||||||
repo_url VARCHAR(3000) NOT NULL,
|
repo_url VARCHAR(3000) NOT NULL UNIQUE,
|
||||||
branch TEXT NOT NULL,
|
branch TEXT NOT NULL,
|
||||||
hostname VARCHAR(3000) NOT NULL UNIQUE,
|
hostname VARCHAR(3000) NOT NULL UNIQUE,
|
||||||
ID SERIAL PRIMARY KEY NOT NULL,
|
ID SERIAL PRIMARY KEY NOT NULL,
|
||||||
|
|
|
@ -77,26 +77,6 @@
|
||||||
},
|
},
|
||||||
"query": "UPDATE librepages_users set name = $1\n WHERE name = $2"
|
"query": "UPDATE librepages_users set name = $1\n WHERE name = $2"
|
||||||
},
|
},
|
||||||
"3705b8869aab99d749c08d9c9633931f0b74216957b6a2881bd56a33c33a8c47": {
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "exists",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Bool"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"nullable": [
|
|
||||||
null
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"query": "SELECT EXISTS (SELECT 1 from librepages_sites WHERE hostname = $1)"
|
|
||||||
},
|
|
||||||
"416b9f0412f0d7ee05d4a350839c5a6d1e06c1d7f8942744f6d806ddc47084c2": {
|
"416b9f0412f0d7ee05d4a350839c5a6d1e06c1d7f8942744f6d806ddc47084c2": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
|
|
|
@ -16,7 +16,6 @@
|
||||||
*/
|
*/
|
||||||
pub mod account;
|
pub mod account;
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod pages;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 actix_web::web;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use tokio::sync::oneshot;
|
|
||||||
|
|
||||||
use crate::ctx::Ctx;
|
|
||||||
use crate::db::Site;
|
|
||||||
use crate::errors::*;
|
|
||||||
use crate::page::Page;
|
|
||||||
use crate::utils::get_random;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize, Eq, PartialEq)]
|
|
||||||
/// Data required to add site
|
|
||||||
pub struct AddSite {
|
|
||||||
pub repo_url: String,
|
|
||||||
pub branch: String,
|
|
||||||
pub hostname: String,
|
|
||||||
pub owner: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AddSite {
|
|
||||||
fn to_site(self) -> Site {
|
|
||||||
let site_secret = get_random(32);
|
|
||||||
Site {
|
|
||||||
site_secret,
|
|
||||||
repo_url: self.repo_url,
|
|
||||||
branch: self.branch,
|
|
||||||
hostname: self.hostname,
|
|
||||||
owner: self.owner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ctx {
|
|
||||||
pub async fn add_site(&self, site: AddSite) -> ServiceResult<()> {
|
|
||||||
let db_site = site.to_site();
|
|
||||||
self.db.add_site(&db_site).await?;
|
|
||||||
let page = Page::from_site(&self.settings, db_site);
|
|
||||||
page.update(&page.branch)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn update_site(&self, secret: &str, branch: Option<String>) -> ServiceResult<()> {
|
|
||||||
if let Ok(db_site) = self.db.get_site_from_secret(secret).await {
|
|
||||||
let page = Page::from_site(&self.settings, db_site);
|
|
||||||
let (tx, rx) = oneshot::channel();
|
|
||||||
let page = page.clone();
|
|
||||||
web::block(move || {
|
|
||||||
if let Some(branch) = branch {
|
|
||||||
tx.send(page.update(&branch)).unwrap();
|
|
||||||
} else {
|
|
||||||
tx.send(page.update(&page.branch)).unwrap();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
rx.await.unwrap()?;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err(ServiceError::WebsiteNotFound)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
39
src/db.rs
39
src/db.rs
|
@ -288,7 +288,7 @@ impl Database {
|
||||||
)
|
)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| map_row_not_found_err(e, ServiceError::WebsiteNotFound))?;
|
.map_err(|e| map_row_not_found_err(e, ServiceError::AccountNotFound))?;
|
||||||
|
|
||||||
struct Owner {
|
struct Owner {
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -300,7 +300,7 @@ impl Database {
|
||||||
)
|
)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| map_row_not_found_err(e, ServiceError::WebsiteNotFound))?;
|
.map_err(|e| map_row_not_found_err(e, ServiceError::AccountNotFound))?;
|
||||||
|
|
||||||
let site = Site {
|
let site = Site {
|
||||||
site_secret: site_secret.to_owned(),
|
site_secret: site_secret.to_owned(),
|
||||||
|
@ -326,7 +326,7 @@ impl Database {
|
||||||
)
|
)
|
||||||
.fetch_one(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| map_row_not_found_err(e, ServiceError::WebsiteNotFound))?;
|
.map_err(|e| map_row_not_found_err(e, ServiceError::AccountNotFound))?;
|
||||||
|
|
||||||
let res = site.to_site(owner.into());
|
let res = site.to_site(owner.into());
|
||||||
|
|
||||||
|
@ -362,27 +362,9 @@ impl Database {
|
||||||
)
|
)
|
||||||
.execute(&self.pool)
|
.execute(&self.pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| map_row_not_found_err(e, ServiceError::WebsiteNotFound))?;
|
.map_err(|e| map_row_not_found_err(e, ServiceError::AccountNotFound))?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// check if hostname exists
|
|
||||||
pub async fn hostname_exists(&self, hostname: &str) -> ServiceResult<bool> {
|
|
||||||
let res = sqlx::query!(
|
|
||||||
"SELECT EXISTS (SELECT 1 from librepages_sites WHERE hostname = $1)",
|
|
||||||
hostname,
|
|
||||||
)
|
|
||||||
.fetch_one(&self.pool)
|
|
||||||
.await
|
|
||||||
.map_err(map_register_err)?;
|
|
||||||
|
|
||||||
let mut resp = false;
|
|
||||||
if let Some(x) = res.exists {
|
|
||||||
resp = x;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(resp)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
struct InnerSite {
|
struct InnerSite {
|
||||||
site_secret: String,
|
site_secret: String,
|
||||||
|
@ -653,6 +635,7 @@ mod tests {
|
||||||
|
|
||||||
db.register(&p).await.unwrap();
|
db.register(&p).await.unwrap();
|
||||||
|
|
||||||
|
// testing adding site
|
||||||
let site = Site {
|
let site = Site {
|
||||||
site_secret: "foobar".into(),
|
site_secret: "foobar".into(),
|
||||||
repo_url: "https://git.batsense.net/LibrePages/librepages.git".into(),
|
repo_url: "https://git.batsense.net/LibrePages/librepages.git".into(),
|
||||||
|
@ -660,16 +643,8 @@ mod tests {
|
||||||
hostname: "db_works.tests.librepages.librepages.org".into(),
|
hostname: "db_works.tests.librepages.librepages.org".into(),
|
||||||
owner: p.username.into(),
|
owner: p.username.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// test if hostname exists. Should be false
|
|
||||||
assert!(!db.hostname_exists(&site.hostname).await.unwrap());
|
|
||||||
|
|
||||||
// testing adding site
|
|
||||||
db.add_site(&site).await.unwrap();
|
db.add_site(&site).await.unwrap();
|
||||||
|
|
||||||
// test if hostname exists. Should be true
|
|
||||||
assert!(db.hostname_exists(&site.hostname).await.unwrap());
|
|
||||||
|
|
||||||
// get site
|
// get site
|
||||||
let db_site = db.get_site(p.username, &site.hostname).await.unwrap();
|
let db_site = db.get_site(p.username, &site.hostname).await.unwrap();
|
||||||
assert_eq!(db_site, site);
|
assert_eq!(db_site, site);
|
||||||
|
@ -687,8 +662,6 @@ mod tests {
|
||||||
|
|
||||||
// delete site
|
// delete site
|
||||||
db.delete_site(p.username, &site.hostname).await.unwrap();
|
db.delete_site(p.username, &site.hostname).await.unwrap();
|
||||||
|
assert!(db.list_all_sites(p.username).await.unwrap().is_empty());
|
||||||
// test if hostname exists. Should be false
|
|
||||||
assert!(!db.hostname_exists(&site.hostname).await.unwrap());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,12 +44,30 @@ pub struct DeployEvent {
|
||||||
pub branch: String,
|
pub branch: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_page<'a>(secret: &str, ctx: &'a AppCtx) -> Option<&'a Page> {
|
||||||
|
for page in ctx.settings.pages.iter() {
|
||||||
|
if page.secret == secret {
|
||||||
|
return Some(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web_codegen_const_routes::post(path = "crate::V1_API_ROUTES.deploy.update")]
|
#[actix_web_codegen_const_routes::post(path = "crate::V1_API_ROUTES.deploy.update")]
|
||||||
async fn update(payload: web::Json<DeployEvent>, ctx: AppCtx) -> ServiceResult<impl Responder> {
|
async fn update(payload: web::Json<DeployEvent>, ctx: AppCtx) -> ServiceResult<impl Responder> {
|
||||||
let payload = payload.into_inner();
|
if let Some(page) = find_page(&payload.secret, &ctx) {
|
||||||
ctx.update_site(&payload.secret, Some(payload.branch))
|
let (tx, rx) = oneshot::channel();
|
||||||
.await?;
|
let page = page.clone();
|
||||||
Ok(HttpResponse::Ok())
|
web::block(move || {
|
||||||
|
tx.send(page.update(&payload.branch)).unwrap();
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
rx.await.unwrap()?;
|
||||||
|
Ok(HttpResponse::Ok())
|
||||||
|
} else {
|
||||||
|
Err(ServiceError::WebsiteNotFound)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
|
||||||
|
@ -85,9 +103,8 @@ async fn deploy_info(
|
||||||
payload: web::Json<DeploySecret>,
|
payload: web::Json<DeploySecret>,
|
||||||
ctx: AppCtx,
|
ctx: AppCtx,
|
||||||
) -> ServiceResult<impl Responder> {
|
) -> ServiceResult<impl Responder> {
|
||||||
if let Ok(page) = ctx.db.get_site_from_secret(&payload.secret).await {
|
if let Some(page) = find_page(&payload.secret, &ctx) {
|
||||||
// if let Some(page) = find_page(&payload.secret, &ctx) {
|
let resp = DeployInfo::from_page(page)?;
|
||||||
let resp = DeployInfo::from_page(&Page::from_site(&ctx.settings, page))?;
|
|
||||||
Ok(HttpResponse::Ok().json(resp))
|
Ok(HttpResponse::Ok().json(resp))
|
||||||
} else {
|
} else {
|
||||||
Err(ServiceError::WebsiteNotFound)
|
Err(ServiceError::WebsiteNotFound)
|
||||||
|
@ -110,21 +127,14 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn deploy_update_works() {
|
async fn deploy_update_works() {
|
||||||
const NAME: &str = "dplyupdwrkuser";
|
|
||||||
const PASSWORD: &str = "longpasswordasdfa2";
|
|
||||||
const EMAIL: &str = "dplyupdwrkuser@a.com";
|
|
||||||
|
|
||||||
let (_dir, ctx) = tests::get_ctx().await;
|
let (_dir, ctx) = tests::get_ctx().await;
|
||||||
let _ = ctx.delete_user(NAME, PASSWORD).await;
|
println!("[log] test configuration {:#?}", ctx.settings);
|
||||||
let (_, _signin_resp) = ctx.register_and_signin(NAME, EMAIL, PASSWORD).await;
|
|
||||||
let hostname = ctx.get_test_hostname(NAME);
|
|
||||||
ctx.add_test_site(NAME.into(), hostname.clone()).await;
|
|
||||||
let app = get_app!(ctx).await;
|
let app = get_app!(ctx).await;
|
||||||
|
let page = ctx.settings.pages.get(0);
|
||||||
let page = ctx.db.get_site(NAME, &hostname).await.unwrap();
|
let page = page.unwrap();
|
||||||
|
|
||||||
let mut payload = DeployEvent {
|
let mut payload = DeployEvent {
|
||||||
secret: page.site_secret.clone(),
|
secret: page.secret.clone(),
|
||||||
branch: page.branch.clone(),
|
branch: page.branch.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -147,20 +157,14 @@ mod tests {
|
||||||
|
|
||||||
#[actix_rt::test]
|
#[actix_rt::test]
|
||||||
async fn deploy_info_works() {
|
async fn deploy_info_works() {
|
||||||
const NAME: &str = "dplyinfwrkuser";
|
|
||||||
const PASSWORD: &str = "longpasswordasdfa2";
|
|
||||||
const EMAIL: &str = "dplyinfwrkuser@a.com";
|
|
||||||
|
|
||||||
let (_dir, ctx) = tests::get_ctx().await;
|
let (_dir, ctx) = tests::get_ctx().await;
|
||||||
let _ = ctx.delete_user(NAME, PASSWORD).await;
|
println!("[log] test configuration {:#?}", ctx.settings);
|
||||||
let (_, _signin_resp) = ctx.register_and_signin(NAME, EMAIL, PASSWORD).await;
|
let page = ctx.settings.pages.get(0);
|
||||||
let hostname = ctx.get_test_hostname(NAME);
|
let page = page.unwrap();
|
||||||
ctx.add_test_site(NAME.into(), hostname.clone()).await;
|
|
||||||
let app = get_app!(ctx).await;
|
let app = get_app!(ctx).await;
|
||||||
|
|
||||||
let page = ctx.db.get_site(NAME, &hostname).await.unwrap();
|
|
||||||
let mut payload = DeploySecret {
|
let mut payload = DeploySecret {
|
||||||
secret: page.site_secret.clone(),
|
secret: page.secret.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let resp = test::call_service(
|
let resp = test::call_service(
|
||||||
|
@ -173,7 +177,7 @@ mod tests {
|
||||||
|
|
||||||
let response: DeployInfo = actix_web::test::read_body_json(resp).await;
|
let response: DeployInfo = actix_web::test::read_body_json(resp).await;
|
||||||
assert_eq!(response.head, page.branch);
|
assert_eq!(response.head, page.branch);
|
||||||
assert_eq!(response.remote, page.repo_url);
|
assert_eq!(response.remote, page.repo);
|
||||||
|
|
||||||
payload.secret = page.branch.clone();
|
payload.secret = page.branch.clone();
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
use git2::*;
|
use git2::*;
|
||||||
use mime_guess::MimeGuess;
|
use mime_guess::MimeGuess;
|
||||||
|
@ -114,7 +113,7 @@ impl ContentType {
|
||||||
/// For example, a read request for "foo bar.md" will fail even if that file is present
|
/// For example, a read request for "foo bar.md" will fail even if that file is present
|
||||||
/// in the repository. However, it will succeed if the output of [escape_spaces] is
|
/// in the repository. However, it will succeed if the output of [escape_spaces] is
|
||||||
/// used in the request.
|
/// used in the request.
|
||||||
pub fn read_file(repo_path: &PathBuf, path: &str) -> ServiceResult<FileInfo> {
|
pub fn read_file(repo_path: &str, path: &str) -> ServiceResult<FileInfo> {
|
||||||
let repo = git2::Repository::open(repo_path).unwrap();
|
let repo = git2::Repository::open(repo_path).unwrap();
|
||||||
let head = repo.head().unwrap();
|
let head = repo.head().unwrap();
|
||||||
let tree = head.peel_to_tree().unwrap();
|
let tree = head.peel_to_tree().unwrap();
|
||||||
|
@ -122,7 +121,7 @@ pub fn read_file(repo_path: &PathBuf, path: &str) -> ServiceResult<FileInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_preview_file(
|
pub fn read_preview_file(
|
||||||
repo_path: &PathBuf,
|
repo_path: &str,
|
||||||
preview_name: &str,
|
preview_name: &str,
|
||||||
path: &str,
|
path: &str,
|
||||||
) -> ServiceResult<FileInfo> {
|
) -> ServiceResult<FileInfo> {
|
||||||
|
@ -280,12 +279,12 @@ pub mod tests {
|
||||||
const PATH: &str = "/tmp/librepges/test_git_write_read_works";
|
const PATH: &str = "/tmp/librepges/test_git_write_read_works";
|
||||||
|
|
||||||
write_file_util(PATH);
|
write_file_util(PATH);
|
||||||
let resp = read_file(&Path::new(PATH).into(), "README.txt").unwrap();
|
let resp = read_file(PATH, "README.txt").unwrap();
|
||||||
assert_eq!(resp.filename, "README.txt");
|
assert_eq!(resp.filename, "README.txt");
|
||||||
assert_eq!(resp.content.bytes(), FILE_CONTENT.as_bytes());
|
assert_eq!(resp.content.bytes(), FILE_CONTENT.as_bytes());
|
||||||
assert_eq!(resp.mime.first().unwrap(), "text/plain");
|
assert_eq!(resp.mime.first().unwrap(), "text/plain");
|
||||||
|
|
||||||
let resp = read_preview_file(&Path::new(PATH).into(), "master", "README.txt").unwrap();
|
let resp = read_preview_file(PATH, "master", "README.txt").unwrap();
|
||||||
assert_eq!(resp.filename, "README.txt");
|
assert_eq!(resp.filename, "README.txt");
|
||||||
assert_eq!(resp.content.bytes(), FILE_CONTENT.as_bytes());
|
assert_eq!(resp.content.bytes(), FILE_CONTENT.as_bytes());
|
||||||
assert_eq!(resp.mime.first().unwrap(), "text/plain");
|
assert_eq!(resp.mime.first().unwrap(), "text/plain");
|
||||||
|
|
15
src/page.rs
15
src/page.rs
|
@ -25,10 +25,7 @@ use std::println as info;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::db::Site;
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
use crate::settings::Settings;
|
|
||||||
use crate::utils::get_website_path;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct Page {
|
pub struct Page {
|
||||||
|
@ -40,18 +37,6 @@ pub struct Page {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Page {
|
impl Page {
|
||||||
pub fn from_site(settings: &Settings, s: Site) -> Self {
|
|
||||||
Self {
|
|
||||||
secret: s.site_secret,
|
|
||||||
repo: s.repo_url,
|
|
||||||
path: get_website_path(settings, &s.hostname)
|
|
||||||
.to_str()
|
|
||||||
.unwrap()
|
|
||||||
.to_owned(),
|
|
||||||
domain: s.hostname,
|
|
||||||
branch: s.branch,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pub fn open_repo(&self) -> ServiceResult<Repository> {
|
pub fn open_repo(&self) -> ServiceResult<Repository> {
|
||||||
Ok(Repository::open(&self.path)?)
|
Ok(Repository::open(&self.path)?)
|
||||||
}
|
}
|
||||||
|
|
77
src/serve.rs
77
src/serve.rs
|
@ -17,6 +17,7 @@
|
||||||
use actix_web::{http::header::ContentType, web, HttpRequest, HttpResponse, Responder};
|
use actix_web::{http::header::ContentType, web, HttpRequest, HttpResponse, Responder};
|
||||||
|
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use crate::page::Page;
|
||||||
use crate::AppCtx;
|
use crate::AppCtx;
|
||||||
|
|
||||||
pub mod routes {
|
pub mod routes {
|
||||||
|
@ -33,6 +34,19 @@ pub mod routes {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_page<'a>(domain: &str, ctx: &'a AppCtx) -> Option<&'a Page> {
|
||||||
|
log::info!("looking for {domain}");
|
||||||
|
for page in ctx.settings.pages.iter() {
|
||||||
|
log::debug!("configured domains: {}", page.domain);
|
||||||
|
log::debug!("{}", page.domain.trim() == domain.trim());
|
||||||
|
if page.domain.trim() == domain.trim() {
|
||||||
|
log::debug!("found configured domains: {}", page.domain);
|
||||||
|
return Some(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
#[actix_web_codegen_const_routes::get(path = "crate::V1_API_ROUTES.serve.catch_all")]
|
#[actix_web_codegen_const_routes::get(path = "crate::V1_API_ROUTES.serve.catch_all")]
|
||||||
async fn index(req: HttpRequest, ctx: AppCtx) -> ServiceResult<impl Responder> {
|
async fn index(req: HttpRequest, ctx: AppCtx) -> ServiceResult<impl Responder> {
|
||||||
let c = req.connection_info();
|
let c = req.connection_info();
|
||||||
|
@ -53,40 +67,47 @@ async fn index(req: HttpRequest, ctx: AppCtx) -> ServiceResult<impl Responder> {
|
||||||
unimplemented!(
|
unimplemented!(
|
||||||
"map a local subdomain on settings.server.domain and use it to fetch page"
|
"map a local subdomain on settings.server.domain and use it to fetch page"
|
||||||
);
|
);
|
||||||
|
let res = match find_page(host, &ctx) {
|
||||||
|
Some(page) => {
|
||||||
|
log::debug!("Page found");
|
||||||
|
let content = crate::git::read_preview_file(
|
||||||
|
&page.path,
|
||||||
|
preview_branch,
|
||||||
|
req.uri().path(),
|
||||||
|
)?;
|
||||||
|
let mime = if let Some(mime) = content.mime.first_raw() {
|
||||||
|
mime
|
||||||
|
} else {
|
||||||
|
"text/html; charset=utf-8"
|
||||||
|
};
|
||||||
|
|
||||||
let res = if ctx.db.hostname_exists(&host).await? {
|
Ok(HttpResponse::Ok()
|
||||||
let path = crate::utils::get_website_path(&ctx.settings, &host);
|
//.content_type(ContentType::html())
|
||||||
let content =
|
.content_type(mime)
|
||||||
crate::git::read_preview_file(&path, preview_branch, req.uri().path())?;
|
.body(content.content.bytes()))
|
||||||
let mime = if let Some(mime) = content.mime.first_raw() {
|
}
|
||||||
mime
|
None => Err(ServiceError::WebsiteNotFound),
|
||||||
} else {
|
|
||||||
"text/html; charset=utf-8"
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
|
||||||
.content_type(mime)
|
|
||||||
.body(content.content.bytes()))
|
|
||||||
} else {
|
|
||||||
Err(ServiceError::WebsiteNotFound)
|
|
||||||
};
|
};
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.db.hostname_exists(host).await? {
|
match find_page(host, &ctx) {
|
||||||
let path = crate::utils::get_website_path(&ctx.settings, &host);
|
Some(page) => {
|
||||||
let content = crate::git::read_file(&path, req.uri().path())?;
|
log::debug!("Page found");
|
||||||
let mime = if let Some(mime) = content.mime.first_raw() {
|
let content = crate::git::read_file(&page.path, req.uri().path())?;
|
||||||
mime
|
let mime = if let Some(mime) = content.mime.first_raw() {
|
||||||
} else {
|
mime
|
||||||
"text/html; charset=utf-8"
|
} else {
|
||||||
};
|
"text/html; charset=utf-8"
|
||||||
|
};
|
||||||
|
|
||||||
Ok(HttpResponse::Ok()
|
Ok(HttpResponse::Ok()
|
||||||
.content_type(mime)
|
//.content_type(ContentType::html())
|
||||||
.body(content.content.bytes()))
|
.content_type(mime)
|
||||||
} else {
|
.body(content.content.bytes()))
|
||||||
Err(ServiceError::WebsiteNotFound)
|
}
|
||||||
|
None => Err(ServiceError::WebsiteNotFound),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,6 +82,7 @@ pub struct Settings {
|
||||||
pub debug: bool,
|
pub debug: bool,
|
||||||
pub server: Server,
|
pub server: Server,
|
||||||
pub source_code: String,
|
pub source_code: String,
|
||||||
|
pub pages: Vec<Arc<Page>>,
|
||||||
pub database: Database,
|
pub database: Database,
|
||||||
pub page: PageConfig,
|
pub page: PageConfig,
|
||||||
}
|
}
|
||||||
|
@ -125,11 +126,14 @@ impl Settings {
|
||||||
match env::var("PORT") {
|
match env::var("PORT") {
|
||||||
Ok(val) => {
|
Ok(val) => {
|
||||||
s = s.set_override("server.port", val).unwrap();
|
s = s.set_override("server.port", val).unwrap();
|
||||||
|
//settings.server.port = val.parse().unwrap();
|
||||||
}
|
}
|
||||||
Err(e) => warn!("couldn't interpret PORT: {}", e),
|
Err(e) => warn!("couldn't interpret PORT: {}", e),
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(val) = env::var("DATABASE_URL") {
|
if let Ok(val) = env::var("DATABASE_URL") {
|
||||||
|
// match env::var("DATABASE_URL") {
|
||||||
|
// Ok(val) => {
|
||||||
let url = Url::parse(&val).expect("couldn't parse Database URL");
|
let url = Url::parse(&val).expect("couldn't parse Database URL");
|
||||||
s = s.set_override("database.url", url.to_string()).unwrap();
|
s = s.set_override("database.url", url.to_string()).unwrap();
|
||||||
let database_type = DBType::from_url(&url).unwrap();
|
let database_type = DBType::from_url(&url).unwrap();
|
||||||
|
@ -138,6 +142,9 @@ impl Settings {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Err(_e) => {
|
||||||
|
// }
|
||||||
|
|
||||||
let intermediate_config = s.build_cloned().unwrap();
|
let intermediate_config = s.build_cloned().unwrap();
|
||||||
|
|
||||||
s = s
|
s = s
|
||||||
|
@ -171,41 +178,41 @@ impl Settings {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(&self) {
|
pub fn init(&self) {
|
||||||
fn create_dir_util(path: &Path) {
|
for (index, page) in self.pages.iter().enumerate() {
|
||||||
if path.exists() && path.is_file() {
|
Url::parse(&page.repo).unwrap();
|
||||||
panic!("Path is a file, should be a directory: {:?}", path);
|
|
||||||
|
fn create_dir_util(path: &Path) {
|
||||||
|
if path.exists() && path.is_file() {
|
||||||
|
panic!("Path is a file, should be a directory: {:?}", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !path.exists() {
|
||||||
|
std::fs::create_dir_all(&path).unwrap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !path.exists() {
|
create_dir_util(Path::new(&page.path));
|
||||||
std::fs::create_dir_all(&path).unwrap();
|
create_dir_util(Path::new(&self.page.base_path));
|
||||||
|
|
||||||
|
for (index2, page2) in self.pages.iter().enumerate() {
|
||||||
|
if index2 == index {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if page.secret == page2.secret {
|
||||||
|
error!("{}", ServiceError::SecretTaken(page.clone(), page2.clone()));
|
||||||
|
} else if page.repo == page2.repo {
|
||||||
|
error!(
|
||||||
|
"{}",
|
||||||
|
ServiceError::DuplicateRepositoryURL(page.clone(), page2.clone(),)
|
||||||
|
);
|
||||||
|
} else if page.path == page2.path {
|
||||||
|
error!("{}", ServiceError::PathTaken(page.clone(), page2.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(e) = page.update(&page.branch) {
|
||||||
|
error!("{e}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// create_dir_util(Path::new(&page.path));
|
|
||||||
create_dir_util(Path::new(&self.page.base_path));
|
|
||||||
|
|
||||||
// for (index, page) in self.pages.iter().enumerate() {
|
|
||||||
// Url::parse(&page.repo).unwrap();
|
|
||||||
//
|
|
||||||
// for (index2, page2) in self.pages.iter().enumerate() {
|
|
||||||
// if index2 == index {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// if page.secret == page2.secret {
|
|
||||||
// error!("{}", ServiceError::SecretTaken(page.clone(), page2.clone()));
|
|
||||||
// } else if page.repo == page2.repo {
|
|
||||||
// error!(
|
|
||||||
// "{}",
|
|
||||||
// ServiceError::DuplicateRepositoryURL(page.clone(), page2.clone(),)
|
|
||||||
// );
|
|
||||||
// } else if page.path == page2.path {
|
|
||||||
// error!("{}", ServiceError::PathTaken(page.clone(), page2.clone()));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if let Err(e) = page.update(&page.branch) {
|
|
||||||
// error!("{e}");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(tarpaulin_include))]
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
|
45
src/tests.rs
45
src/tests.rs
|
@ -14,6 +14,7 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* 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/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
use std::path::Path;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
|
@ -26,16 +27,12 @@ use mktemp::Temp;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::ctx::api::v1::auth::{Login, Register};
|
use crate::ctx::api::v1::auth::{Login, Register};
|
||||||
use crate::ctx::api::v1::pages::AddSite;
|
|
||||||
use crate::ctx::Ctx;
|
use crate::ctx::Ctx;
|
||||||
use crate::errors::*;
|
use crate::errors::*;
|
||||||
|
use crate::page::Page;
|
||||||
use crate::settings::Settings;
|
use crate::settings::Settings;
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
const HOSTNAME: &str = "example.org";
|
|
||||||
pub const REPO_URL: &str = "https://github.com/mCaptcha/website/";
|
|
||||||
pub const BRANCH: &str = "gh-pages";
|
|
||||||
|
|
||||||
pub async fn get_ctx() -> (Temp, Arc<Ctx>) {
|
pub async fn get_ctx() -> (Temp, Arc<Ctx>) {
|
||||||
// mktemp::Temp is returned because the temp directory created
|
// mktemp::Temp is returned because the temp directory created
|
||||||
// is removed once the variable goes out of scope
|
// is removed once the variable goes out of scope
|
||||||
|
@ -43,8 +40,23 @@ pub async fn get_ctx() -> (Temp, Arc<Ctx>) {
|
||||||
|
|
||||||
let tmp_dir = Temp::new_dir().unwrap();
|
let tmp_dir = Temp::new_dir().unwrap();
|
||||||
println!("[log] Test temp directory: {}", tmp_dir.to_str().unwrap());
|
println!("[log] Test temp directory: {}", tmp_dir.to_str().unwrap());
|
||||||
|
let mut pages = Vec::with_capacity(settings.pages.len());
|
||||||
let page_base_path = tmp_dir.as_path().join("base_path");
|
let page_base_path = tmp_dir.as_path().join("base_path");
|
||||||
settings.page.base_path = page_base_path.to_str().unwrap().into();
|
for page in settings.pages.iter() {
|
||||||
|
let name = Path::new(&page.path).file_name().unwrap().to_str().unwrap();
|
||||||
|
let path = tmp_dir.as_path().join(name);
|
||||||
|
let page = Page {
|
||||||
|
path: path.to_str().unwrap().to_string(),
|
||||||
|
secret: page.secret.clone(),
|
||||||
|
branch: page.branch.clone(),
|
||||||
|
repo: page.repo.clone(),
|
||||||
|
domain: "mcaptcha.org".into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
pages.push(Arc::new(page));
|
||||||
|
}
|
||||||
|
|
||||||
|
settings.pages = pages;
|
||||||
settings.init();
|
settings.init();
|
||||||
println!("[log] Initialzing settings again with test config");
|
println!("[log] Initialzing settings again with test config");
|
||||||
settings.init();
|
settings.init();
|
||||||
|
@ -189,10 +201,9 @@ impl Ctx {
|
||||||
post_request!(&msg, crate::V1_API_ROUTES.auth.register).to_request(),
|
post_request!(&msg, crate::V1_API_ROUTES.auth.register).to_request(),
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
if resp.status() != StatusCode::OK {
|
// let resp_err: ErrorToResponse = actix_web::test::read_body_json(resp).await;
|
||||||
let resp_err: ErrorToResponse = actix_web::test::read_body_json(resp).await;
|
// panic!("{}", resp_err.error);
|
||||||
panic!("{}", resp_err.error);
|
assert_eq!(resp.status(), StatusCode::OK);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// signin util
|
/// signin util
|
||||||
|
@ -267,18 +278,4 @@ impl Ctx {
|
||||||
//println!("{}", resp_err.error);
|
//println!("{}", resp_err.error);
|
||||||
assert_eq!(resp_err.error, format!("{}", err));
|
assert_eq!(resp_err.error, format!("{}", err));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_test_site(&self, owner: String, hostname: String) {
|
|
||||||
let msg = AddSite {
|
|
||||||
repo_url: REPO_URL.into(),
|
|
||||||
branch: BRANCH.into(),
|
|
||||||
hostname,
|
|
||||||
owner,
|
|
||||||
};
|
|
||||||
self.add_site(msg).await.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_test_hostname(&self, unique: &str) -> String {
|
|
||||||
format!("{unique}.{HOSTNAME}")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue