Compare commits

..

21 Commits

Author SHA1 Message Date
f668a07a8b Update build screipt with conditional steps
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-28 22:14:46 +00:00
637054ed29 Attempt building with a static openssl
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-28 22:05:29 +00:00
d41a21dbe9 Try and verify successful CLI buld
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-28 21:53:51 +00:00
0ee3555bb5 Update build script for the binary cli
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-28 21:43:10 +00:00
ce7bcf0908 Update capsule to refer to captown CLI 0.2
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-28 21:20:57 +00:00
77e5271a40 Add support for specifying the number of logs to return
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-28 20:49:43 +00:00
921e410c9b Fix DB mapping
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-28 20:06:40 +00:00
1da463facd Added basic log-retrieval for the CLI 2023-04-28 19:08:26 +00:00
e61014fb4f Revert to Rocket 4 for the API
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-28 18:09:39 +00:00
aa0812677c Attempt build with rustix feature flag
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-28 17:39:44 +00:00
ea0084c02c Attempt API build
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-28 16:33:06 +00:00
9a7abea7ab Minor tweaks to home capsule
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-27 08:55:23 +00:00
8062033f8e Update homepage text
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-26 14:05:48 +00:00
bae265caed Update rust image
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-25 17:25:32 +00:00
b759b8e00b Update gitignore
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
2023-04-25 17:16:02 +00:00
803ffae22c Tidy up for production 2023-04-25 17:15:07 +00:00
49a56f13ba Update API paths to DB and capsules 2023-04-25 17:10:44 +00:00
07f7917152 Add function to return view logs from API 2023-04-25 17:06:38 +00:00
35ca28316d Upgraded rocket 2023-04-25 16:35:46 +00:00
debc8625fc Add support for basic access logs
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-25 13:25:22 +00:00
ad543edf31 Remove CLI build for now
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-04-22 16:15:46 +00:00
14 changed files with 1478 additions and 896 deletions

3
.gitignore vendored
View File

@ -4,3 +4,6 @@ cli/capsule/
db.db3
cert.pem
key.rsa
content/
.DS_Store
build/

View File

@ -1,19 +1,11 @@
pipeline:
buildCli:
group: build
image: rust
commands:
- cd cli
- rustup target add x86_64-apple-darwin
- cargo build --release --target x86_64-unknown-linux-gnu
buildApi:
group: build
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
when:
path: "api/*"
path: "api/**/*"
settings:
repo: wilw/capsuletown-api
dockerfile: api/Dockerfile
@ -24,22 +16,46 @@ pipeline:
image: woodpeckerci/plugin-docker-buildx
secrets: [docker_username, docker_password]
when:
path: "server/*"
path: "server/**/*"
settings:
repo: wilw/capsuletown-server
dockerfile: server/Dockerfile
context: server
deployRootCapsule:
image: python:3.10
buildCli:
group: build
image: rust
when:
path: "home/*"
path: "cli/**/*"
commands:
- cd cli
- cargo build --release
- target/release/captown help
deployRootCapsule:
group: deploy
image: python:3.10
when:
path: "home/**/*"
secrets: [ CAPSULE_TOWN_KEY ]
commands:
- cd home
- CAPSULE=$(tar -czf /tmp/c.tar.gz -C capsule . && cat /tmp/c.tar.gz | base64)
- 'echo "{\"capsuleArchive\": \"$CAPSULE\"}" > /tmp/capsule_file'
- 'curl -X PUT -H "Content-Type: application/json" -H "api-key: $CAPSULE_TOWN_KEY" -d @/tmp/capsule_file https://api.capsule.town/capsule'
deployCli:
group: deploy
image: alpine
when:
path: "cli/**/*"
secrets: [ LINODE_ACCESS_KEY, LINODE_SECRET_ACCESS_KEY, BUNNY_KEY ]
commands:
- cd cli
- apk update
- apk add s3cmd curl
- s3cmd --configure --access_key=$LINODE_ACCESS_KEY --secret_key=$LINODE_SECRET_ACCESS_KEY --host=https://eu-central-1.linodeobjects.com --host-bucket="%(bucket)s.eu-central-1.linodeobjects.com" --dump-config > /root/.s3cfg
- s3cmd -c /root/.s3cfg put target/release/captown s3://download.capsule.town/captown-linux-0.2 --acl-public
- 'curl -X POST -H "AccessKey: $BUNNY_KEY" https://api.bunny.net/pullzone/783552/purgeCache'
#branches: main
branches: main

648
api/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,15 +4,14 @@ version = "0.1.0"
authors = ["Will Webberley <me@wilw.dev>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rocket = "0.4.8"
rocket_contrib = "0.4.6"
jsonwebtoken = "7.2"
serde = { version = "1.0", features = ["derive"] }
flate2 = "1.0.19"
tar = "0.4.30"
base64 = "0.13.0"
rusqlite = "0.24.2"
uuid = { version = "0.8", features = ["v4"] }
rocket = "0.4.11"
jsonwebtoken = "8.3.0"
serde = { version = "1.0.160", features = ["derive"] }
flate2 = "1.0.26"
tar = "0.4.38"
base64 = "0.21.0"
rusqlite = "0.29.0"
uuid = { version = "1.3.1", features = ["v4"] }
ubyte = "0.10.3"
rocket_contrib = "0.4.11"

View File

@ -3,15 +3,15 @@
#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket_contrib;
use std::{env, str, fs::{self, File}, time::{SystemTime, UNIX_EPOCH}, io::Write};
use rocket::{self, routes, get, post, put, config::{Config, Limits}, response::Responder, request::{self, Request, FromRequest}, Outcome, http::Status};
use std::{env, str, fs::{self, File}, time::{SystemTime, UNIX_EPOCH}, io::Write, collections::HashSet};
use rocket::{self, routes, get, post, put, catch, config::{Config, Limits}, response::Responder, request::{self, Request, FromRequest}, Outcome, http::Status};
use rocket_contrib::json::{Json, JsonValue};
use serde::{Serialize, Deserialize};
use jsonwebtoken::{self, encode, decode, Header, Validation, EncodingKey, DecodingKey};
use jsonwebtoken::{self, encode, decode, Header, Validation, EncodingKey, DecodingKey, Algorithm};
use rusqlite::{params, Connection};
use flate2::read::GzDecoder;
use tar::Archive;
use base64;
use base64::{Engine as _, engine::general_purpose};
use uuid::Uuid;
const DB_FILE: &str = "/usr/local/var/capsules_db/db.db3";
@ -73,10 +73,9 @@ fn generate_access_token(capsule_id: &str) -> Result<String> {
// Verify the access JWT token, and checks still valid in DB. Returns the authenticated capsule.
fn validate_access_token(token: &str) -> Result<AuthenticatedCapsule> {
// Validate the token integrity itself
let my_validation = Validation {
validate_exp: false,
..Default::default()
};
let mut my_validation = Validation::new(Algorithm::HS256);
my_validation.validate_exp = false;
my_validation.required_spec_claims = HashSet::new();
let claims = decode::<Claims>(&token, &DecodingKey::from_secret(get_jwt_secret().as_ref()), &my_validation)?.claims;
// Verify the token in the DB and retrieve the capsule info
@ -105,6 +104,11 @@ enum Resp {
NotFound(JsonValue),
}
#[catch(400)]
fn bad_request(_: &Request) -> Resp {
return Resp::BadRequest(json!({"message": "There was a problem with your request"}));
}
#[get("/")]
fn index() -> Resp {
return Resp::NotFound(json!({"message": "There is no resource available at this path."}));
@ -232,7 +236,7 @@ struct CapsuleDeployRequest {
#[put("/capsule", data = "<capsule>")]
fn deploy_capsule(capsule: Json<CapsuleDeployRequest>, authenticated_capsule: AuthenticatedCapsule) -> Resp {
// Decode base64-encoded JSON capsule_archive. This gives a Vec<u8>
let archive_vec: Vec<u8> = match base64::decode(&capsule.capsule_archive) {
let archive_vec: Vec<u8> = match general_purpose::STANDARD.decode(&capsule.capsule_archive) {
Ok(x) => x,
Err(_) => return Resp::BadRequest(json!({"message": "Your capsule archive is not valid"})),
};
@ -267,6 +271,30 @@ fn deploy_capsule(capsule: Json<CapsuleDeployRequest>, authenticated_capsule: Au
return Resp::Ok(json!({"message": "Your capsule was deployed", "url": url}));
}
#[derive(Serialize)]
struct CapsuleViewLog {
path: String,
timestamp: i32,
}
#[get("/logs")]
fn get_logs(authenticated_capsule: AuthenticatedCapsule) -> Resp {
let conn = match get_db() {
Ok(c) => c,
Err(_) => return Resp::InternalServerError(json!({"message": "Database problem"})),
};
let mut stmt = conn
.prepare("SELECT path, timestamp FROM view WHERE capsule_id = ?1 ORDER BY timestamp DESC LIMIT 100")
.unwrap();
let view_results = stmt.query_map(params![authenticated_capsule.id], |row| {
Ok(CapsuleViewLog {
path: row.get(0)?,
timestamp: row.get(1)?,
})
}).unwrap();
let views: Vec<CapsuleViewLog> = view_results.map(|view| view.unwrap()).collect();
return Resp::Ok(json!({"logs": &views}));
}
fn main() {
// Create needed directories
fs::create_dir_all(CAPSULE_DIR).unwrap_or_default();
@ -276,5 +304,5 @@ fn main() {
my_config.set_port(9000);
my_config.set_limits(Limits::new().limit("json", 50 * 1024 * 1024)); // 50MB
let app = rocket::custom(my_config);
app.mount("/", routes![index, create_capsule, deploy_capsule]).launch();
app.mount("/", routes![index, create_capsule, deploy_capsule, get_logs]).launch();
}

1327
cli/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -17,3 +17,6 @@ reqwest = { version = "0.11", features = ["json", "blocking"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
serde_yaml = "0.8"
chrono = "0.4.24"
prettytable = "0.10.0"
openssl = { version = "0.10", features = ["vendored"] }

View File

@ -5,6 +5,8 @@ use clap::{Arg, App, SubCommand};
use flate2::{Compression, write::GzEncoder};
use serde::{Serialize, Deserialize};
use tempfile::tempdir;
use chrono::prelude::*;
use prettytable::{Table, row};
use dirs;
use base64;
use reqwest;
@ -205,6 +207,81 @@ fn create_capsule() -> Result<bool>{
}
}
#[derive(Deserialize, Debug)]
struct LogsResponse {
logs: Option<Vec<Log>>,
message: Option<String>,
}
#[derive(Deserialize, Debug)]
struct Log {
path: String,
timestamp: i64,
}
impl Log {
fn time(&self) -> String {
let naive = NaiveDateTime::from_timestamp_opt(self.timestamp, 0).unwrap();
let datetime: DateTime<Utc> = DateTime::from_utc(naive, Utc);
let newdate = format!("{}", datetime.format("%Y-%m-%d %H:%M:%S"));
return newdate;
}
}
// PUTs the base64-encoded capsule tarball to the server.
fn capsule_logs(flag_capsule_name: Option<&str>, flag_number_of_logs: Option<&str>) -> Result<bool>{
let capsule_name: String = match flag_capsule_name {
Some(x) => String::from(x),
None => prompt_for("Which capsule do you want to view logs for?"),
};
let capsule_config = match read_config_capsule(&capsule_name) {
Ok(x) => x,
Err(x) => {
println!("{:?}", x);
return Err(Box::from("Unable to get logs"));
},
};
let number_of_logs: usize = match flag_number_of_logs {
Some(x) => {
let required = match x.parse::<usize>() {
Ok(y) => y,
Err(_) => return Err(Box::from("Log count must be a number")),
};
if required > 100 {
return Err(Box::from("Maximum log count is 100"));
}
required
},
None => 20,
};
let client = reqwest::blocking::Client::new();
let res = client.get(&format!("{}/logs", get_api_url()))
.header("api-key", &capsule_config.access_token).send()?;
let status = res.status();
let response_data = res.json::<LogsResponse>()?;
if status.is_success() {
match response_data.logs {
None => println!("No logs found"),
Some(mut logs) => {
logs.truncate(number_of_logs);
logs.reverse();
let mut table = Table::new();
table.add_row(row!["TIME", "CAPSULE", "PATH"]);
for log in logs {
table.add_row(row![log.time(), capsule_name, log.path]);
}
table.printstd();
},
}
return Ok(true);
} else {
let error_message = match response_data.message {
None => String::from("Unknown error"),
Some(message) => message,
};
println!("Unable to get the logs for your capsule: {}", &error_message);
return Err(Box::from("Unable to get logs"));
}
}
fn main() {
// Setup config
@ -214,7 +291,7 @@ fn main() {
// Read command-line commands/args
let app_args = App::new("Capsule.Town")
.version("0.1")
.version("0.2")
.author("Will W. (wilw.dev)")
.about("Publishes your capsule to capsule.town")
.subcommand(SubCommand::with_name("create")
@ -225,7 +302,19 @@ fn main() {
.short("c")
.long("capsule")
.takes_value(true)
.help("Name of the capsule to be published")));
.help("Name of the capsule to be published")))
.subcommand(SubCommand::with_name("logs")
.about("View access logs for your capsule")
.arg(Arg::with_name("capsule")
.short("c")
.long("capsule")
.takes_value(true)
.help("Name of the capsule to view logs for"))
.arg(Arg::with_name("number")
.short("n")
.takes_value(true)
.help("Number of logs to display (default: 20, maximum: 100)"))
);
let matches = app_args.get_matches();
// Handle commands
@ -235,12 +324,17 @@ fn main() {
let capsule_name = sub_match.value_of("capsule");
publish_capsule(capsule_name)
},
("logs", Some(sub_match)) => {
let capsule_name = sub_match.value_of("capsule");
let number_of_logs = sub_match.value_of("number");
capsule_logs(capsule_name, number_of_logs)
},
_ => {
println!("Unknown command. Available commands: create, publish");
println!("Unknown command. Available commands: create, publish, logs");
Err(Box::from("Unable to continue"))
},
} {
println!("Program is exiting with error. {}", err);
println!("Exiting with error: {}", err);
std::process::exit(1);
}
}

View File

@ -19,7 +19,6 @@
✨ No sign-up is required and your capsule can be live within seconds.
✨ You get a free Geminispace hosted at <yoursubdomain>.capsule.town.
✨ You can publish entirely anonymously.
✨ There is no logging, tracking, or analytics.
🚧 This project is currently in open beta. Feedback, bug-reports, and contributions are welcome. Please see below for ways to get in touch.
@ -39,9 +38,9 @@ Think of this service as the Gemini version of static site hosting services like
## Why is it free?
capsule.town does not generate any income, and I pay for the servers/upkeep myself.
capsule.town does not generate any income, and I pay for the servers and upkeep myself.
This is one way I can give back to the indie and open-source community. There is no logging or analytics and your data is not collected or sold. You can view and evaluate the source yourself (there is a link above).
This is one way I can give back to the indie and open-source community. Your data is not sold. You can view and evaluate the source yourself (there is a link above).
By taking part you are certainly not the product; you are a member of the community.

View File

@ -2,7 +2,7 @@
=> gemini://capsule.town Return home
This page describes the Rules applying to all capsules hosted on capsule.town.
This page describes the rules applying to all capsules hosted on capsule.town.
By creating and publishing your capsule, you declare that you agree to these rules.

View File

@ -25,21 +25,21 @@ You can use CURL to get the client, as described below.
For Linux:
```
➡️ curl -O https://download.capsule.town/captown-linux-0.1
➡️ curl https://download.capsule.town/captown-linux-0.2 -o captown
```
For Mac:
```
➡️ curl -O https://download.capsule.town/captown-mac-0.1
➡️ curl https://download.capsule.town/captown-mac-0.2 -o captown
```
The Windows client is coming soon (contributions welcome).
We do not yet have a Windows version, but contributions are welcome.
Once downloaded:
1. Mark the binary as executable: `chmod +x captown-mac-0.1`
2. Move the binary to a location of your choosing: e.g. `mv captown-mac-0.1 /usr/local/bin/captown`.
1. Mark the binary as executable: `chmod +x captown`
2. Move the binary to a location of your choosing: e.g. `sudo mv captown /usr/local/bin`.
The rest of this guide assumes the binary is somewhere on your `PATH` and can be invoked using `captown` (as above).
@ -103,6 +103,16 @@ Whichever route you choose, if all goes well you'll then be able to visit `gemin
🗒 Note: the `publish` command must be run from the root of your project (i.e. the directory containing the `capsule/` sub-directory).
### 5. View your capsule's logs
You can see an access log of your capsule by running the `logs` command:
```
➡️ captown logs -c mycapsule
```
View other options for this command by running `captown help logs`.
## Making updates
When you've made updates that you want to publish, simply run `captown publish` again to make them live.

117
server/Cargo.lock generated
View File

@ -1,5 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "agate"
version = "2.3.0"
@ -10,12 +12,24 @@ dependencies = [
"mime_guess",
"once_cell",
"percent-encoding",
"rusqlite",
"rustls",
"tokio",
"tokio-rustls",
"url",
]
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "atty"
version = "0.2.14"
@ -39,6 +53,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "bitflags"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813"
[[package]]
name = "bumpalo"
version = "3.4.0"
@ -81,6 +101,18 @@ dependencies = [
"termcolor",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "form_urlencoded"
version = "1.0.0"
@ -100,6 +132,35 @@ dependencies = [
"unicode-width",
]
[[package]]
name = "getrandom"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
dependencies = [
"cfg-if 1.0.0",
"libc",
"wasi",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
]
[[package]]
name = "hashlink"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa"
dependencies = [
"hashbrown",
]
[[package]]
name = "hermit-abi"
version = "0.1.17"
@ -143,9 +204,19 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.82"
version = "0.2.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
[[package]]
name = "libsqlite3-sys"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326"
dependencies = [
"pkg-config",
"vcpkg",
]
[[package]]
name = "log"
@ -228,9 +299,9 @@ dependencies = [
[[package]]
name = "once_cell"
version = "1.5.2"
version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "percent-encoding"
@ -244,6 +315,12 @@ version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "439697af366c49a6d0a010c56a0d97685bc140ce0d377b13a2ea2aa42d64a827"
[[package]]
name = "pkg-config"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "proc-macro2"
version = "1.0.24"
@ -277,6 +354,20 @@ dependencies = [
"winapi",
]
[[package]]
name = "rusqlite"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",
"libsqlite3-sys",
"smallvec",
]
[[package]]
name = "rustls"
version = "0.19.0"
@ -300,6 +391,12 @@ dependencies = [
"untrusted",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.3.19"
@ -435,12 +532,24 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.69"

View File

@ -21,6 +21,7 @@ once_cell = "1.4"
percent-encoding = "2.1"
rustls = "0.19.0"
url = "2.1"
rusqlite = "0.29.0"
[profile.release]
lto = true

View File

@ -14,6 +14,7 @@ use {
net::SocketAddr,
path::Path,
sync::Arc,
time::{SystemTime, UNIX_EPOCH},
},
tokio::{
io::{AsyncReadExt, AsyncWriteExt},
@ -22,8 +23,16 @@ use {
},
tokio_rustls::{TlsAcceptor, server::TlsStream},
url::{Host, Url},
rusqlite::{Connection},
};
const DB_FILE: &str = "/usr/local/var/capsules_db/db.db3";
// Utility function to retrieve a connection to the database
fn get_db() -> Result<Connection, rusqlite::Error> {
return Ok(Connection::open(DB_FILE)?);
}
fn main() -> Result {
if !ARGS.silent {
env_logger::Builder::new().parse_filters("info").init();
@ -243,7 +252,7 @@ async fn send_response(url: Url, stream: &mut TlsStream<TcpStream>) -> Result {
}
}
}
// Make sure the file opens successfully before sending the success header.
let mut file = match tokio::fs::File::open(&path).await {
Ok(file) => file,
@ -263,6 +272,26 @@ async fn send_response(url: Url, stream: &mut TlsStream<TcpStream>) -> Result {
// Send body.
tokio::io::copy(&mut file, stream).await?;
// Capture the view
#[derive(Debug)]
struct Capsule {
id: String,
}
let conn = get_db().unwrap();
let mut stmt = conn.prepare(&format!("SELECT id, name FROM capsule WHERE name = '{}'", &subdomain))?;
let mut capsule_cursor = stmt.query_map([], |row| {
Ok(Capsule {
id: row.get(0)?,
})
})?;
let capsule = capsule_cursor.next().unwrap()?;
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs();
_ = conn.execute("CREATE TABLE IF NOT EXISTS view (capsule_id TEXT, path TEXT, timestamp INTEGER)", ());
conn.execute("INSERT INTO view (capsule_id, path, timestamp) VALUES (?1, ?2, ?3)",
(&capsule.id, &url.path(), timestamp),
)?;
Ok(())
}