f494fddce894613f2191b1af8092f2864f868575
commit f494fddce894613f2191b1af8092f2864f868575
Author: Simon Watson <spesk@pm.me>
Date: Wed Dec 7 21:52:31 2022 -0500

Added downloads, integrating with fin-api

diff --git a/fin-lisp.lisp b/fin-lisp.lisp
index 3431b26..317ed83 100644
--- a/fin-lisp.lisp
+++ b/fin-lisp.lisp
@@ -3,10 +3,12 @@
(ql:quickload "cl-ppcre")
(ql:quickload "unix-opts")
(ql:quickload "ironclad")
+(ql:quickload "dexador")

;;; Features
;;; - Import records from old .txt format
;;; - Removed as I'll never use it again
+;;; - Added in again to retest some things
;;; - Interactive prompt to manage expenses
;;; - Generic expense handling
;;; TODO
@@ -24,6 +26,9 @@
;;; where the key is the month stamp, eg 20210701
;;; and the value is the monthly expenses hash
(defvar *records* (make-hash-table :test 'equalp))
+(defvar *api-config-path* "./auth.json")
+(defvar *api-url* NIL)
+(defvar *api-key* NIL)

(defun file-test (filename)
(if (probe-file filename) filename (print "Couldn't find filename")))
@@ -73,6 +78,9 @@
;;; See: https://www.cliki.net/Ironclad

;;; Return cipher when provided key
+;;; Currently, this is 'insecure' as we are using a string
+;;; coerced into a byte array as the key, aka a non-random secret.
+;;; Should use twofish
(defun get-cipher (key)
(ironclad:make-cipher
:blowfish
@@ -101,6 +109,34 @@
;;; End Encryption Stuff ;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;

+;;; Can only be called from the REPL,
+;;; used for importing according to the old schema
+(defun import-records (filename)
+ (let ((old-file-lines
+ (with-open-file (stream filename)
+ (loop for line = (read-line stream nil)
+ while line
+ collect line)))
+ (mre (ppcre:create-scanner "^(.*)[0-9]{4}$"))
+ (ere (ppcre:create-scanner "^([A-Z].*)\ -\ \\\$([0-9]{1,4}) - PAID"))
+ (cur-mon)
+ (cur-exp))
+ (loop for line in old-file-lines
+ do (progn
+ (if (ppcre:scan mre line) (setf cur-mon line))
+ (if (ppcre:scan ere line)
+ (progn
+ (setf cur-exp (ppcre:register-groups-bind (first second) (ere line) :sharedp t (list first second)))
+ (print cur-exp)
+ (if (gethash cur-mon *records*)
+ (let ((innerhash (gethash cur-mon *records*)))
+ (setf (gethash (first cur-exp) innerhash) (second cur-exp))))
+ (if (not (gethash cur-mon *records*))
+ (progn
+ (add-month cur-mon)
+ (let ((innerhash (gethash cur-mon *records*)))
+ (setf (gethash (first cur-exp) innerhash) (second cur-exp)))))))))))
+
(defun reset-records ()
(setf *records* (make-hash-table :test 'equal)))

@@ -145,6 +181,7 @@
(let ((record-key-list (loop for key being the hash-keys of *records* collect key)))
(dolist (month-key record-key-list) (dump-month month-key))))

+;;; Serialization and communicating with the web API
(defun serialize-records (key filename)
(with-open-file (stream filename
:direction :output
@@ -157,6 +194,21 @@
(defun deserialize-records (key filename)
(setf *records* (yason:parse (decrypt-records key filename))))

+(defun parse-api-config (path)
+ (let ((api-config-hash (yason:parse (uiop:read-file-string path)))
+ (ret-tuple '()))
+ (push (gethash "token" api-config-hash) ret-tuple)
+ (push (gethash "url" api-config-hash) ret-tuple)
+ ret-tuple))
+
+(defun download-records ()
+ (let* ((api-config (parse-api-config *api-config-path*))
+ (dl-records (yason:parse (dex:get (concatenate 'string (first api-config) "download")
+ :headers (list (cons "X-Token" (second api-config)))))))
+ dl-records))
+
+(defun upload-records (records-file))
+
(defmacro generic-handler (form error-string)
`(handler-case ,form
(error (e)
@@ -215,7 +267,6 @@
(when-option (matches :help)
(display-help))
(when-option (matches :print-month)
-
(when-option (matches :interactive-mode)
(progn
(interactive-mode)