Compare commits
21 Commits
Author | SHA1 | Date | |
---|---|---|---|
f668a07a8b | |||
637054ed29 | |||
d41a21dbe9 | |||
0ee3555bb5 | |||
ce7bcf0908 | |||
77e5271a40 | |||
921e410c9b | |||
1da463facd | |||
e61014fb4f | |||
aa0812677c | |||
ea0084c02c | |||
9a7abea7ab | |||
8062033f8e | |||
bae265caed | |||
b759b8e00b | |||
803ffae22c | |||
49a56f13ba | |||
07f7917152 | |||
35ca28316d | |||
debc8625fc | |||
ad543edf31 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,3 +4,6 @@ cli/capsule/
|
||||
db.db3
|
||||
cert.pem
|
||||
key.rsa
|
||||
content/
|
||||
.DS_Store
|
||||
build/
|
@ -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
648
api/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
|
@ -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
1327
cli/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"] }
|
||||
|
102
cli/src/main.rs
102
cli/src/main.rs
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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.
|
||||
|
||||
|
@ -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
117
server/Cargo.lock
generated
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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(())
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user