aboutsummaryrefslogtreecommitdiffstats
path: root/crates/atuin-server/src/handlers
diff options
context:
space:
mode:
authorBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-11 00:54:30 +0200
committerBenedikt Peetz <benedikt.peetz@b-peetz.de>2026-06-11 00:54:30 +0200
commit5c39e7cf284a1f6e9a1657f2deb44e359fc47eb8 (patch)
treec64baa8d5866c8e339eaf660dd3f94f30a3f7d8a /crates/atuin-server/src/handlers
parentchore: Somewhat simplify sync code (diff)
downloadatuin-5c39e7cf284a1f6e9a1657f2deb44e359fc47eb8.zip
chore: Move everything into one big crate
That helps remove duplicated code and rustc/cargo will now also show dead code correctly.
Diffstat (limited to 'crates/atuin-server/src/handlers')
-rw-r--r--crates/atuin-server/src/handlers/health.rs15
-rw-r--r--crates/atuin-server/src/handlers/history.rs237
-rw-r--r--crates/atuin-server/src/handlers/mod.rs60
-rw-r--r--crates/atuin-server/src/handlers/record.rs42
-rw-r--r--crates/atuin-server/src/handlers/status.rs45
-rw-r--r--crates/atuin-server/src/handlers/user.rs269
-rw-r--r--crates/atuin-server/src/handlers/v0/me.rs16
-rw-r--r--crates/atuin-server/src/handlers/v0/mod.rs3
-rw-r--r--crates/atuin-server/src/handlers/v0/record.rs114
-rw-r--r--crates/atuin-server/src/handlers/v0/store.rs37
10 files changed, 0 insertions, 838 deletions
diff --git a/crates/atuin-server/src/handlers/health.rs b/crates/atuin-server/src/handlers/health.rs
deleted file mode 100644
index aebd1e8f..00000000
--- a/crates/atuin-server/src/handlers/health.rs
+++ /dev/null
@@ -1,15 +0,0 @@
-use axum::{Json, http, response::IntoResponse};
-
-use serde::Serialize;
-
-#[derive(Serialize)]
-pub struct HealthResponse {
- pub status: &'static str,
-}
-
-pub async fn health_check() -> impl IntoResponse {
- (
- http::StatusCode::OK,
- Json(HealthResponse { status: "healthy" }),
- )
-}
diff --git a/crates/atuin-server/src/handlers/history.rs b/crates/atuin-server/src/handlers/history.rs
deleted file mode 100644
index bdafcc60..00000000
--- a/crates/atuin-server/src/handlers/history.rs
+++ /dev/null
@@ -1,237 +0,0 @@
-use std::{collections::HashMap, convert::TryFrom};
-
-use axum::{
- Json,
- extract::{Path, Query, State},
- http::{HeaderMap, StatusCode},
-};
-use metrics::counter;
-use time::{Month, UtcOffset};
-use tracing::{debug, error, instrument};
-
-use super::{ErrorResponse, ErrorResponseStatus, RespExt};
-use crate::{
- router::{AppState, UserAuth},
- utils::client_version_min,
-};
-use atuin_server_database::{
- Database,
- calendar::{TimePeriod, TimePeriodInfo},
- models::NewHistory,
-};
-
-use atuin_common::api::*;
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn count<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
-) -> Result<Json<CountResponse>, ErrorResponseStatus<'static>> {
- let db = &state.0.database;
- match db.count_history_cached(&user).await {
- // By default read out the cached value
- Ok(count) => Ok(Json(CountResponse { count })),
-
- // If that fails, fallback on a full COUNT. Cache is built on a POST
- // only
- Err(_) => match db.count_history(&user).await {
- Ok(count) => Ok(Json(CountResponse { count })),
- Err(_) => Err(ErrorResponse::reply("failed to query history count")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR)),
- },
- }
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn list<DB: Database>(
- req: Query<SyncHistoryRequest>,
- UserAuth(user): UserAuth,
- headers: HeaderMap,
- state: State<AppState<DB>>,
-) -> Result<Json<SyncHistoryResponse>, ErrorResponseStatus<'static>> {
- let db = &state.0.database;
-
- let agent = headers
- .get("user-agent")
- .map_or("", |v| v.to_str().unwrap_or(""));
-
- let variable_page_size = client_version_min(agent, ">=15.0.0").unwrap_or(false);
-
- let page_size = if variable_page_size {
- state.settings.page_size
- } else {
- 100
- };
-
- if req.sync_ts.unix_timestamp_nanos() < 0 || req.history_ts.unix_timestamp_nanos() < 0 {
- error!("client asked for history from < epoch 0");
- counter!("atuin_history_epoch_before_zero").increment(1);
-
- return Err(
- ErrorResponse::reply("asked for history from before epoch 0")
- .with_status(StatusCode::BAD_REQUEST),
- );
- }
-
- let history = db
- .list_history(&user, req.sync_ts, req.history_ts, &req.host, page_size)
- .await;
-
- if let Err(e) = history {
- error!("failed to load history: {}", e);
- return Err(ErrorResponse::reply("failed to load history")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
-
- let history: Vec<String> = history
- .unwrap()
- .iter()
- .map(|i| i.data.to_string())
- .collect();
-
- debug!(
- "loaded {} items of history for user {}",
- history.len(),
- user.id
- );
-
- counter!("atuin_history_returned").increment(history.len() as u64);
-
- Ok(Json(SyncHistoryResponse { history }))
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn delete<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
- Json(req): Json<DeleteHistoryRequest>,
-) -> Result<Json<MessageResponse>, ErrorResponseStatus<'static>> {
- let db = &state.0.database;
-
- // user_id is the ID of the history, as set by the user (the server has its own ID)
- let deleted = db.delete_history(&user, req.client_id).await;
-
- if let Err(e) = deleted {
- error!("failed to delete history: {}", e);
- return Err(ErrorResponse::reply("failed to delete history")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
-
- Ok(Json(MessageResponse {
- message: String::from("deleted OK"),
- }))
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn add<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
- Json(req): Json<Vec<AddHistoryRequest>>,
-) -> Result<(), ErrorResponseStatus<'static>> {
- let State(AppState { database, settings }) = state;
-
- debug!("request to add {} history items", req.len());
- counter!("atuin_history_uploaded").increment(req.len() as u64);
-
- let mut history: Vec<NewHistory> = req
- .into_iter()
- .map(|h| NewHistory {
- client_id: h.id,
- user_id: user.id,
- hostname: h.hostname,
- timestamp: h.timestamp,
- data: h.data,
- })
- .collect();
-
- history.retain(|h| {
- // keep if within limit, or limit is 0 (unlimited)
- let keep = h.data.len() <= settings.max_history_length || settings.max_history_length == 0;
-
- // Don't return an error here. We want to insert as much of the
- // history list as we can, so log the error and continue going.
- if !keep {
- counter!("atuin_history_too_long").increment(1);
-
- tracing::warn!(
- "history too long, got length {}, max {}",
- h.data.len(),
- settings.max_history_length
- );
- }
-
- keep
- });
-
- if let Err(e) = database.add_history(&history).await {
- error!("failed to add history: {}", e);
-
- return Err(ErrorResponse::reply("failed to add history")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- };
-
- Ok(())
-}
-
-#[derive(serde::Deserialize, Debug)]
-pub struct CalendarQuery {
- #[serde(default = "serde_calendar::zero")]
- year: i32,
- #[serde(default = "serde_calendar::one")]
- month: u8,
-
- #[serde(default = "serde_calendar::utc")]
- tz: UtcOffset,
-}
-
-mod serde_calendar {
- use time::UtcOffset;
-
- pub fn zero() -> i32 {
- 0
- }
-
- pub fn one() -> u8 {
- 1
- }
-
- pub fn utc() -> UtcOffset {
- UtcOffset::UTC
- }
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn calendar<DB: Database>(
- Path(focus): Path<String>,
- Query(params): Query<CalendarQuery>,
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
-) -> Result<Json<HashMap<u64, TimePeriodInfo>>, ErrorResponseStatus<'static>> {
- let focus = focus.as_str();
-
- let year = params.year;
- let month = Month::try_from(params.month).map_err(|e| ErrorResponseStatus {
- error: ErrorResponse {
- reason: e.to_string().into(),
- },
- status: StatusCode::BAD_REQUEST,
- })?;
-
- let period = match focus {
- "year" => TimePeriod::Year,
- "month" => TimePeriod::Month { year },
- "day" => TimePeriod::Day { year, month },
- _ => {
- return Err(ErrorResponse::reply("invalid focus: use year/month/day")
- .with_status(StatusCode::BAD_REQUEST));
- }
- };
-
- let db = &state.0.database;
- let focus = db.calendar(&user, period, params.tz).await.map_err(|_| {
- ErrorResponse::reply("failed to query calendar")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR)
- })?;
-
- Ok(Json(focus))
-}
diff --git a/crates/atuin-server/src/handlers/mod.rs b/crates/atuin-server/src/handlers/mod.rs
deleted file mode 100644
index 2176ac5e..00000000
--- a/crates/atuin-server/src/handlers/mod.rs
+++ /dev/null
@@ -1,60 +0,0 @@
-use atuin_common::api::{ErrorResponse, IndexResponse};
-use atuin_server_database::Database;
-use axum::{Json, extract::State, http, response::IntoResponse};
-
-use crate::router::AppState;
-
-pub mod health;
-pub mod history;
-pub mod record;
-pub mod status;
-pub mod user;
-pub mod v0;
-
-const VERSION: &str = env!("CARGO_PKG_VERSION");
-
-pub async fn index<DB: Database>(state: State<AppState<DB>>) -> Json<IndexResponse> {
- let homage = r#""Through the fathomless deeps of space swims the star turtle Great A'Tuin, bearing on its back the four giant elephants who carry on their shoulders the mass of the Discworld." -- Sir Terry Pratchett"#;
-
- let version = state
- .settings
- .fake_version
- .clone()
- .unwrap_or(VERSION.to_string());
-
- Json(IndexResponse {
- homage: homage.to_string(),
- version,
- })
-}
-
-impl IntoResponse for ErrorResponseStatus<'_> {
- fn into_response(self) -> axum::response::Response {
- (self.status, Json(self.error)).into_response()
- }
-}
-
-pub struct ErrorResponseStatus<'a> {
- pub error: ErrorResponse<'a>,
- pub status: http::StatusCode,
-}
-
-pub trait RespExt<'a> {
- fn with_status(self, status: http::StatusCode) -> ErrorResponseStatus<'a>;
- fn reply(reason: &'a str) -> Self;
-}
-
-impl<'a> RespExt<'a> for ErrorResponse<'a> {
- fn with_status(self, status: http::StatusCode) -> ErrorResponseStatus<'a> {
- ErrorResponseStatus {
- error: self,
- status,
- }
- }
-
- fn reply(reason: &'a str) -> ErrorResponse<'a> {
- Self {
- reason: reason.into(),
- }
- }
-}
diff --git a/crates/atuin-server/src/handlers/record.rs b/crates/atuin-server/src/handlers/record.rs
deleted file mode 100644
index 410c54bd..00000000
--- a/crates/atuin-server/src/handlers/record.rs
+++ /dev/null
@@ -1,42 +0,0 @@
-use axum::{Json, http::StatusCode, response::IntoResponse};
-use serde_json::json;
-use tracing::instrument;
-
-use super::{ErrorResponse, ErrorResponseStatus, RespExt};
-use crate::router::UserAuth;
-
-use atuin_common::record::{EncryptedData, Record};
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn post(UserAuth(user): UserAuth) -> Result<(), ErrorResponseStatus<'static>> {
- // anyone who has actually used the old record store (a very small number) will see this error
- // upon trying to sync.
- // 1. The status endpoint will say that the server has nothing
- // 2. The client will try to upload local records
- // 3. Sync will fail with this error
-
- // If the client has no local records, they will see the empty index and do nothing. For the
- // vast majority of users, this is the case.
- return Err(
- ErrorResponse::reply("record store deprecated; please upgrade")
- .with_status(StatusCode::BAD_REQUEST),
- );
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn index(UserAuth(user): UserAuth) -> axum::response::Response {
- let ret = json!({
- "hosts": {}
- });
-
- ret.to_string().into_response()
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn next(
- UserAuth(user): UserAuth,
-) -> Result<Json<Vec<Record<EncryptedData>>>, ErrorResponseStatus<'static>> {
- let records = Vec::new();
-
- Ok(Json(records))
-}
diff --git a/crates/atuin-server/src/handlers/status.rs b/crates/atuin-server/src/handlers/status.rs
deleted file mode 100644
index 9c152d51..00000000
--- a/crates/atuin-server/src/handlers/status.rs
+++ /dev/null
@@ -1,45 +0,0 @@
-use axum::{Json, extract::State, http::StatusCode};
-use tracing::instrument;
-
-use super::{ErrorResponse, ErrorResponseStatus, RespExt};
-use crate::router::{AppState, UserAuth};
-use atuin_server_database::Database;
-
-use atuin_common::api::*;
-
-const VERSION: &str = env!("CARGO_PKG_VERSION");
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn status<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
-) -> Result<Json<StatusResponse>, ErrorResponseStatus<'static>> {
- let db = &state.0.database;
-
- let deleted = db.deleted_history(&user).await.unwrap_or(vec![]);
-
- let count = match db.count_history_cached(&user).await {
- // By default read out the cached value
- Ok(count) => count,
-
- // If that fails, fallback on a full COUNT. Cache is built on a POST
- // only
- Err(_) => match db.count_history(&user).await {
- Ok(count) => count,
- Err(_) => {
- return Err(ErrorResponse::reply("failed to query history count")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
- },
- };
-
- tracing::debug!(user = user.username, "requested sync status");
-
- Ok(Json(StatusResponse {
- count,
- deleted,
- username: user.username,
- version: VERSION.to_string(),
- page_size: state.settings.page_size,
- }))
-}
diff --git a/crates/atuin-server/src/handlers/user.rs b/crates/atuin-server/src/handlers/user.rs
deleted file mode 100644
index dda7a381..00000000
--- a/crates/atuin-server/src/handlers/user.rs
+++ /dev/null
@@ -1,269 +0,0 @@
-use std::borrow::Borrow;
-use std::collections::HashMap;
-use std::time::Duration;
-
-use argon2::{
- Algorithm, Argon2, Params, PasswordHash, PasswordHasher, PasswordVerifier, Version,
- password_hash::SaltString,
-};
-use axum::{
- Json,
- extract::{Path, State},
- http::StatusCode,
-};
-use metrics::counter;
-
-use rand::rngs::OsRng;
-use tracing::{debug, error, info, instrument};
-
-use atuin_common::tls::ensure_crypto_provider;
-
-use super::{ErrorResponse, ErrorResponseStatus, RespExt};
-use crate::router::{AppState, UserAuth};
-use atuin_server_database::{
- Database, DbError,
- models::{NewSession, NewUser},
-};
-
-use reqwest::header::CONTENT_TYPE;
-
-use atuin_common::{api::*, utils::crypto_random_string};
-
-pub fn verify_str(hash: &str, password: &str) -> bool {
- let arg2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default());
- let Ok(hash) = PasswordHash::new(hash) else {
- return false;
- };
- arg2.verify_password(password.as_bytes(), &hash).is_ok()
-}
-
-// Try to send a Discord webhook once - if it fails, we don't retry. "At most once", and best effort.
-// Don't return the status because if this fails, we don't really care.
-async fn send_register_hook(url: &str, username: String, registered: String) {
- ensure_crypto_provider();
- let hook = HashMap::from([
- ("username", username),
- ("content", format!("{registered} has just signed up!")),
- ]);
-
- let client = reqwest::Client::new();
-
- let resp = client
- .post(url)
- .timeout(Duration::new(5, 0))
- .header(CONTENT_TYPE, "application/json")
- .json(&hook)
- .send()
- .await;
-
- match resp {
- Ok(_) => info!("register webhook sent ok!"),
- Err(e) => error!("failed to send register webhook: {}", e),
- }
-}
-
-#[instrument(skip_all, fields(user.username = username.as_str()))]
-pub async fn get<DB: Database>(
- Path(username): Path<String>,
- state: State<AppState<DB>>,
-) -> Result<Json<UserResponse>, ErrorResponseStatus<'static>> {
- let db = &state.0.database;
- let user = match db.get_user(username.as_ref()).await {
- Ok(user) => user,
- Err(DbError::NotFound) => {
- debug!("user not found: {}", username);
- return Err(ErrorResponse::reply("user not found").with_status(StatusCode::NOT_FOUND));
- }
- Err(DbError::Other(err)) => {
- error!("database error: {}", err);
- return Err(ErrorResponse::reply("database error")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
- };
-
- Ok(Json(UserResponse {
- username: user.username,
- }))
-}
-
-#[instrument(skip_all)]
-pub async fn register<DB: Database>(
- state: State<AppState<DB>>,
- Json(register): Json<RegisterRequest>,
-) -> Result<Json<RegisterResponse>, ErrorResponseStatus<'static>> {
- if !state.settings.open_registration {
- return Err(
- ErrorResponse::reply("this server is not open for registrations")
- .with_status(StatusCode::BAD_REQUEST),
- );
- }
-
- for c in register.username.chars() {
- match c {
- 'a'..='z' | 'A'..='Z' | '0'..='9' | '-' => {}
- _ => {
- return Err(ErrorResponse::reply(
- "Only alphanumeric and hyphens (-) are allowed in usernames",
- )
- .with_status(StatusCode::BAD_REQUEST));
- }
- }
- }
-
- let hashed = hash_secret(&register.password);
-
- let new_user = NewUser {
- email: register.email.clone(),
- username: register.username.clone(),
- password: hashed,
- };
-
- let db = &state.0.database;
- let user_id = match db.add_user(&new_user).await {
- Ok(id) => id,
- Err(e) => {
- error!("failed to add user: {}", e);
- return Err(
- ErrorResponse::reply("failed to add user").with_status(StatusCode::BAD_REQUEST)
- );
- }
- };
-
- // 24 bytes encoded as base64
- let token = crypto_random_string::<24>();
-
- let new_session = NewSession {
- user_id,
- token: (&token).into(),
- };
-
- if let Some(url) = &state.settings.register_webhook_url {
- // Could probs be run on another thread, but it's ok atm
- send_register_hook(
- url,
- state.settings.register_webhook_username.clone(),
- register.username,
- )
- .await;
- }
-
- counter!("atuin_users_registered").increment(1);
-
- match db.add_session(&new_session).await {
- Ok(_) => Ok(Json(RegisterResponse {
- session: token,
- auth: Some("cli".into()),
- })),
- Err(e) => {
- error!("failed to add session: {}", e);
- Err(ErrorResponse::reply("failed to register user")
- .with_status(StatusCode::BAD_REQUEST))
- }
- }
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn delete<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
-) -> Result<Json<DeleteUserResponse>, ErrorResponseStatus<'static>> {
- debug!("request to delete user {}", user.id);
-
- let db = &state.0.database;
- if let Err(e) = db.delete_user(&user).await {
- error!("failed to delete user: {}", e);
-
- return Err(ErrorResponse::reply("failed to delete user")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- };
-
- counter!("atuin_users_deleted").increment(1);
-
- Ok(Json(DeleteUserResponse {}))
-}
-
-#[instrument(skip_all, fields(user.id = user.id, change_password))]
-pub async fn change_password<DB: Database>(
- UserAuth(mut user): UserAuth,
- state: State<AppState<DB>>,
- Json(change_password): Json<ChangePasswordRequest>,
-) -> Result<Json<ChangePasswordResponse>, ErrorResponseStatus<'static>> {
- let db = &state.0.database;
-
- let verified = verify_str(
- user.password.as_str(),
- change_password.current_password.borrow(),
- );
- if !verified {
- return Err(
- ErrorResponse::reply("password is not correct").with_status(StatusCode::UNAUTHORIZED)
- );
- }
-
- let hashed = hash_secret(&change_password.new_password);
- user.password = hashed;
-
- if let Err(e) = db.update_user_password(&user).await {
- error!("failed to change user password: {}", e);
-
- return Err(ErrorResponse::reply("failed to change user password")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- };
- Ok(Json(ChangePasswordResponse {}))
-}
-
-#[instrument(skip_all, fields(user.username = login.username.as_str()))]
-pub async fn login<DB: Database>(
- state: State<AppState<DB>>,
- login: Json<LoginRequest>,
-) -> Result<Json<LoginResponse>, ErrorResponseStatus<'static>> {
- let db = &state.0.database;
- let user = match db.get_user(login.username.borrow()).await {
- Ok(u) => u,
- Err(DbError::NotFound) => {
- return Err(ErrorResponse::reply("user not found").with_status(StatusCode::NOT_FOUND));
- }
- Err(DbError::Other(e)) => {
- error!("failed to get user {}: {}", login.username.clone(), e);
-
- return Err(ErrorResponse::reply("database error")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
- };
-
- let session = match db.get_user_session(&user).await {
- Ok(u) => u,
- Err(DbError::NotFound) => {
- debug!("user session not found for user id={}", user.id);
- return Err(ErrorResponse::reply("user not found").with_status(StatusCode::NOT_FOUND));
- }
- Err(DbError::Other(err)) => {
- error!("database error for user {}: {}", login.username, err);
- return Err(ErrorResponse::reply("database error")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
- };
-
- let verified = verify_str(user.password.as_str(), login.password.borrow());
-
- if !verified {
- debug!(user = user.username, "login failed");
- return Err(
- ErrorResponse::reply("password is not correct").with_status(StatusCode::UNAUTHORIZED)
- );
- }
-
- debug!(user = user.username, "login success");
-
- Ok(Json(LoginResponse {
- session: session.token,
- auth: Some("cli".into()),
- }))
-}
-
-fn hash_secret(password: &str) -> String {
- let arg2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default());
- let salt = SaltString::generate(&mut OsRng);
- let hash = arg2.hash_password(password.as_bytes(), &salt).unwrap();
- hash.to_string()
-}
diff --git a/crates/atuin-server/src/handlers/v0/me.rs b/crates/atuin-server/src/handlers/v0/me.rs
deleted file mode 100644
index 7960b479..00000000
--- a/crates/atuin-server/src/handlers/v0/me.rs
+++ /dev/null
@@ -1,16 +0,0 @@
-use axum::Json;
-use tracing::instrument;
-
-use crate::handlers::ErrorResponseStatus;
-use crate::router::UserAuth;
-
-use atuin_common::api::*;
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn get(
- UserAuth(user): UserAuth,
-) -> Result<Json<MeResponse>, ErrorResponseStatus<'static>> {
- Ok(Json(MeResponse {
- username: user.username,
- }))
-}
diff --git a/crates/atuin-server/src/handlers/v0/mod.rs b/crates/atuin-server/src/handlers/v0/mod.rs
deleted file mode 100644
index d6f880f2..00000000
--- a/crates/atuin-server/src/handlers/v0/mod.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub(crate) mod me;
-pub(crate) mod record;
-pub(crate) mod store;
diff --git a/crates/atuin-server/src/handlers/v0/record.rs b/crates/atuin-server/src/handlers/v0/record.rs
deleted file mode 100644
index 5c57910b..00000000
--- a/crates/atuin-server/src/handlers/v0/record.rs
+++ /dev/null
@@ -1,114 +0,0 @@
-use axum::{Json, extract::Query, extract::State, http::StatusCode};
-use metrics::counter;
-use serde::Deserialize;
-use tracing::{error, instrument};
-
-use crate::{
- handlers::{ErrorResponse, ErrorResponseStatus, RespExt},
- router::{AppState, UserAuth},
-};
-use atuin_server_database::Database;
-
-use atuin_common::record::{EncryptedData, HostId, Record, RecordIdx, RecordStatus};
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn post<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
- Json(records): Json<Vec<Record<EncryptedData>>>,
-) -> Result<(), ErrorResponseStatus<'static>> {
- let State(AppState { database, settings }) = state;
-
- tracing::debug!(
- count = records.len(),
- user = user.username,
- "request to add records"
- );
-
- counter!("atuin_record_uploaded").increment(records.len() as u64);
-
- let keep = records
- .iter()
- .all(|r| r.data.data.len() <= settings.max_record_size || settings.max_record_size == 0);
-
- if !keep {
- counter!("atuin_record_too_large").increment(1);
-
- return Err(
- ErrorResponse::reply("could not add records; record too large")
- .with_status(StatusCode::BAD_REQUEST),
- );
- }
-
- if let Err(e) = database.add_records(&user, &records).await {
- error!("failed to add record: {}", e);
-
- return Err(ErrorResponse::reply("failed to add record")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- };
-
- Ok(())
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn index<DB: Database>(
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
-) -> Result<Json<RecordStatus>, ErrorResponseStatus<'static>> {
- let State(AppState {
- database,
- settings: _,
- }) = state;
-
- let record_index = match database.status(&user).await {
- Ok(index) => index,
- Err(e) => {
- error!("failed to get record index: {}", e);
-
- return Err(ErrorResponse::reply("failed to calculate record index")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
- };
-
- tracing::debug!(user = user.username, "record index request");
-
- Ok(Json(record_index))
-}
-
-#[derive(Deserialize)]
-pub struct NextParams {
- host: HostId,
- tag: String,
- start: Option<RecordIdx>,
- count: u64,
-}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn next<DB: Database>(
- params: Query<NextParams>,
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
-) -> Result<Json<Vec<Record<EncryptedData>>>, ErrorResponseStatus<'static>> {
- let State(AppState {
- database,
- settings: _,
- }) = state;
- let params = params.0;
-
- let records = match database
- .next_records(&user, params.host, params.tag, params.start, params.count)
- .await
- {
- Ok(records) => records,
- Err(e) => {
- error!("failed to get record index: {}", e);
-
- return Err(ErrorResponse::reply("failed to calculate record index")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
- };
-
- counter!("atuin_record_downloaded").increment(records.len() as u64);
-
- Ok(Json(records))
-}
diff --git a/crates/atuin-server/src/handlers/v0/store.rs b/crates/atuin-server/src/handlers/v0/store.rs
deleted file mode 100644
index 6ca455d7..00000000
--- a/crates/atuin-server/src/handlers/v0/store.rs
+++ /dev/null
@@ -1,37 +0,0 @@
-use axum::{extract::Query, extract::State, http::StatusCode};
-use metrics::counter;
-use serde::Deserialize;
-use tracing::{error, instrument};
-
-use crate::{
- handlers::{ErrorResponse, ErrorResponseStatus, RespExt},
- router::{AppState, UserAuth},
-};
-use atuin_server_database::Database;
-
-#[derive(Deserialize)]
-pub struct DeleteParams {}
-
-#[instrument(skip_all, fields(user.id = user.id))]
-pub async fn delete<DB: Database>(
- _params: Query<DeleteParams>,
- UserAuth(user): UserAuth,
- state: State<AppState<DB>>,
-) -> Result<(), ErrorResponseStatus<'static>> {
- let State(AppState {
- database,
- settings: _,
- }) = state;
-
- if let Err(e) = database.delete_store(&user).await {
- counter!("atuin_store_delete_failed").increment(1);
- error!("failed to delete store {e:?}");
-
- return Err(ErrorResponse::reply("failed to delete store")
- .with_status(StatusCode::INTERNAL_SERVER_ERROR));
- }
-
- counter!("atuin_store_deleted").increment(1);
-
- Ok(())
-}