From ac5a9ec31add0e72534808e41a220b367e556ae6 Mon Sep 17 00:00:00 2001
From: Immanuel Onyeka <immanuel@onyeka.ca>
Date: Fri, 18 Nov 2022 15:35:10 -0500
Subject: [PATCH] Use zero vector for missing values instead of null

Null makes missing values harder to convert to json using the Scan
operation. 0 and '' will be used instead to be type consistent.
---
 migrations/0_29092022_create_main_tables.sql | 29 +++---
 migrations/seed.sql                          | 36 +++----
 skouter.go                                   | 98 +++++++++++++-------
 3 files changed, 97 insertions(+), 66 deletions(-)

diff --git a/migrations/0_29092022_create_main_tables.sql b/migrations/0_29092022_create_main_tables.sql
index 99c800f..08d383d 100644
--- a/migrations/0_29092022_create_main_tables.sql
+++ b/migrations/0_29092022_create_main_tables.sql
@@ -7,7 +7,7 @@ CREATE TABLE comparison (
 
 CREATE TABLE branch (
 	id 				INT AUTO_INCREMENT,
-	type 			ENUM('NMLS', 'FSRA'),
+	type 			ENUM('NMLS', 'FSRA') NOT NULL,
 	num 			VARCHAR(40) NOT NULL,
 	PRIMARY KEY (id)
 );
@@ -50,8 +50,8 @@ CREATE TABLE license (
  * Types with branch_id and user_id values of null will be general cases. */
 CREATE TABLE loan_type (
 	id 				INT AUTO_INCREMENT,
-	branch_id 		INT,
-	user_id 		INT,
+	branch_id 		INT NOT NULL,
+	user_id 		INT NOT NULL,
 	name 			VARCHAR(30) NOT NULL,
 	FOREIGN KEY (branch_id) REFERENCES branch(id),
 	FOREIGN KEY (user_id) REFERENCES user(id),
@@ -60,9 +60,9 @@ CREATE TABLE loan_type (
 
 CREATE TABLE borrower (
 	id 				INT AUTO_INCREMENT,
-	credit_score 	SMALLINT,
-	monthly_income 	INT,
-	num 			TINYINT, /* Number of people borrowing. */
+	credit_score 	SMALLINT NOT NULL,
+	monthly_income 	INT NOT NULL,
+	num 			TINYINT NOT NULL, /* Number of people borrowing. */
 	PRIMARY KEY (`id`)
 );
 
@@ -113,18 +113,19 @@ CREATE TABLE fee (
 );
 
 /* Templates to be reused by users or branches. Either user_id or branch_id must
- * be non-null. */
+ * be non-null.*/
 CREATE TABLE fee_template (
 	id 				INT AUTO_INCREMENT,
-	user_id 		INT,
-	branch_id 		INT,
-	amount 			INT,
-	perc 			SMALLINT, /* Percentage of sale price instead of amount */
+	user_id 		INT NOT NULL,
+	branch_id 		INT NOT NULL,
+	amount 			INT NOT NULL,
+	perc 			SMALLINT NOT NULL,
+	/* Percentage of sale price instead of amount */
 	type 			ENUM('Goverment', 'Title', 'Required', 'Lender', 'Other'),
-	notes 			VARCHAR(255),
-	name 			VARCHAR(30),
+	notes 			VARCHAR(255) NOT NULL,
+	name 			VARCHAR(30) NOT NULL,
 	/* Group heading shown in report */
-	category 		VARCHAR(60),
+	category 		VARCHAR(60) NOT NULL,
 	auto 			BOOLEAN NOT NULL,
 	/* If fee should be automatically applied */
 	PRIMARY KEY (`id`),
diff --git a/migrations/seed.sql b/migrations/seed.sql
index 5394a86..a01b6a5 100644
--- a/migrations/seed.sql
+++ b/migrations/seed.sql
@@ -32,7 +32,7 @@ INSERT IGNORE INTO user (
 	'Giant',
 	'Coltrane',
 	sha2('test123', 256),
-	null,
+	0,
 	'USA',
 	'Mortgage Broker',
 	'unverified@example.com',
@@ -75,11 +75,11 @@ INSERT IGNORE INTO loan_type (
 ) VALUES
 (
 	(SELECT id FROM branch LIMIT 1),
-	NULL,
+	0,
 	'testType'
 ),
 (
-	NULL,
+	0,
 	(SELECT id FROM user WHERE email="manager@example.com" LIMIT 1),
 	'manager idea'
 );
@@ -94,7 +94,7 @@ INSERT IGNORE INTO fee_template (
 	name
 ) VALUES
 (
-	NULL,
+	0,
 	(SELECT id FROM branch LIMIT 1),
 	0,
 	200,
@@ -113,7 +113,7 @@ INSERT IGNORE INTO fee_template (
 ),
 (
 	(SELECT id FROM user WHERE email="test@example.com" LIMIT 1),
-	NULL,
+	0,
 	9900,
 	0,
 	"Government",
@@ -122,7 +122,7 @@ INSERT IGNORE INTO fee_template (
 ),
 (
 	(SELECT id FROM user WHERE email="test@example.com" LIMIT 1),
-	NULL,
+	0,
 	0,
 	400,
 	'Lender',
@@ -136,27 +136,27 @@ INSERT IGNORE INTO loan_type (
 	name
 ) VALUES
 (
-	NULL,
-	NULL,
+	0,
+	0,
 	"Conventional"
 ),
 (
-	NULL,
-	NULL,
+	0,
+	0,
 	"FHA"
 ),
 (
-	NULL,
-	NULL,
+	0,
+	0,
 	"VA"
 ),
 (
-	NULL,
-	NULL,
+	0,
+	0,
 	"USDA"
 ),
 (
-	NULL,
+	0,
 	(SELECT id FROM branch LIMIT 1),
 	"Test"
 );
@@ -192,7 +192,7 @@ INSERT IGNORE INTO estimate (
 (
 	(SELECT id FROM user WHERE email="test@example.com" LIMIT 1),
 	(SELECT id FROM borrower ORDER BY id DESC LIMIT 1),
-	NULL,
+	0,
 	'Purchase',
 	(SELECT id FROM loan_type WHERE name="Conventional"),
 	3300000,
@@ -209,7 +209,7 @@ INSERT IGNORE INTO estimate (
 (
 	(SELECT id FROM user WHERE email="manager@example.com" LIMIT 1),
 	(SELECT id FROM borrower ORDER BY id DESC LIMIT 1),
-	NULL,
+	0,
 	'Purchase',
 	(SELECT id FROM loan_type WHERE name="FHA"),
 	2510000,
@@ -226,7 +226,7 @@ INSERT IGNORE INTO estimate (
 (
 	(SELECT id FROM user WHERE email="test@example.com" LIMIT 1),
 	(SELECT id FROM borrower ORDER BY id DESC LIMIT 1),
-	NULL,
+	0,
 	'Refinance',
 	(SELECT id FROM loan_type WHERE name="USDA"),
 	8000000,
diff --git a/skouter.go b/skouter.go
index 07ce163..fdaf59b 100644
--- a/skouter.go
+++ b/skouter.go
@@ -9,6 +9,7 @@ import (
 		"database/sql"
 		_ "github.com/go-sql-driver/mysql"
 		"fmt"
+		"encoding/json"
 )
 
 type Page struct {
@@ -18,23 +19,23 @@ type Page struct {
 }
 
 type LoanType struct {
-	id int
-	user sql.NullInt64
-	branch sql.NullInt64
-	name string
+	Id 		int 	`json:"id"`
+	User 	int 	`json:"user"`
+	Branch 	int 	`json:"branch"`
+	Name 	string 	`json:"name"`
 }
 
 type FeeTemplate struct {
-	id sql.NullInt64
-	user sql.NullInt64
-	branch sql.NullInt64
-	amount sql.NullInt64
-	perc sql.NullInt64
-	ftype sql.NullString
-	notes sql.NullString
-	name sql.NullString
-	category sql.NullString
-	auto bool
+	Id int
+	User int
+	Branch int
+	Amount int
+	Perc int
+	Ftype string
+	Notes string
+	Name string
+	Category string
+	Auto bool
 }
 
 type Estimate struct {
@@ -113,7 +114,7 @@ func getLoanType(db *sql.DB, id int, isUser bool) ([]LoanType, error) {
 	// Should be changed to specify user
 	rows, err :=
 	db.Query("SELECT * FROM loan_type WHERE user_id = ? " +
-	"OR (user_id = NULL AND branch_id = NULL)", id)
+	"OR (user_id = 0 AND branch_id = 0)", id)
 
 	if err != nil {
 		return nil, fmt.Errorf("loan_type error: %v", err)
@@ -125,9 +126,10 @@ func getLoanType(db *sql.DB, id int, isUser bool) ([]LoanType, error) {
 		var loan LoanType
 
 		if err := rows.Scan(
-			&loan.user,
-			&loan.branch,
-			&loan.name)
+			&loan.Id,
+			&loan.User,
+			&loan.Branch,
+			&loan.Name)
 		err != nil {
 			log.Printf("Error occured fetching loan: %v", err)
 			return nil, fmt.Errorf("Error occured fetching loan: %v", err)
@@ -136,6 +138,7 @@ func getLoanType(db *sql.DB, id int, isUser bool) ([]LoanType, error) {
 		loans = append(loans, loan)
 	}
 
+	log.Printf("The loans: %v", loans)
 	return loans, nil
 }
 
@@ -155,16 +158,16 @@ func getFees(db *sql.DB, user int) ([]FeeTemplate, error) {
 		var fee FeeTemplate
 
 		if err := rows.Scan(
-			&fee.id,
-			&fee.user,
-			&fee.branch,
-			&fee.amount,
-			&fee.perc,
-			&fee.ftype,
-			&fee.notes,
-			&fee.name,
-			&fee.category,
-			&fee.auto)
+			&fee.Id,
+			&fee.User,
+			&fee.Branch,
+			&fee.Amount,
+			&fee.Perc,
+			&fee.Ftype,
+			&fee.Notes,
+			&fee.Name,
+			&fee.Category,
+			&fee.Auto)
 		err != nil {
 			return nil, fmt.Errorf("The fees %q: %v", user, err)
         }
@@ -179,8 +182,8 @@ func getFees(db *sql.DB, user int) ([]FeeTemplate, error) {
 func route(w http.ResponseWriter, r *http.Request) {
     var page Page
 	var args []string
-
     p := r.URL.Path
+
     switch {
     case r.Method == "GET" && match(p, "/", &args):
         page = pages[ "home" ]
@@ -198,24 +201,51 @@ func route(w http.ResponseWriter, r *http.Request) {
     page.Render(w)
 }
 
-func main() {
-	files := http.FileServer(http.Dir(""))
+func api(w http.ResponseWriter, r *http.Request) {
+	var args []string
+	// var response string
+	p := r.URL.Path
 	db, err := sql.Open("mysql",
-		fmt.Sprintf( "%s:%s@tcp(127.0.0.1:3306)/skouter",
+		fmt.Sprintf("%s:%s@tcp(127.0.0.1:3306)/skouter",
 			config["DBUser"],
 			config["DBPass"]))
 
+	w.Header().Set("Content-Type", "application/json; charset=UTF-8")
+
 	err = db.Ping()
 	if err != nil {
-		// do something here like abort
 		print("Bad database configuration: %v", err)
 		panic(err)
 		// maybe os.Exit(1) instead
 	}
 
-	fmt.Print(getLoanType(db, 0, true))
+	switch {
+	case match(p, "/api/loans", &args):
+		resp, err := getLoanType(db, 0, true)
+
+		if resp != nil {
+			json.NewEncoder(w).Encode(resp)
+		} else {
+			json.NewEncoder(w).Encode(err)
+		}
+
+	case match(p, "/api/fees", &args):
+		resp, err := getFees(db, 0)
+
+		if resp != nil {
+			json.NewEncoder(w).Encode(resp)
+		} else {
+			json.NewEncoder(w).Encode(err)
+		}
+	}
+
+}
+
+func main() {
+	files := http.FileServer(http.Dir(""))
 
 	http.Handle("/assets/", files)
+	http.HandleFunc("/api/", api)
 	http.HandleFunc("/", route)
 	log.Fatal(http.ListenAndServe(address, nil))
 }