mirror of
https://github.com/cupcakearmy/cryptgeon.git
synced 2026-09-26 20:41:45 +00:00
first v3
This commit is contained in:
Generated
+21
-1
@@ -252,7 +252,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cryptgeon"
|
||||
version = "2.9.3"
|
||||
version = "3.0.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"bs62",
|
||||
@@ -261,6 +261,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"redis",
|
||||
"ring",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -1004,6 +1005,25 @@ dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp"
|
||||
version = "0.8.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ba8be72d372b2c9b35542551678538b562e7cf86c3315773cae48dfbfe7790c"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rmp-serde"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
|
||||
dependencies = [
|
||||
"rmp",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.1.4"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cryptgeon"
|
||||
version = "2.9.3"
|
||||
version = "3.0.0"
|
||||
authors = ["cupcakearmy <hi@nicco.io>"]
|
||||
edition = "2024"
|
||||
rust-version = "1.95"
|
||||
@@ -20,8 +20,9 @@ redis = { version = "1", features = ["tls-native-tls"] }
|
||||
|
||||
# Utility
|
||||
serde_json = "1"
|
||||
rmp-serde = "1"
|
||||
lazy_static = "1"
|
||||
ring = "0.17"
|
||||
bs62 = "0.1"
|
||||
byte-unit = "4"
|
||||
dotenv = "0.15"
|
||||
dotenv = "0.15"
|
||||
@@ -34,7 +34,7 @@ pub static ref ID_LENGTH: u32 = std::env::var("ID_LENGTH")
|
||||
.unwrap_or("32".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
pub static ref REDIS_PREFIX: String = std::env::var("REDIS_PREFIX")
|
||||
pub static ref CACHE_PREFIX: String = std::env::var("CACHE_PREFIX")
|
||||
.unwrap_or("".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
@@ -50,6 +50,10 @@ pub static ref IMPRINT_HTML: String = std::env::var("IMPRINT_HTML")
|
||||
.unwrap_or("".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
pub static ref EXTRA_SIZE_LIMIT: usize = std::env::var("EXTRA_SIZE_LIMIT")
|
||||
.unwrap_or("512".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// THEME
|
||||
@@ -78,4 +82,4 @@ lazy_static! {
|
||||
.unwrap_or("true".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,9 @@ use crate::store;
|
||||
use axum::http::StatusCode;
|
||||
|
||||
pub async fn report_health() -> (StatusCode,) {
|
||||
if store::can_reach_redis() {
|
||||
if store::can_reach_cache() {
|
||||
return (StatusCode::OK,);
|
||||
} else {
|
||||
return (StatusCode::SERVICE_UNAVAILABLE,);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SharedState {
|
||||
pub locks: LockMap,
|
||||
}
|
||||
|
||||
pub type LockMap = Arc<Mutex<HashMap<String, Arc<Mutex<()>>>>>;
|
||||
@@ -1,13 +1,9 @@
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use axum::{
|
||||
Router, ServiceExt,
|
||||
extract::{DefaultBodyLimit, Request},
|
||||
routing::{delete, get, post},
|
||||
};
|
||||
use dotenv::dotenv;
|
||||
use lock::SharedState;
|
||||
use tokio::sync::Mutex;
|
||||
use tower::Layer;
|
||||
use tower_http::{
|
||||
compression::CompressionLayer,
|
||||
@@ -21,7 +17,6 @@ extern crate lazy_static;
|
||||
mod config;
|
||||
mod csp;
|
||||
mod health;
|
||||
mod lock;
|
||||
mod note;
|
||||
mod status;
|
||||
mod store;
|
||||
@@ -30,34 +25,30 @@ mod store;
|
||||
async fn main() {
|
||||
dotenv().ok();
|
||||
|
||||
let shared_state = SharedState {
|
||||
locks: Arc::new(Mutex::new(HashMap::new())),
|
||||
};
|
||||
|
||||
if !store::can_reach_redis() {
|
||||
println!("cannot reach redis");
|
||||
panic!("cannot reach redis");
|
||||
if !store::can_reach_cache() {
|
||||
println!("cannot reach cache");
|
||||
panic!("cannot reach cache");
|
||||
}
|
||||
|
||||
let notes_routes = Router::new()
|
||||
.route("/", post(note::create))
|
||||
.route("/{id}", delete(note::delete))
|
||||
.route("/{id}", delete(note::view))
|
||||
.route("/{id}", get(note::preview));
|
||||
let health_routes = Router::new().route("/live", get(health::report_health));
|
||||
let health_routes = Router::new().route("/healthz", get(health::report_health));
|
||||
let status_routes = Router::new().route("/status", get(status::get_status));
|
||||
let api_routes = Router::new()
|
||||
let v3_routes = Router::new()
|
||||
.nest("/notes", notes_routes)
|
||||
.merge(health_routes)
|
||||
.merge(status_routes);
|
||||
|
||||
let api_routes = Router::new().nest("/v3", v3_routes);
|
||||
|
||||
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
|
||||
let serve_dir =
|
||||
ServeDir::new(config::FRONTEND_PATH.to_string()).not_found_service(ServeFile::new(index));
|
||||
let app = Router::new()
|
||||
.nest("/api", api_routes)
|
||||
.merge(health_routes)
|
||||
.fallback_service(serve_dir)
|
||||
// Disabled for now, as svelte inlines scripts
|
||||
// .layer(middleware::from_fn(csp::add_csp_header))
|
||||
.layer(DefaultBodyLimit::max(*config::LIMIT))
|
||||
.layer(
|
||||
CompressionLayer::new()
|
||||
@@ -65,8 +56,7 @@ async fn main() {
|
||||
.deflate(true)
|
||||
.gzip(true)
|
||||
.zstd(true),
|
||||
)
|
||||
.with_state(shared_state);
|
||||
);
|
||||
|
||||
let app = NormalizePathLayer::trim_trailing_slash().layer(app);
|
||||
|
||||
@@ -77,4 +67,4 @@ async fn main() {
|
||||
axum::serve(listener, ServiceExt::<Request>::into_make_service(app))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,4 @@ mod model;
|
||||
mod routes;
|
||||
|
||||
pub use model::*;
|
||||
pub use routes::*;
|
||||
pub use routes::*;
|
||||
@@ -5,22 +5,35 @@ use serde::{Deserialize, Serialize};
|
||||
use crate::config;
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct Note {
|
||||
pub meta: String,
|
||||
pub contents: String,
|
||||
pub struct NoteMeta {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub views: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub expiration: Option<u32>,
|
||||
#[serde(default)]
|
||||
pub extra: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct NoteInfo {
|
||||
pub meta: String,
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct CreateRequest {
|
||||
pub meta: NoteMeta,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct NotePublic {
|
||||
pub meta: String,
|
||||
pub contents: String,
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct CreateResponse {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct MetaResponse {
|
||||
pub meta: NoteMeta,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct NoteResponse {
|
||||
pub meta: NoteMeta,
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
pub fn generate_id() -> String {
|
||||
@@ -32,5 +45,5 @@ pub fn generate_id() -> String {
|
||||
let _ = sr.fill(&mut id);
|
||||
result.push_str(&bs62::encode_data(&id));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
result
|
||||
}
|
||||
+102
-114
@@ -2,155 +2,143 @@ use axum::{
|
||||
extract::Path,
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
body::Bytes,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{sync::Arc, time::SystemTime};
|
||||
use tokio::sync::Mutex;
|
||||
use serde::Deserialize;
|
||||
use std::time::SystemTime;
|
||||
|
||||
use crate::note::{generate_id, Note, NoteInfo};
|
||||
use crate::note::{CreateRequest, generate_id};
|
||||
use crate::store;
|
||||
use crate::{config, lock::SharedState};
|
||||
use crate::config;
|
||||
|
||||
use super::NotePublic;
|
||||
use super::{CreateResponse, MetaResponse, NoteResponse, NoteMeta};
|
||||
|
||||
pub fn now() -> u32 {
|
||||
pub fn now() -> u64 {
|
||||
SystemTime::now()
|
||||
.duration_since(SystemTime::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs() as u32
|
||||
.as_secs()
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct OneNoteParams {
|
||||
pub struct NoteParams {
|
||||
id: String,
|
||||
}
|
||||
|
||||
pub async fn preview(Path(OneNoteParams { id }): Path<OneNoteParams>) -> Response {
|
||||
let note = store::get(&id);
|
||||
pub async fn create(body: Bytes) -> Response {
|
||||
let req: CreateRequest = match rmp_serde::from_slice(&body) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return (StatusCode::BAD_REQUEST, "Invalid msgpack").into_response(),
|
||||
};
|
||||
|
||||
match note {
|
||||
Ok(Some(n)) => (StatusCode::OK, Json(NoteInfo { meta: n.meta })).into_response(),
|
||||
Ok(None) => (StatusCode::NOT_FOUND).into_response(),
|
||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||
if req.meta.views.is_none() && req.meta.expiration.is_none() {
|
||||
return (StatusCode::BAD_REQUEST, "At least views or expiration must be set").into_response();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct CreateResponse {
|
||||
id: String,
|
||||
}
|
||||
|
||||
pub async fn create(Json(mut n): Json<Note>) -> Response {
|
||||
// let mut n = note.into_inner();
|
||||
let id = generate_id();
|
||||
// let bad_req = HttpResponse::BadRequest().finish();
|
||||
if n.views == None && n.expiration == None {
|
||||
return (
|
||||
StatusCode::BAD_REQUEST,
|
||||
"At least views or expiration must be set",
|
||||
)
|
||||
.into_response();
|
||||
if req.meta.extra.len() > *config::EXTRA_SIZE_LIMIT {
|
||||
return (StatusCode::BAD_REQUEST, "Extra data too large").into_response();
|
||||
}
|
||||
|
||||
let mut meta = req.meta;
|
||||
|
||||
if !*config::ALLOW_ADVANCED {
|
||||
n.views = Some(1);
|
||||
n.expiration = None;
|
||||
meta.views = Some(1);
|
||||
meta.expiration = None;
|
||||
}
|
||||
match n.views {
|
||||
|
||||
match meta.views {
|
||||
Some(v) => {
|
||||
if v > *config::MAX_VIEWS || v < 1 {
|
||||
return (StatusCode::BAD_REQUEST, "Invalid views").into_response();
|
||||
}
|
||||
n.expiration = None; // views overrides expiration
|
||||
}
|
||||
_ => {}
|
||||
None => {}
|
||||
}
|
||||
match n.expiration {
|
||||
|
||||
let expiration_ts = match meta.expiration {
|
||||
Some(e) => {
|
||||
if e > *config::MAX_EXPIRATION || e < 1 {
|
||||
return (StatusCode::BAD_REQUEST, "Invalid expiration").into_response();
|
||||
}
|
||||
let expiration = now() + (e * 60);
|
||||
n.expiration = Some(expiration);
|
||||
Some(now() + (e as u64 * 60))
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let id = generate_id();
|
||||
let views = meta.views.map(|v| v as i64);
|
||||
|
||||
match store::set(&id, &req.data, views, expiration_ts, &meta.extra) {
|
||||
Ok(_) => {
|
||||
let resp = CreateResponse { id };
|
||||
let bytes = rmp_serde::to_vec_named(&resp).unwrap();
|
||||
(StatusCode::OK, Bytes::from(bytes)).into_response()
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
match store::set(&id.clone(), &n.clone()) {
|
||||
Ok(_) => (StatusCode::OK, Json(CreateResponse { id })).into_response(),
|
||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
Path(OneNoteParams { id }): Path<OneNoteParams>,
|
||||
state: axum::extract::State<SharedState>,
|
||||
) -> Response {
|
||||
let mut locks_map = state.locks.lock().await;
|
||||
let lock = locks_map
|
||||
.entry(id.clone())
|
||||
.or_insert_with(|| Arc::new(Mutex::new(())))
|
||||
.clone();
|
||||
drop(locks_map);
|
||||
let _guard = lock.lock().await;
|
||||
|
||||
let note = store::get(&id);
|
||||
match note {
|
||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||
pub async fn preview(Path(NoteParams { id }): Path<NoteParams>) -> Response {
|
||||
match store::get_meta(&id) {
|
||||
Ok(Some((views, expiration, extra))) => {
|
||||
let meta = NoteMeta {
|
||||
views: views.map(|v| v as u32),
|
||||
expiration: expiration.map(|e| e as u32),
|
||||
extra,
|
||||
};
|
||||
let resp = MetaResponse { meta };
|
||||
let bytes = rmp_serde::to_vec_named(&resp).unwrap();
|
||||
(StatusCode::OK, Bytes::from(bytes)).into_response()
|
||||
}
|
||||
Ok(None) => (StatusCode::NOT_FOUND).into_response(),
|
||||
Ok(Some(note)) => {
|
||||
let mut changed = note.clone();
|
||||
if changed.views == None && changed.expiration == None {
|
||||
return (StatusCode::BAD_REQUEST).into_response();
|
||||
}
|
||||
match changed.views {
|
||||
Some(v) => {
|
||||
changed.views = Some(v - 1);
|
||||
let id = id.clone();
|
||||
if v <= 1 {
|
||||
match store::del(&id) {
|
||||
Err(e) => {
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
||||
.into_response();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
match store::set(&id, &changed.clone()) {
|
||||
Err(e) => {
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
||||
.into_response();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
let n = now();
|
||||
match changed.expiration {
|
||||
Some(e) => {
|
||||
if e < n {
|
||||
match store::del(&id.clone()) {
|
||||
Ok(_) => return (StatusCode::BAD_REQUEST).into_response(),
|
||||
Err(e) => {
|
||||
return (StatusCode::INTERNAL_SERVER_ERROR, e.to_string())
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
return (
|
||||
StatusCode::OK,
|
||||
Json(NotePublic {
|
||||
contents: changed.contents,
|
||||
meta: changed.meta,
|
||||
}),
|
||||
)
|
||||
.into_response();
|
||||
}
|
||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn view(Path(NoteParams { id }): Path<NoteParams>) -> Response {
|
||||
let (views, expiration, extra) = match store::get_meta(&id) {
|
||||
Ok(Some(v)) => v,
|
||||
_ => return (StatusCode::NOT_FOUND).into_response(),
|
||||
};
|
||||
|
||||
let has_views = views.is_some();
|
||||
|
||||
if has_views {
|
||||
let remaining = match store::decrement_views(&id) {
|
||||
Ok(r) => r,
|
||||
Err(_) => return (StatusCode::NOT_FOUND).into_response(),
|
||||
};
|
||||
|
||||
let data = match store::get_data(&id) {
|
||||
Ok(Some(d)) => d,
|
||||
_ => return (StatusCode::NOT_FOUND).into_response(),
|
||||
};
|
||||
|
||||
if remaining <= 0 {
|
||||
let _ = store::del(&id);
|
||||
}
|
||||
|
||||
let meta = NoteMeta {
|
||||
views: Some(if remaining > 0 { remaining as u32 } else { 0 }),
|
||||
expiration: expiration.map(|e| e as u32),
|
||||
extra,
|
||||
};
|
||||
let resp = NoteResponse { meta, data };
|
||||
let bytes = rmp_serde::to_vec_named(&resp).unwrap();
|
||||
(StatusCode::OK, Bytes::from(bytes)).into_response()
|
||||
} else {
|
||||
let data = match store::get_data(&id) {
|
||||
Ok(Some(d)) => d,
|
||||
_ => return (StatusCode::NOT_FOUND).into_response(),
|
||||
};
|
||||
|
||||
let meta = NoteMeta {
|
||||
views: None,
|
||||
expiration: expiration.map(|e| e as u32),
|
||||
extra,
|
||||
};
|
||||
let resp = NoteResponse { meta, data };
|
||||
let bytes = rmp_serde::to_vec_named(&resp).unwrap();
|
||||
(StatusCode::OK, Bytes::from(bytes)).into_response()
|
||||
}
|
||||
}
|
||||
@@ -1,71 +1,90 @@
|
||||
use redis;
|
||||
use redis::Commands;
|
||||
|
||||
use crate::config;
|
||||
use crate::note::now;
|
||||
use crate::note::Note;
|
||||
|
||||
lazy_static! {
|
||||
static ref REDIS_CLIENT: String = std::env::var("REDIS")
|
||||
static ref CACHE_URL: String = std::env::var("CACHE")
|
||||
.unwrap_or("redis://127.0.0.1/".to_string())
|
||||
.parse()
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
fn prefixed(id: &String) -> String {
|
||||
format!("{}{}", config::REDIS_PREFIX.as_str(), id)
|
||||
fn prefixed(id: &str) -> String {
|
||||
format!("{}{}", config::CACHE_PREFIX.as_str(), id)
|
||||
}
|
||||
|
||||
fn get_connection() -> Result<redis::Connection, &'static str> {
|
||||
fn conn() -> Result<redis::Connection, &'static str> {
|
||||
let client =
|
||||
redis::Client::open(REDIS_CLIENT.to_string()).map_err(|_| "Unable to connect to redis")?;
|
||||
client
|
||||
.get_connection()
|
||||
.map_err(|_| "Unable to connect to redis")
|
||||
redis::Client::open(CACHE_URL.to_string()).map_err(|_| "Unable to connect to cache")?;
|
||||
client.get_connection().map_err(|_| "Unable to connect to cache")
|
||||
}
|
||||
|
||||
pub fn can_reach_redis() -> bool {
|
||||
let conn = get_connection();
|
||||
return match conn {
|
||||
Ok(_) => true,
|
||||
Err(_) => false,
|
||||
};
|
||||
pub fn can_reach_cache() -> bool {
|
||||
conn().is_ok()
|
||||
}
|
||||
|
||||
pub fn set(id: &String, note: &Note) -> Result<(), &'static str> {
|
||||
pub fn set(id: &str, data: &[u8], views: Option<i64>, expiration: Option<u64>, extra: &[u8]) -> Result<(), &'static str> {
|
||||
let key = prefixed(id);
|
||||
let serialized = serde_json::to_string(¬e.clone()).unwrap();
|
||||
let mut conn = get_connection()?;
|
||||
let mut c = conn()?;
|
||||
|
||||
conn.set::<_, _, ()>(key.as_str(), serialized)
|
||||
.map_err(|_| "Unable to set note in redis")?;
|
||||
match note.expiration {
|
||||
Some(e) => {
|
||||
let seconds = e - now();
|
||||
conn.expire::<_, ()>(key.as_str(), seconds as i64)
|
||||
.map_err(|_| "Unable to set expiration on note")?
|
||||
}
|
||||
None => {}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
c.hset::<_, _, _, ()>(&key, "data", data).map_err(|_| "Unable to set note")?;
|
||||
c.hset::<_, _, _, ()>(&key, "extra", extra).map_err(|_| "Unable to set note")?;
|
||||
|
||||
pub fn get(id: &String) -> Result<Option<Note>, &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut conn = get_connection()?;
|
||||
let value: Option<String> = conn.get(key.as_str()).map_err(|_| "Could not load note in redis")?;
|
||||
match value {
|
||||
None => return Ok(None),
|
||||
Some(s) => {
|
||||
let deserialize: Note = serde_json::from_str(&s).unwrap();
|
||||
return Ok(Some(deserialize));
|
||||
}
|
||||
if let Some(v) = views {
|
||||
c.hset::<_, _, _, ()>(&key, "views", v).map_err(|_| "Unable to set note")?;
|
||||
}
|
||||
if let Some(e) = expiration {
|
||||
let now = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
let ttl = e.saturating_sub(now);
|
||||
c.expire::<_, ()>(&key, ttl as i64).map_err(|_| "Unable to set expiration")?;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn del(id: &String) -> Result<(), &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut conn = get_connection()?;
|
||||
conn.del::<_, ()>(key.as_str()).map_err(|_| "Unable to delete note in redis")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_meta(id: &str) -> Result<Option<(Option<i64>, Option<u64>, Vec<u8>)>, &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut c = conn()?;
|
||||
|
||||
let exists: bool = c.exists::<_, bool>(&key).map_err(|_| "Cache error")?;
|
||||
if !exists {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let views: Option<i64> = c.hget::<_, _, Option<i64>>(&key, "views").map_err(|_| "Cache error")?;
|
||||
let expiration: Option<u64> = c.hget::<_, _, Option<u64>>(&key, "expiration").map_err(|_| "Cache error")?;
|
||||
let extra: Vec<u8> = c.hget::<_, _, Vec<u8>>(&key, "extra").unwrap_or_default();
|
||||
|
||||
Ok(Some((views, expiration, extra)))
|
||||
}
|
||||
|
||||
pub fn get_data(id: &str) -> Result<Option<Vec<u8>>, &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut c = conn()?;
|
||||
let data: Option<Vec<u8>> = c.hget::<_, _, Option<Vec<u8>>>(&key, "data").map_err(|_| "Cache error")?;
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
pub fn has_views(id: &str) -> Result<bool, &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut c = conn()?;
|
||||
let has: bool = c.hexists::<_, _, bool>(&key, "views").map_err(|_| "Cache error")?;
|
||||
Ok(has)
|
||||
}
|
||||
|
||||
pub fn decrement_views(id: &str) -> Result<i64, &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut c = conn()?;
|
||||
let result: i64 = c.hincr::<_, _, _, i64>(&key, "views", -1).map_err(|_| "Cache error")?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn del(id: &str) -> Result<(), &'static str> {
|
||||
let key = prefixed(id);
|
||||
let mut c = conn()?;
|
||||
c.del::<_, ()>(&key).map_err(|_| "Unable to delete note")?;
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user