Просмотр исходного кода

Users can now create transactions.
Bug fix: login was not retrieving and adding accounts to user.

Lee Morgan 11 месяцев назад
Родитель
Сommit
5e05144654

+ 4 - 1
src/controllers/user.rs

@@ -39,11 +39,14 @@ pub async fn login_route(
     payload: web::Json<LoginInput>
 ) -> Result<HttpResponse, AppError> {
     let user_collection = db.collection::<User>("users");
+    let account_collection = db.collection::<Account>("accounts");
     let email = payload.email.to_lowercase();
     let user = User::find_by_email(&user_collection, &email).await?;
     validate_password(&user, &payload.password_hash, &payload.password_salt)?;
     let cookie = create_user_cookie(Some(user.id.to_string()));
-    Ok(HttpResponse::Ok().cookie(cookie).json(user.response(None)))
+    let accounts = Account::find_by_user(&account_collection, user.id).await?;
+    let response_accounts = accounts.into_iter().map(Account::response).collect();
+    Ok(HttpResponse::Ok().cookie(cookie).json(user.response(Some(response_accounts))))
 }
 
 #[get("/api/user/logout")]

+ 10 - 0
src/views/js/Format.js

@@ -10,4 +10,14 @@ export default class Format{
 
         return `${year}-${month}-${day}`;
     }
+
+    static dollarsToCents(num){
+        if(typeof num === "string") num = Number(num);
+
+        return Math.round(num * 100);
+    }
+
+    static centsToDollars(num){
+        return num / 100;
+    }
 }

+ 31 - 23
src/views/js/data/Account.js

@@ -19,8 +19,12 @@ export default class Account{
         this._populated = false;
     }
 
+    get id(){
+        return this._id;
+    }
+
     get balance(){
-        return this.toDollars(this._balance);
+        return Format.centsToDollars(this._balance);
     }
 
     incomeTotal(){
@@ -28,7 +32,7 @@ export default class Account{
         for(let i = 0; i < this._income.length; i++){
             income += this._income[i].amount;
         }
-        return this.toDollars(income);
+        return Format.centsToDollars(income);
     }
 
     billsTotal(){
@@ -36,13 +40,13 @@ export default class Account{
         for(let i = 0; i < this._bills.length; i++){
             bills += this._bills[i].amount;
         }
-        return this.toDollars(bills);
+        return Format.centsToDollars(bills);
     }
 
     static async create(name, balance){
         const data = {
             name: name,
-            balance: Account.toCents(balance),
+            balance: Format.dollarsToCents(balance),
             income: [],
             bills: [],
             allowances: []
@@ -67,13 +71,13 @@ export default class Account{
     }
 
     async addIncome(name, amount){
-        this._income.push(Income.create(name, this.toCents(amount)));
+        this._income.push(Income.create(name, Format.dollarsToCents(amount)));
 
         await this.save();
     }
 
     async addBill(name, amount){
-        this._bills.push(Bill.create(name, this.toCents(amount)));
+        this._bills.push(Bill.create(name, Format.dollarsToCents(amount)));
 
         await this.save();
     }
@@ -82,7 +86,7 @@ export default class Account{
         if(isPercent){
             amount = Number(amount);
         }else{
-            amount = this.toCents(amount);
+            amount = Format.dollarsToCents(amount);
         }
 
         this._allowances.push(Allowance.create(name, amount, isPercent));
@@ -90,6 +94,26 @@ export default class Account{
         await this.save();
     }
 
+    addTransaction(transaction){
+        this._transactions.push(transaction);
+        this.sortTransactions();
+    }
+
+    removeTransaction(transaction){
+        if(transaction instanceof Transaction){
+            for(let i = 0; i < this._transactions.length; i++){
+                if(this._transactions[i] === transaction){
+                    this._transactions.splice(i, 1);
+                    break;
+                }
+            }
+        }
+    }
+
+    sortTransactions(){
+        this._transactions.sort((a, b)=>{a > b ? 1 : -1});
+    }
+
     listIncome(){
         const income = [];
         for(let i = 0; i < this._income.length; i++){
@@ -123,22 +147,6 @@ export default class Account{
         return allowances;
     }
 
-    toCents(num){
-        if(typeof num === "string") num = Number(num);
-
-        return Math.round(num * 100);
-    }
-
-    static toCents(num){
-        if(typeof num === "string") num = Number(num);
-
-        return Math.round(num * 100);
-    }
-
-    toDollars(num){
-        return num / 100;
-    }
-
     async populateTransactions(){
         if(this._populated) return;
 

+ 80 - 9
src/views/js/data/Transaction.js

@@ -1,15 +1,41 @@
+import Format from "../Format.js";
+
 export default class Transaction{
-    constructor(id, account, date, amount, tags, location, note, iv, type, typeId){
+    constructor(parent, id, iv, date, data,){
+        this._parent = parent;
         this._id = id;
-        this._account = account;
-        this._date = date;
-        this._type = type;
-        this._typeId = typeId;
-        this._amount = amount;
-        this._tags = tags;
-        this._location = location;
-        this._note = note;
         this._iv = iv;
+        this._date = date;
+        this._category = data.category;
+        this._categoryId = data.categoryId;
+        this._amount = data.amount;
+        this._tags = data.tags;
+        this._location = data.location;
+        this._note = data.note;
+    }
+
+    static create(account, date, amount, tags, location, note, category){j
+        let categoryId = null;
+        if(category !== "discretionary"){
+            let categorySplit = category.split("-");
+            category = categorySplit[0];
+            categoryId = categorySplit[1];
+        }
+
+        return new Transaction(
+            account,
+            null,
+            encryptionHandler.generateIv(),
+            Format.transactionDate(date),
+            {
+                category: category,
+                categoryId: categoryId,
+                amount: Format.dollarsToCents(amount),
+                tags: tags.split(","),
+                location: location,
+                note: note
+            }
+        );
     }
 
     static decrypt(transaction){
@@ -28,4 +54,49 @@ export default class Transaction{
             data.typeId
         );
     }
+
+    serialize(){
+        return {
+            category: this._category,
+            categoryId: this._categoryId,
+            amount: this._amount,
+            tags: this._tags,
+            location: this._location,
+            note: this._note
+        };
+    }
+
+    async save(isNew = false){
+        const data = await encryptionHandler.encrypt(this.serialize(), this._iv);
+
+        let method, body;
+        if(isNew){
+            method = "POST";
+            body = {
+                account: this._parent.id,
+                date: this._date,
+                data: data,
+                iv: this._iv
+            };
+        }
+
+        fetch("/api/transactions", {
+            method: method,
+            headers: {"Content-Type": "application/json"},
+            body: body
+        })
+            .then(r=>r.json())
+            .then((response)=>{
+                if(response.error){
+                    new Notifier("error", response.error.message);
+                    this._parent.removeTransaction(this);
+                }else{
+                    this._id = response.id;
+                }
+            })
+            .catch((err)=>{
+                new Notifier("error", "Something went wrong, try refreshing the page");
+                this._parent.removeTransaction(this);
+            });
+   }
 }

+ 0 - 1
src/views/js/index.js

@@ -62,6 +62,5 @@ fetch("/api/user", {
         currentPage = new Home();
     })
     .catch((err)=>{
-        console.log(err);
         currentPage = new Login();
     });

+ 21 - 8
src/views/js/pages/CreateTransaction.js

@@ -1,5 +1,6 @@
 import Page from "./Page.js";
 import Elem from "../Elem.js";
+import Transaction from "../data/Transaction.js";
 
 export default class CreateTransaction extends Page{
     constructor(){
@@ -12,10 +13,22 @@ export default class CreateTransaction extends Page{
         );
     }
 
-    submit(event){
+    async submit(event){
         event.preventDefault();
-        console.log("submitting");
-    }
+
+        const transaction = Transaction.create(
+            user.account,
+            document.querySelector(".date").valueAsDate,
+            document.querySelector(".amount").value,
+            document.querySelector(".tags").value,
+            document.querySelector(".location").value,
+            document.querySelector(".note").value,
+            document.querySelector(".category").value
+        );
+        await transaction.save();
+        user.account.addTransaction(transaction);
+        changePage("home");
+   }
 
     render(income, bills, allowances){
         let select, incomeOpt, billsOpt, allowancesOpt;
@@ -37,7 +50,7 @@ export default class CreateTransaction extends Page{
                 )
             )
             .append(new Elem("select")
-                .addClass("type")
+                .addClass("category")
                 .toVar((a)=>{select = a})
                 .append(new Elem("option")
                     .value("discretionary")
@@ -84,7 +97,7 @@ export default class CreateTransaction extends Page{
             .append(new Elem("label")
                 .text("Notes")
                 .append(new Elem("textarea")
-                    .addClass("notes")
+                    .addClass("note")
                     .rows(3)
                 )
             )
@@ -100,15 +113,15 @@ export default class CreateTransaction extends Page{
             .appendTo(this.container);
 
         for(let i = 0; i < allowances.length; i++){
-            allowancesOpt.append(new Elem("option").text(allowances[i].name).value(allowances[i].id));
+            allowancesOpt.append(new Elem("option").text(allowances[i].name).value(`allowance-${allowances[i].id}`));
         }
 
         for(let i = 0; i < bills.length; i++){
-            billsOpt.append(new Elem("option").text(bills[i].name).value(bills[i].id));
+            billsOpt.append(new Elem("option").text(bills[i].name).value(`bill-${bills[i].id}`));
         }
 
         for(let i = 0; i < income.length; i++){
-            incomeOpt.append(new Elem("option").text(income[i].name).value(income[i].id));
+            incomeOpt.append(new Elem("option").text(income[i].name).value(`income-${income[i].id}`));
         }
     }
 }

+ 17 - 5
src/views/js/pages/Login.js

@@ -11,7 +11,7 @@ export default class Login extends Page{
         this.render();
     }
 
-    async submit(){
+    submit(){
         event.preventDefault();
 
         const email = this.container.querySelector(".email").value;
@@ -49,13 +49,25 @@ export default class Login extends Page{
                 window.user = new User(
                     response.id,
                     response.name,
-                    response.email,
-                    response.accounts
+                    response.email
                 );
-                return EncryptionHandler.create(password, response.encryption_salt);
+
+                return Promise.all([
+                    EncryptionHandler.create(password, response.encryption_salt),
+                    response.accounts
+                ]);
             })
-            .then((encryptionHandler)=>{
+            .then(([encryptionHandler, accounts])=>{
                 window.encryptionHandler = encryptionHandler;
+
+                let promises = [];
+                for(let i = 0; i < accounts.length; i++){
+                    promises.push(user.decryptAndAddAccount(accounts[i]));
+                }
+
+                return Promise.all(promises);
+            })
+            .then((response)=>{
                 changePage("home");
             })
             .catch((err)=>{