Made application monouser
This commit is contained in:
parent
1c1a9589b7
commit
d2a020b226
17 changed files with 165 additions and 856 deletions
|
|
@ -1,17 +1,8 @@
|
||||||
-- Add migration script here
|
-- Add migration script here
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS users(
|
|
||||||
user_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
||||||
username TEXT,
|
|
||||||
pass TEXT,
|
|
||||||
UNIQUE(username)
|
|
||||||
);
|
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS accounts(
|
CREATE TABLE IF NOT EXISTS accounts(
|
||||||
account_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
account_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
user INTEGER,
|
account_name TEXT
|
||||||
account_name TEXT,
|
|
||||||
FOREIGN KEY (user) REFERENCES users(user_id)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS categories (
|
CREATE TABLE IF NOT EXISTS categories (
|
||||||
|
|
@ -22,10 +13,8 @@ CREATE TABLE IF NOT EXISTS categories (
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS rules(
|
CREATE TABLE IF NOT EXISTS rules(
|
||||||
rule_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
rule_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
user INTEGER,
|
|
||||||
regex TEXT,
|
regex TEXT,
|
||||||
category INTEGER,
|
category INTEGER,
|
||||||
FOREIGN KEY (user) REFERENCES users(user_id)
|
|
||||||
FOREIGN KEY (category) REFERENCES categories(category_id)
|
FOREIGN KEY (category) REFERENCES categories(category_id)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
use chrono::prelude::*;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::{FromRow, Result, SqlitePool};
|
use sqlx::{FromRow, Result, SqlitePool};
|
||||||
|
|
||||||
|
|
@ -7,7 +6,6 @@ use super::{rules::Rule, transaction::Transaction};
|
||||||
#[derive(FromRow, Serialize, Deserialize, Debug)]
|
#[derive(FromRow, Serialize, Deserialize, Debug)]
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
account_id: i32,
|
account_id: i32,
|
||||||
user: i32,
|
|
||||||
account_name: String,
|
account_name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -16,10 +14,6 @@ impl Account {
|
||||||
self.account_id
|
self.account_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_user(&self) -> i32 {
|
|
||||||
self.user
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_account_name(&self) -> &str {
|
pub fn get_account_name(&self) -> &str {
|
||||||
self.account_name.as_str()
|
self.account_name.as_str()
|
||||||
}
|
}
|
||||||
|
|
@ -42,18 +36,16 @@ impl Account {
|
||||||
.and_then(|r| Account::from_row(&r))
|
.and_then(|r| Account::from_row(&r))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn new(pool: &SqlitePool, user: i32, name: &str) -> Result<Self> {
|
pub async fn new(pool: &SqlitePool, name: &str) -> Result<Self> {
|
||||||
let row = sqlx::query("INSERT INTO accounts(user, account_name) VALUES (?,?) RETURNING *")
|
let row = sqlx::query("INSERT INTO accounts(account_name) VALUES (?) RETURNING *")
|
||||||
.bind(user)
|
|
||||||
.bind(name)
|
.bind(name)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await?;
|
.await?;
|
||||||
Self::from_row(&row)
|
Self::from_row(&row)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list(pool: &SqlitePool, user: i32) -> Result<Vec<Self>> {
|
pub async fn list(pool: &SqlitePool) -> Result<Vec<Self>> {
|
||||||
let rows = sqlx::query("SELECT * FROM accounts WHERE user=?")
|
let rows = sqlx::query("SELECT * FROM accounts")
|
||||||
.bind(user)
|
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await?;
|
.await?;
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
|
|
@ -64,7 +56,7 @@ impl Account {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn recategorize_transactions(&self, pool: &SqlitePool) -> Result<()> {
|
pub async fn recategorize_transactions(&self, pool: &SqlitePool) -> Result<()> {
|
||||||
let rules = Rule::list_by_user(pool, self.user).await?;
|
let rules = Rule::list(pool).await?;
|
||||||
let mut tx_list = Transaction::list_uncategorized(pool, self.account_id).await?;
|
let mut tx_list = Transaction::list_uncategorized(pool, self.account_id).await?;
|
||||||
for tx in tx_list.iter_mut() {
|
for tx in tx_list.iter_mut() {
|
||||||
println!("Checking {}", tx.get_description());
|
println!("Checking {}", tx.get_description());
|
||||||
|
|
@ -84,7 +76,6 @@ impl Account {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Account;
|
use super::Account;
|
||||||
use crate::models::users::User;
|
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
async fn get_db() -> SqlitePool {
|
async fn get_db() -> SqlitePool {
|
||||||
|
|
@ -96,19 +87,10 @@ mod tests {
|
||||||
std::fs::remove_file("account_test.db").unwrap();
|
std::fs::remove_file("account_test.db").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn new_user(pool: &SqlitePool) -> User {
|
|
||||||
User::create_user(pool, "account_test", "pass")
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn create_test() {
|
async fn create_test() {
|
||||||
let pool = get_db().await;
|
let pool = get_db().await;
|
||||||
let user = new_user(&pool).await;
|
Account::new(&pool, "account_test").await.unwrap();
|
||||||
Account::new(&pool, user.get_id(), "account_test")
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
remove_db(pool).await;
|
remove_db(pool).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ use sqlx::{FromRow, SqlitePool};
|
||||||
#[derive(FromRow, Serialize)]
|
#[derive(FromRow, Serialize)]
|
||||||
pub struct Rule {
|
pub struct Rule {
|
||||||
pub rule_id: i32,
|
pub rule_id: i32,
|
||||||
pub user: i32,
|
|
||||||
pub regex: String,
|
pub regex: String,
|
||||||
pub category: i32,
|
pub category: i32,
|
||||||
}
|
}
|
||||||
|
|
@ -19,27 +18,8 @@ impl Rule {
|
||||||
.and_then(|r| Rule::from_row(&r))
|
.and_then(|r| Rule::from_row(&r))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_by_user(pool: &SqlitePool, user: i32) -> sqlx::Result<Vec<Self>> {
|
pub async fn new(pool: &SqlitePool, regex: String, category: i32) -> sqlx::Result<Self> {
|
||||||
let mut res = Vec::new();
|
sqlx::query("INSERT INTO rules(regex, category) VALUES (?,?) RETURNING *")
|
||||||
for r in sqlx::query("SELECT * FROM rules WHERE user=?")
|
|
||||||
.bind(user)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?
|
|
||||||
.iter()
|
|
||||||
{
|
|
||||||
res.push(Rule::from_row(r)?);
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn new(
|
|
||||||
pool: &SqlitePool,
|
|
||||||
user: i32,
|
|
||||||
regex: String,
|
|
||||||
category: i32,
|
|
||||||
) -> sqlx::Result<Self> {
|
|
||||||
sqlx::query("INSERT INTO rules(user, regex, category) VALUES (?,?,?) RETURNING *")
|
|
||||||
.bind(user)
|
|
||||||
.bind(regex)
|
.bind(regex)
|
||||||
.bind(category)
|
.bind(category)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
|
|
@ -47,6 +27,26 @@ impl Rule {
|
||||||
.and_then(|r| Rule::from_row(&r))
|
.and_then(|r| Rule::from_row(&r))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn list(pool: &SqlitePool) -> sqlx::Result<Vec<Self>> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
for r in sqlx::query("SELECT * FROM rules")
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?
|
||||||
|
.iter()
|
||||||
|
{
|
||||||
|
res.push(Rule::from_row(r)?)
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn delete(&self, pool: &SqlitePool) -> sqlx::Result<()> {
|
||||||
|
sqlx::query("DELETE FROM rules WHERE rule_id=?")
|
||||||
|
.bind(self.rule_id)
|
||||||
|
.execute(pool)
|
||||||
|
.await
|
||||||
|
.map(|_| ())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn matches(&self, description: &str) -> Result<bool, regex::Error> {
|
pub fn matches(&self, description: &str) -> Result<bool, regex::Error> {
|
||||||
let re = Regex::new(&self.regex)?;
|
let re = Regex::new(&self.regex)?;
|
||||||
Ok(re.is_match(description))
|
Ok(re.is_match(description))
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,25 @@ impl Transaction {
|
||||||
.and_then(|x| Transaction::from_row(&x))
|
.and_then(|x| Transaction::from_row(&x))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list(
|
pub async fn list(pool: &SqlitePool, limit: i32, offset: i32, asc: bool) -> Result<Vec<Self>> {
|
||||||
|
let rows = sqlx::query(if asc {
|
||||||
|
"SELECT * FROM transactions ORDER BY tx_date ASC LIMIT ? OFFSET ?"
|
||||||
|
} else {
|
||||||
|
"SELECT * FROM transactions ORDER BY tx_date DESC LIMIT ? OFFSET ?"
|
||||||
|
})
|
||||||
|
.bind(limit)
|
||||||
|
.bind(offset)
|
||||||
|
.fetch_all(pool)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let mut res = Vec::new();
|
||||||
|
for r in &rows {
|
||||||
|
res.push(Transaction::from_row(r)?);
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn list_by_account(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
account: i32,
|
account: i32,
|
||||||
limit: i32,
|
limit: i32,
|
||||||
|
|
@ -78,41 +96,19 @@ impl Transaction {
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list_by_user(
|
|
||||||
pool: &SqlitePool,
|
|
||||||
user: i32,
|
|
||||||
limit: i32,
|
|
||||||
offset: i32,
|
|
||||||
asc: bool,
|
|
||||||
) -> Result<Vec<Self>> {
|
|
||||||
let rows = sqlx::query(
|
|
||||||
if asc {
|
|
||||||
"SELECT t.* FROM transactions t JOIN accounts a ON a.account_id=t.account WHERE a.user=? ORDER BY tx_date ASC LIMIT ? OFFSET ?"
|
|
||||||
} else {
|
|
||||||
"SELECT t.* FROM transactions t JOIN accounts a ON a.account_id=t.account WHERE a.user=? ORDER BY tx_date DESC LIMIT ? OFFSET ?"
|
|
||||||
}
|
|
||||||
).bind(user)
|
|
||||||
.bind(limit)
|
|
||||||
.bind(offset)
|
|
||||||
.fetch_all(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let mut res = Vec::new();
|
|
||||||
for r in &rows {
|
|
||||||
res.push(Transaction::from_row(r)?);
|
|
||||||
}
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn query_by_date<'a>(
|
pub fn query_by_date<'a>(
|
||||||
account: i32,
|
account: Option<i32>,
|
||||||
after: Option<DateTime<Utc>>,
|
after: Option<DateTime<Utc>>,
|
||||||
before: Option<DateTime<Utc>>,
|
before: Option<DateTime<Utc>>,
|
||||||
limit: Option<i32>,
|
limit: Option<i32>,
|
||||||
asc: bool,
|
asc: bool,
|
||||||
) -> sqlx::QueryBuilder<'a, Sqlite> {
|
) -> sqlx::QueryBuilder<'a, Sqlite> {
|
||||||
let mut query = sqlx::QueryBuilder::new("SELECT * FROM TRANSACTIONS WHERE account=");
|
let mut query = sqlx::QueryBuilder::new("SELECT * FROM transactions WHERE TRUE ");
|
||||||
query.push_bind(account);
|
|
||||||
|
if let Some(acc) = account {
|
||||||
|
query.push(" AND account=");
|
||||||
|
query.push_bind(acc);
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(after) = after {
|
if let Some(after) = after {
|
||||||
query.push(" AND tx_date >= ");
|
query.push(" AND tx_date >= ");
|
||||||
|
|
@ -140,7 +136,7 @@ impl Transaction {
|
||||||
|
|
||||||
pub async fn list_by_date(
|
pub async fn list_by_date(
|
||||||
pool: &SqlitePool,
|
pool: &SqlitePool,
|
||||||
account: i32,
|
account: Option<i32>,
|
||||||
after: Option<DateTime<Utc>>,
|
after: Option<DateTime<Utc>>,
|
||||||
before: Option<DateTime<Utc>>,
|
before: Option<DateTime<Utc>>,
|
||||||
limit: Option<i32>,
|
limit: Option<i32>,
|
||||||
|
|
@ -284,7 +280,7 @@ impl Transaction {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Transaction;
|
use super::Transaction;
|
||||||
use crate::models::{account::Account, users::User};
|
use crate::models::account::Account;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
async fn get_db() -> SqlitePool {
|
async fn get_db() -> SqlitePool {
|
||||||
|
|
@ -296,15 +292,10 @@ mod tests {
|
||||||
std::fs::remove_file("tx_test.db").unwrap();
|
std::fs::remove_file("tx_test.db").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn new_user(pool: &SqlitePool) -> User {
|
|
||||||
User::create_user(pool, "testuser", "pass").await.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn create_test() {
|
async fn create_test() {
|
||||||
let pool = get_db().await;
|
let pool = get_db().await;
|
||||||
let user = new_user(&pool).await;
|
let acc = Account::new(&pool, "tx_test").await.unwrap();
|
||||||
let acc = Account::new(&pool, user.get_id(), "tx_test").await.unwrap();
|
|
||||||
let tx = Transaction::new(
|
let tx = Transaction::new(
|
||||||
&pool,
|
&pool,
|
||||||
acc.get_id(),
|
acc.get_id(),
|
||||||
|
|
|
||||||
|
|
@ -1,586 +0,0 @@
|
||||||
/*
|
|
||||||
! tailwindcss v3.4.3 | MIT License | https://tailwindcss.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
|
||||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
|
||||||
*/
|
|
||||||
|
|
||||||
*,
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
/* 1 */
|
|
||||||
border-width: 0;
|
|
||||||
/* 2 */
|
|
||||||
border-style: solid;
|
|
||||||
/* 2 */
|
|
||||||
border-color: #e5e7eb;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
--tw-content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use a consistent sensible line-height in all browsers.
|
|
||||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
||||||
3. Use a more readable tab size.
|
|
||||||
4. Use the user's configured `sans` font-family by default.
|
|
||||||
5. Use the user's configured `sans` font-feature-settings by default.
|
|
||||||
6. Use the user's configured `sans` font-variation-settings by default.
|
|
||||||
7. Disable tap highlights on iOS
|
|
||||||
*/
|
|
||||||
|
|
||||||
html,
|
|
||||||
:host {
|
|
||||||
line-height: 1.5;
|
|
||||||
/* 1 */
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
/* 2 */
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
-o-tab-size: 4;
|
|
||||||
tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
/* 4 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 5 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 6 */
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
/* 7 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove the margin in all browsers.
|
|
||||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Add the correct height in Firefox.
|
|
||||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
|
||||||
3. Ensure horizontal rules are visible by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
height: 0;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-top-width: 1px;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbr:where([title]) {
|
|
||||||
-webkit-text-decoration: underline dotted;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the default font size and weight for headings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-size: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset links to optimize for opt-in styling instead of opt-out.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font weight in Edge and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use the user's configured `mono` font-family by default.
|
|
||||||
2. Use the user's configured `mono` font-feature-settings by default.
|
|
||||||
3. Use the user's configured `mono` font-variation-settings by default.
|
|
||||||
4. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp,
|
|
||||||
pre {
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
/* 1 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 2 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 3 */
|
|
||||||
font-size: 1em;
|
|
||||||
/* 4 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font size in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
|
||||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
|
||||||
3. Remove gaps between table borders by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
text-indent: 0;
|
|
||||||
/* 1 */
|
|
||||||
border-color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-collapse: collapse;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Change the font styles in all browsers.
|
|
||||||
2. Remove the margin in Firefox and Safari.
|
|
||||||
3. Remove default padding in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-feature-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-variation-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-size: 100%;
|
|
||||||
/* 1 */
|
|
||||||
font-weight: inherit;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 1 */
|
|
||||||
letter-spacing: inherit;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 1 */
|
|
||||||
margin: 0;
|
|
||||||
/* 2 */
|
|
||||||
padding: 0;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inheritance of text transform in Edge and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Remove default button styles.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input:where([type='button']),
|
|
||||||
input:where([type='reset']),
|
|
||||||
input:where([type='submit']) {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
background-color: transparent;
|
|
||||||
/* 2 */
|
|
||||||
background-image: none;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Use the modern Firefox focus style for all focusable elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-focusring {
|
|
||||||
outline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-ui-invalid {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct vertical alignment in Chrome and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Correct the cursor style of increment and decrement buttons in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-inner-spin-button,
|
|
||||||
::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the odd appearance in Chrome and Safari.
|
|
||||||
2. Correct the outline style in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type='search'] {
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
/* 1 */
|
|
||||||
outline-offset: -2px;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inner padding in Chrome and Safari on macOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Change font properties to `inherit` in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
font: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct display in Chrome and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Removes the default spacing and border for appropriate elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
blockquote,
|
|
||||||
dl,
|
|
||||||
dd,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
hr,
|
|
||||||
figure,
|
|
||||||
p,
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
menu {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset default styling for dialogs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
dialog {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent resizing textareas horizontally by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
|
||||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder,
|
|
||||||
textarea::placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the default cursor for buttons.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[role="button"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Make sure disabled buttons don't get the pointer cursor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
|
||||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video,
|
|
||||||
canvas,
|
|
||||||
audio,
|
|
||||||
iframe,
|
|
||||||
embed,
|
|
||||||
object {
|
|
||||||
display: block;
|
|
||||||
/* 1 */
|
|
||||||
vertical-align: middle;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
|
||||||
|
|
||||||
[hidden] {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
*, ::before, ::after {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
::backdrop {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ars-input {
|
|
||||||
margin: 0.25rem;
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ars-input input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border: solid 1px #57534e;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ars-input select {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border: solid 1px #57534e;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ars-button {
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border: solid 1px #57534e;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ars-button:hover {
|
|
||||||
background: #57534e77;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod routes;
|
pub mod routes;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod static_values;
|
pub mod static_values;
|
||||||
pub mod users;
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
mod routes;
|
mod routes;
|
||||||
mod server;
|
mod server;
|
||||||
mod static_values;
|
mod static_values;
|
||||||
mod users;
|
|
||||||
|
|
||||||
const DB_URL: &str = "sqlite://sqlite.db";
|
const DB_URL: &str = "sqlite://sqlite.db";
|
||||||
|
|
||||||
|
|
@ -9,7 +8,7 @@ const DB_URL: &str = "sqlite://sqlite.db";
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let server = server::start_server("127.0.0.1:3000", DB_URL);
|
let server = server::start_server("127.0.0.1:3000", DB_URL);
|
||||||
|
|
||||||
/*let wv_task = tokio::task::spawn_blocking(|| {
|
let wv_task = tokio::task::spawn_blocking(|| {
|
||||||
web_view::builder()
|
web_view::builder()
|
||||||
.title("Test")
|
.title("Test")
|
||||||
.content(web_view::Content::Url("http://localhost:3000"))
|
.content(web_view::Content::Url("http://localhost:3000"))
|
||||||
|
|
@ -27,6 +26,5 @@ async fn main() {
|
||||||
e = server => {
|
e = server => {
|
||||||
println!("Axum finished with result {e:?}");
|
println!("Axum finished with result {e:?}");
|
||||||
}
|
}
|
||||||
}*/
|
}
|
||||||
server.await.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,27 +1,30 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::{Json, Path, State};
|
use axum::{
|
||||||
use hyper::StatusCode;
|
extract::{Json, Path, State},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use hyper::{header::CONTENT_TYPE, StatusCode};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::users::UserToken;
|
|
||||||
use accounters::models::account::Account;
|
use accounters::models::account::Account;
|
||||||
|
|
||||||
pub async fn account_get(
|
pub async fn account_get(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
uid: UserToken,
|
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
) -> (StatusCode, String) {
|
) -> impl IntoResponse {
|
||||||
match Account::get_by_id(db.as_ref(), id).await {
|
match Account::get_by_id(db.as_ref(), id).await {
|
||||||
Ok(a) => {
|
Ok(a) => (
|
||||||
if a.get_user() == uid.user_id {
|
StatusCode::OK,
|
||||||
(StatusCode::OK, serde_json::to_string(&a).unwrap())
|
[(CONTENT_TYPE, "application/json")],
|
||||||
} else {
|
serde_json::to_string(&a).unwrap(),
|
||||||
(StatusCode::UNAUTHORIZED, String::new())
|
),
|
||||||
}
|
Err(e) => (
|
||||||
}
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")),
|
[(CONTENT_TYPE, "plain/text")],
|
||||||
|
format!("{e}"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,37 +35,53 @@ pub struct AccountRequestCreate {
|
||||||
|
|
||||||
pub async fn account_create(
|
pub async fn account_create(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
uid: UserToken,
|
|
||||||
Json(account): Json<AccountRequestCreate>,
|
Json(account): Json<AccountRequestCreate>,
|
||||||
) -> (StatusCode, String) {
|
) -> impl IntoResponse {
|
||||||
match Account::new(db.as_ref(), uid.user_id, &account.name).await {
|
match Account::new(db.as_ref(), &account.name).await {
|
||||||
Ok(a) => (StatusCode::OK, serde_json::to_string(&a).unwrap()),
|
Ok(a) => (
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")),
|
StatusCode::OK,
|
||||||
|
[(CONTENT_TYPE, "application/json")],
|
||||||
|
serde_json::to_string(&a).unwrap(),
|
||||||
|
),
|
||||||
|
Err(e) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
format!("{e}"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn account_list(
|
pub async fn account_list(State(db): State<Arc<SqlitePool>>) -> impl IntoResponse {
|
||||||
State(db): State<Arc<SqlitePool>>,
|
match Account::list(db.as_ref()).await {
|
||||||
uid: UserToken,
|
Ok(a) => (
|
||||||
) -> (StatusCode, String) {
|
StatusCode::OK,
|
||||||
match Account::list(db.as_ref(), uid.user_id).await {
|
[(CONTENT_TYPE, "application/json")],
|
||||||
Ok(a) => (StatusCode::OK, serde_json::to_string(&a).unwrap()),
|
serde_json::to_string(&a).unwrap(),
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")),
|
),
|
||||||
|
Err(e) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
format!("{e}"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn recategorize(
|
pub async fn recategorize(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
uid: UserToken,
|
|
||||||
Path(account): Path<i32>,
|
Path(account): Path<i32>,
|
||||||
) -> (StatusCode, String) {
|
) -> impl IntoResponse {
|
||||||
let account = Account::get_by_id(db.as_ref(), account).await.unwrap();
|
let account = Account::get_by_id(db.as_ref(), account).await.unwrap();
|
||||||
if account.get_user() != uid.user_id {
|
|
||||||
return (StatusCode::UNAUTHORIZED, String::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
match account.recategorize_transactions(db.as_ref()).await {
|
match account.recategorize_transactions(db.as_ref()).await {
|
||||||
Ok(_) => (StatusCode::OK, String::new()),
|
Ok(_) => (
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e}")),
|
StatusCode::OK,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
String::new(),
|
||||||
|
),
|
||||||
|
Err(e) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
format!("{e}"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,10 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::{extract::State, Json};
|
use axum::{extract::State, response::IntoResponse, Json};
|
||||||
use hyper::StatusCode;
|
use hyper::{header::CONTENT_TYPE, StatusCode};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::users::UserToken;
|
|
||||||
use accounters::models::categories::Category;
|
use accounters::models::categories::Category;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
@ -16,18 +15,25 @@ pub struct CategoryCreateRequest {
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
uid: UserToken,
|
|
||||||
Json(new_category): Json<CategoryCreateRequest>,
|
Json(new_category): Json<CategoryCreateRequest>,
|
||||||
) -> (StatusCode, String) {
|
) -> impl IntoResponse {
|
||||||
match Category::new(db.as_ref(), &new_category.name, &new_category.description).await {
|
match Category::new(db.as_ref(), &new_category.name, &new_category.description).await {
|
||||||
Ok(_) => (StatusCode::OK, String::new()),
|
Ok(_) => (StatusCode::OK, String::new()),
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")),
|
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list(State(db): State<Arc<SqlitePool>>, uid: UserToken) -> (StatusCode, String) {
|
pub async fn list(State(db): State<Arc<SqlitePool>>) -> impl IntoResponse {
|
||||||
match Category::list(db.as_ref()).await {
|
match Category::list(db.as_ref()).await {
|
||||||
Ok(c) => (StatusCode::OK, serde_json::to_string(&c).unwrap()),
|
Ok(c) => (
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")),
|
StatusCode::OK,
|
||||||
|
[(CONTENT_TYPE, "application/json")],
|
||||||
|
serde_json::to_string(&c).unwrap(),
|
||||||
|
),
|
||||||
|
Err(e) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
format!("{e:?}"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,13 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::extract::{Json, State};
|
use axum::{
|
||||||
use hyper::StatusCode;
|
extract::{Json, State},
|
||||||
|
response::IntoResponse,
|
||||||
|
};
|
||||||
|
use hyper::{header::CONTENT_TYPE, StatusCode};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
use crate::users::UserToken;
|
|
||||||
use accounters::models::rules::Rule;
|
use accounters::models::rules::Rule;
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
@ -16,18 +18,33 @@ pub struct RuleCreateRequest {
|
||||||
|
|
||||||
pub async fn create(
|
pub async fn create(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
uid: UserToken,
|
|
||||||
Json(rule): Json<RuleCreateRequest>,
|
Json(rule): Json<RuleCreateRequest>,
|
||||||
) -> (StatusCode, String) {
|
) -> impl IntoResponse {
|
||||||
match Rule::new(db.as_ref(), uid.user_id, rule.regex, rule.category).await {
|
match Rule::new(db.as_ref(), rule.regex, rule.category).await {
|
||||||
Ok(r) => (StatusCode::OK, serde_json::to_string(&r).unwrap()),
|
Ok(r) => (
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")),
|
StatusCode::OK,
|
||||||
|
[(CONTENT_TYPE, "application/json")],
|
||||||
|
serde_json::to_string(&r).unwrap(),
|
||||||
|
),
|
||||||
|
Err(e) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
format!("{e:?}"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn list(State(db): State<Arc<SqlitePool>>, uid: UserToken) -> (StatusCode, String) {
|
pub async fn list(State(db): State<Arc<SqlitePool>>) -> impl IntoResponse {
|
||||||
match Rule::list_by_user(db.as_ref(), uid.user_id).await {
|
match Rule::list(db.as_ref()).await {
|
||||||
Ok(rule_list) => (StatusCode::OK, serde_json::to_string(&rule_list).unwrap()),
|
Ok(rule_list) => (
|
||||||
Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, format!("{e:?}")),
|
StatusCode::OK,
|
||||||
|
[(CONTENT_TYPE, "application/json")],
|
||||||
|
serde_json::to_string(&rule_list).unwrap(),
|
||||||
|
),
|
||||||
|
Err(e) => (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
[(CONTENT_TYPE, "text/plain")],
|
||||||
|
format!("{e:?}"),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ pub async fn list(
|
||||||
Path(account): Path<i32>,
|
Path(account): Path<i32>,
|
||||||
Query(pagination): Query<PaginationOptions>,
|
Query(pagination): Query<PaginationOptions>,
|
||||||
) -> (StatusCode, String) {
|
) -> (StatusCode, String) {
|
||||||
match Transaction::list(
|
match Transaction::list_by_account(
|
||||||
db.as_ref(),
|
db.as_ref(),
|
||||||
account,
|
account,
|
||||||
pagination.limit.unwrap_or(100),
|
pagination.limit.unwrap_or(100),
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ use serde::Serialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use crate::users::UserToken;
|
|
||||||
use accounters::models::{account::Account, categories::Category, transaction::Transaction};
|
use accounters::models::{account::Account, categories::Category, transaction::Transaction};
|
||||||
|
|
||||||
pub mod account;
|
pub mod account;
|
||||||
|
|
@ -23,7 +22,7 @@ struct AccountRender {
|
||||||
|
|
||||||
impl AccountRender {
|
impl AccountRender {
|
||||||
async fn from_account(pool: &SqlitePool, acc: Account) -> Self {
|
async fn from_account(pool: &SqlitePool, acc: Account) -> Self {
|
||||||
let last_acc = Transaction::list(pool, acc.get_id(), 1, 0, false)
|
let last_acc = Transaction::list_by_account(pool, acc.get_id(), 1, 0, false)
|
||||||
.await
|
.await
|
||||||
.map_or(0.0, |x| {
|
.map_or(0.0, |x| {
|
||||||
x.get(0)
|
x.get(0)
|
||||||
|
|
@ -54,11 +53,10 @@ fn hm_sort(hm: HashMap<i32, i64>, collapse: usize) -> Vec<(i32, i64)> {
|
||||||
pub async fn index(
|
pub async fn index(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
State(tmpls): State<Arc<Tera>>,
|
State(tmpls): State<Arc<Tera>>,
|
||||||
uid: UserToken,
|
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
|
|
||||||
let accounts = Account::list(db.as_ref(), uid.user_id).await.unwrap();
|
let accounts = Account::list(db.as_ref()).await.unwrap();
|
||||||
let mut acc_render = Vec::new();
|
let mut acc_render = Vec::new();
|
||||||
|
|
||||||
for acc in accounts.into_iter() {
|
for acc in accounts.into_iter() {
|
||||||
|
|
@ -69,7 +67,7 @@ pub async fn index(
|
||||||
|
|
||||||
let last_month = Transaction::list_by_date(
|
let last_month = Transaction::list_by_date(
|
||||||
db.as_ref(),
|
db.as_ref(),
|
||||||
uid.user_id,
|
None,
|
||||||
Some(Utc::now() - chrono::Duration::days(30)),
|
Some(Utc::now() - chrono::Duration::days(30)),
|
||||||
Some(Utc::now()),
|
Some(Utc::now()),
|
||||||
None,
|
None,
|
||||||
|
|
@ -119,9 +117,7 @@ pub async fn index(
|
||||||
|
|
||||||
ctx.insert("colors", &colors);
|
ctx.insert("colors", &colors);
|
||||||
|
|
||||||
let transactions = Transaction::list_by_user(db.as_ref(), uid.user_id, 10, 0, false)
|
let transactions = Transaction::list(db.as_ref(), 10, 0, false).await.unwrap();
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
ctx.insert("transactions", &transactions);
|
ctx.insert("transactions", &transactions);
|
||||||
|
|
||||||
match tmpls.render("index.html", &ctx) {
|
match tmpls.render("index.html", &ctx) {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ use serde::Deserialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use crate::users::UserToken;
|
|
||||||
use accounters::models::{account::Account, categories::Category, transaction::Transaction};
|
use accounters::models::{account::Account, categories::Category, transaction::Transaction};
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
|
|
@ -31,7 +30,6 @@ fn parse_date(s: &str) -> Option<DateTime<Utc>> {
|
||||||
pub async fn show(
|
pub async fn show(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
State(tmpls): State<Arc<Tera>>,
|
State(tmpls): State<Arc<Tera>>,
|
||||||
uid: UserToken,
|
|
||||||
Path(account_id): Path<i32>,
|
Path(account_id): Path<i32>,
|
||||||
Query(AccountViewParams { from, to }): Query<AccountViewParams>,
|
Query(AccountViewParams { from, to }): Query<AccountViewParams>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
@ -48,14 +46,6 @@ pub async fn show(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if account.get_user() != uid.user_id {
|
|
||||||
return (
|
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
[(CONTENT_TYPE, "text/plain")],
|
|
||||||
String::from("You cannot access this resource"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let from = from
|
let from = from
|
||||||
.and_then(|x| parse_date(&x))
|
.and_then(|x| parse_date(&x))
|
||||||
.unwrap_or(Utc::now().duration_trunc(Duration::days(1)).unwrap() - Duration::days(30));
|
.unwrap_or(Utc::now().duration_trunc(Duration::days(1)).unwrap() - Duration::days(30));
|
||||||
|
|
@ -80,7 +70,8 @@ pub async fn show(
|
||||||
.collect();
|
.collect();
|
||||||
ctx.insert("categories", &categories);
|
ctx.insert("categories", &categories);
|
||||||
|
|
||||||
let txs = match Transaction::list(db.as_ref(), account.get_id(), 10, 0, false).await {
|
let txs = match Transaction::list_by_account(db.as_ref(), account.get_id(), 10, 0, false).await
|
||||||
|
{
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -109,7 +100,6 @@ pub struct AccountTxListParams {
|
||||||
pub async fn list_transactions(
|
pub async fn list_transactions(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
State(tmpls): State<Arc<Tera>>,
|
State(tmpls): State<Arc<Tera>>,
|
||||||
uid: UserToken,
|
|
||||||
Path(account_id): Path<i32>,
|
Path(account_id): Path<i32>,
|
||||||
Query(AccountTxListParams { entries, page }): Query<AccountTxListParams>,
|
Query(AccountTxListParams { entries, page }): Query<AccountTxListParams>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
@ -126,14 +116,6 @@ pub async fn list_transactions(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if account.get_user() != uid.user_id {
|
|
||||||
return (
|
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
[(CONTENT_TYPE, "text/plain")],
|
|
||||||
String::from("You cannot access this resource"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let categories: HashMap<i32, String> = Category::list(db.as_ref())
|
let categories: HashMap<i32, String> = Category::list(db.as_ref())
|
||||||
.await
|
.await
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
|
@ -145,7 +127,7 @@ pub async fn list_transactions(
|
||||||
let n_entries = entries.unwrap_or(10).max(10);
|
let n_entries = entries.unwrap_or(10).max(10);
|
||||||
let page = page.unwrap_or(0).max(0);
|
let page = page.unwrap_or(0).max(0);
|
||||||
|
|
||||||
let txs = match Transaction::list(
|
let txs = match Transaction::list_by_account(
|
||||||
db.as_ref(),
|
db.as_ref(),
|
||||||
account.get_id(),
|
account.get_id(),
|
||||||
n_entries,
|
n_entries,
|
||||||
|
|
@ -181,7 +163,6 @@ pub async fn list_transactions(
|
||||||
pub async fn add_transactions_view(
|
pub async fn add_transactions_view(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
State(tmpls): State<Arc<Tera>>,
|
State(tmpls): State<Arc<Tera>>,
|
||||||
uid: UserToken,
|
|
||||||
Path(account_id): Path<i32>,
|
Path(account_id): Path<i32>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let mut ctxt = Context::new();
|
let mut ctxt = Context::new();
|
||||||
|
|
@ -198,14 +179,6 @@ pub async fn add_transactions_view(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if account.get_user() != uid.user_id {
|
|
||||||
return (
|
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
[(CONTENT_TYPE, "text/plain")],
|
|
||||||
String::from("You cannot access this resource"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ctxt.insert("account", &account);
|
ctxt.insert("account", &account);
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -224,11 +197,9 @@ pub struct CreateTransactionRequest {
|
||||||
|
|
||||||
pub async fn add_transactions_action(
|
pub async fn add_transactions_action(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
uid: UserToken,
|
|
||||||
Path(account_id): Path<i32>,
|
Path(account_id): Path<i32>,
|
||||||
Json(body): Json<Vec<CreateTransactionRequest>>,
|
Json(body): Json<Vec<CreateTransactionRequest>>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
// TODO missing user id check
|
|
||||||
for tx in body.iter() {
|
for tx in body.iter() {
|
||||||
if let Err(e) = Transaction::new(
|
if let Err(e) = Transaction::new(
|
||||||
db.as_ref(),
|
db.as_ref(),
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,11 @@ use serde::Deserialize;
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tera::{Context, Tera};
|
use tera::{Context, Tera};
|
||||||
|
|
||||||
use crate::users::UserToken;
|
|
||||||
|
|
||||||
pub async fn view_classifiers(
|
pub async fn view_classifiers(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
State(tmpls): State<Arc<Tera>>,
|
State(tmpls): State<Arc<Tera>>,
|
||||||
uid: UserToken,
|
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let rules = match Rule::list_by_user(db.as_ref(), uid.user_id).await {
|
let rules = match Rule::list(db.as_ref()).await {
|
||||||
Ok(r) => r,
|
Ok(r) => r,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
return (
|
return (
|
||||||
|
|
@ -57,7 +54,6 @@ pub async fn view_classifiers(
|
||||||
pub async fn rules_new_view(
|
pub async fn rules_new_view(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
State(tmpls): State<Arc<Tera>>,
|
State(tmpls): State<Arc<Tera>>,
|
||||||
uid: UserToken,
|
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let categories = Category::list(db.as_ref()).await.unwrap();
|
let categories = Category::list(db.as_ref()).await.unwrap();
|
||||||
let mut ctx = Context::new();
|
let mut ctx = Context::new();
|
||||||
|
|
@ -78,10 +74,9 @@ pub struct NewRuleParams {
|
||||||
|
|
||||||
pub async fn rules_new_action(
|
pub async fn rules_new_action(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
uid: UserToken,
|
|
||||||
Form(params): Form<NewRuleParams>,
|
Form(params): Form<NewRuleParams>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match Rule::new(db.as_ref(), uid.user_id, params.regex, params.category).await {
|
match Rule::new(db.as_ref(), params.regex, params.category).await {
|
||||||
Ok(_) => (
|
Ok(_) => (
|
||||||
StatusCode::MOVED_PERMANENTLY,
|
StatusCode::MOVED_PERMANENTLY,
|
||||||
[(LOCATION, "/classifiers")],
|
[(LOCATION, "/classifiers")],
|
||||||
|
|
@ -95,7 +90,7 @@ pub async fn rules_new_action(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn category_new_view(State(tmpl): State<Arc<Tera>>, uid: UserToken) -> impl IntoResponse {
|
pub async fn category_new_view(State(tmpl): State<Arc<Tera>>) -> impl IntoResponse {
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[(CONTENT_TYPE, "text/html;charset=utf-8")],
|
[(CONTENT_TYPE, "text/html;charset=utf-8")],
|
||||||
|
|
@ -111,7 +106,6 @@ pub struct CategoryNewRuleParams {
|
||||||
|
|
||||||
pub async fn category_new_action(
|
pub async fn category_new_action(
|
||||||
State(db): State<Arc<SqlitePool>>,
|
State(db): State<Arc<SqlitePool>>,
|
||||||
uid: UserToken,
|
|
||||||
Form(params): Form<CategoryNewRuleParams>,
|
Form(params): Form<CategoryNewRuleParams>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
match Category::new(db.as_ref(), ¶ms.name, ¶ms.description).await {
|
match Category::new(db.as_ref(), ¶ms.name, ¶ms.description).await {
|
||||||
|
|
|
||||||
|
|
@ -12,12 +12,9 @@ use serde::{Deserialize, Deserializer};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tera::Tera;
|
use tera::Tera;
|
||||||
|
|
||||||
use crate::users::UserToken;
|
|
||||||
|
|
||||||
pub async fn view(
|
pub async fn view(
|
||||||
db: State<Arc<SqlitePool>>,
|
db: State<Arc<SqlitePool>>,
|
||||||
tmpl: State<Arc<Tera>>,
|
tmpl: State<Arc<Tera>>,
|
||||||
user: UserToken,
|
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
let tx = Transaction::get_by_id(db.as_ref(), id).await.unwrap();
|
let tx = Transaction::get_by_id(db.as_ref(), id).await.unwrap();
|
||||||
|
|
@ -58,8 +55,6 @@ pub struct TxUpdateRequest {
|
||||||
|
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
db: State<Arc<SqlitePool>>,
|
db: State<Arc<SqlitePool>>,
|
||||||
tmpl: State<Arc<Tera>>,
|
|
||||||
user: UserToken,
|
|
||||||
Path(id): Path<i32>,
|
Path(id): Path<i32>,
|
||||||
Form(req): Form<TxUpdateRequest>,
|
Form(req): Form<TxUpdateRequest>,
|
||||||
) -> impl IntoResponse {
|
) -> impl IntoResponse {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
use std::net::{AddrParseError, SocketAddr};
|
use std::net::{AddrParseError, SocketAddr};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use axum::headers::ContentType;
|
|
||||||
use hyper::{header, StatusCode};
|
use hyper::{header, StatusCode};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
use accounters::models::users::User;
|
|
||||||
use axum::{
|
|
||||||
async_trait,
|
|
||||||
extract::{FromRef, FromRequestParts},
|
|
||||||
headers::authorization::{Basic, Bearer},
|
|
||||||
headers::Authorization,
|
|
||||||
http::request::Parts,
|
|
||||||
response::{IntoResponse, Redirect},
|
|
||||||
RequestPartsExt, TypedHeader,
|
|
||||||
};
|
|
||||||
use hyper::StatusCode;
|
|
||||||
|
|
||||||
use crate::server::AppState;
|
|
||||||
|
|
||||||
pub struct AuthRedirect;
|
|
||||||
|
|
||||||
impl IntoResponse for AuthRedirect {
|
|
||||||
fn into_response(self) -> axum::response::Response {
|
|
||||||
Redirect::temporary("/login").into_response()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct UserToken {
|
|
||||||
pub user_id: i32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
impl<S> FromRequestParts<S> for UserToken
|
|
||||||
where
|
|
||||||
AppState: FromRef<S>,
|
|
||||||
S: Send + Sync,
|
|
||||||
{
|
|
||||||
type Rejection = (StatusCode, [(&'static str, &'static str); 1]);
|
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
|
|
||||||
match parts.extract::<TypedHeader<Authorization<Bearer>>>().await {
|
|
||||||
Ok(auth) => Ok(UserToken {
|
|
||||||
user_id: auth.0 .0.token().parse().unwrap(),
|
|
||||||
}),
|
|
||||||
Err(_) => match parts.extract::<TypedHeader<Authorization<Basic>>>().await {
|
|
||||||
Ok(auth) => {
|
|
||||||
let state = AppState::from_ref(state);
|
|
||||||
let user = User::get_user(state.db.as_ref(), auth.username())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
if user.check_pass(auth.password()) {
|
|
||||||
Ok(UserToken {
|
|
||||||
user_id: user.get_id(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
Err((StatusCode::UNAUTHORIZED, [("", "")]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => Err((
|
|
||||||
StatusCode::UNAUTHORIZED,
|
|
||||||
[("WWW-Authenticate", "Basic realm=\"Access\"")],
|
|
||||||
)),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue