feat: started db cli tool; switched to i64 ids

episode-actions
Jef Roosens 2025-02-23 17:06:26 +01:00
parent b343fbccea
commit 1f4b0c35c5
No known key found for this signature in database
GPG Key ID: 21FD3D77D56BAF49
10 changed files with 86 additions and 29 deletions

2
.env
View File

@ -1 +1 @@
DATABASE_URL=data/db.sqlite3
DATABASE_URL=data/otter.sqlite3

View File

@ -6,8 +6,8 @@ The (initial) goal here is only to support JSON. Other formats *might* be added
on later, no guarantees.
* Authentication API
- [ ] Login / Verify Login
- [ ] Logout
- [x] Login / Verify Login
- [x] Logout
* Directory API
- [ ] Retrieve Top Tags
- [ ] Retrieve Podcasts for Tag

View File

@ -4,6 +4,7 @@
[print_schema]
file = "src/db/schema.rs"
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
sqlite_integer_primary_key_is_bigint = true
[migrations_directory]
dir = "migrations"

View File

@ -1,5 +1,5 @@
create table users (
id bigint primary key not null,
id integer primary key not null,
username text unique not null,
password_hash text not null
);

49
src/cli/db.rs 100644
View File

@ -0,0 +1,49 @@
use clap::Subcommand;
use crate::{db::DbResult, ErrorExt};
/// Tools to view and manage the database.
#[derive(Subcommand)]
pub enum DbCommand {
#[command(subcommand)]
Add(AddCommand),
}
/// Insert a new entity into the database
#[derive(Subcommand)]
pub enum AddCommand {
User { username: String, password: String },
}
impl DbCommand {
pub fn run(&self, cli: &super::Cli) -> u8 {
match self {
DbCommand::Add(cmd) => cmd.run(cli),
}
}
}
impl AddCommand {
pub fn run(&self, cli: &super::Cli) -> u8 {
match self.run_err(cli) {
Ok(()) => 0,
Err(err) => {
eprintln!("An error occured: {}", err.stack());
1
}
}
}
pub fn run_err(&self, cli: &super::Cli) -> DbResult<()> {
let pool = crate::db::initialize_db(cli.data_dir.join(crate::DB_FILENAME), false)?;
match self {
Self::User { username, password } => {
crate::db::NewUser::new(username.clone(), password.clone()).insert(&pool)?;
}
}
Ok(())
}
}

View File

@ -1,9 +1,12 @@
mod db;
mod serve;
use std::path::PathBuf;
use clap::{Parser, Subcommand};
/// Otter is a lightweight implementation of the Gpodder API, designed to be used for small
/// personal deployments.
#[derive(Parser)]
pub struct Cli {
#[arg(
@ -21,12 +24,15 @@ pub struct Cli {
#[derive(Subcommand)]
pub enum Command {
Serve(serve::ServeCommand),
#[command(subcommand)]
Db(db::DbCommand),
}
impl Cli {
pub fn run(&self) -> u8 {
match &self.cmd {
Command::Serve(cmd) => cmd.run(self),
Command::Db(cmd) => cmd.run(self),
}
}
}

View File

@ -2,8 +2,7 @@ use clap::Args;
use crate::{db, server};
const DB_FILENAME: &str = "otter.sqlite3";
/// Run the Otter web server
#[derive(Args)]
pub struct ServeCommand {
#[arg(
@ -31,7 +30,7 @@ impl ServeCommand {
tracing::info!("Initializing database and running migrations");
let pool = db::initialize_db(cli.data_dir.join(DB_FILENAME), true).unwrap();
let pool = db::initialize_db(cli.data_dir.join(crate::DB_FILENAME), true).unwrap();
let ctx = server::Context { pool };
let app = server::app().with_state(ctx);

View File

@ -1,5 +1,5 @@
use argon2::{password_hash::SaltString, Argon2, PasswordHash, PasswordHasher, PasswordVerifier};
use diesel::prelude::*;
use diesel::{prelude::*, sqlite::Sqlite};
use rand::rngs::OsRng;
use serde::{Deserialize, Serialize};

View File

@ -4,7 +4,27 @@ mod server;
use clap::Parser;
use std::process::ExitCode;
use std::{fmt::Write, process::ExitCode};
const DB_FILENAME: &str = "otter.sqlite3";
pub trait ErrorExt: std::error::Error {
/// Return the full chain of error messages
fn stack(&self) -> String {
let mut msg = format!("{}", self);
let mut err = self.source();
while let Some(src) = err {
write!(msg, " - {}", src).unwrap();
err = src.source();
}
msg
}
}
impl<E: std::error::Error> ErrorExt for E {}
fn main() -> ExitCode {
let args = cli::Cli::parse();

View File

@ -1,8 +1,8 @@
use std::fmt::{self, Write};
use std::fmt;
use axum::{http::StatusCode, response::IntoResponse};
use crate::db;
use crate::{db, ErrorExt};
pub type AppResult<T> = Result<T, AppError>;
@ -40,24 +40,6 @@ impl std::error::Error for AppError {
}
}
pub trait ErrorExt: std::error::Error {
/// Return the full chain of error messages
fn stack(&self) -> String {
let mut msg = format!("{}", self);
let mut err = self.source();
while let Some(src) = err {
write!(msg, " - {}", src).unwrap();
err = src.source();
}
msg
}
}
impl<E: std::error::Error> ErrorExt for E {}
impl From<db::DbError> for AppError {
fn from(value: db::DbError) -> Self {
Self::Db(value)