@@ -43,7 +43,6 @@ function getCookie(name) { | |||||
function refreshToken() { | function refreshToken() { | ||||
const token = getCookie("skouter") | const token = getCookie("skouter") | ||||
this.token = token | |||||
fetch(`/api/token`, | fetch(`/api/token`, | ||||
{method: 'GET', | {method: 'GET', | ||||
@@ -54,6 +53,8 @@ function refreshToken() { | |||||
}).then(response => { | }).then(response => { | ||||
if (!response.ok) { | if (!response.ok) { | ||||
console.log("Error refreshing token.") | console.log("Error refreshing token.") | ||||
} else { | |||||
this.token = getCookie("skouter") | |||||
} | } | ||||
}) | }) | ||||
@@ -99,7 +100,6 @@ function getFees() { | |||||
}).then (result => { | }).then (result => { | ||||
if (!result || !result.length) return // Exit if token is invalid or no fees are saved | if (!result || !result.length) return // Exit if token is invalid or no fees are saved | ||||
this.fees = result | this.fees = result | ||||
console.log(result) | |||||
}) | }) | ||||
} | } | ||||
@@ -33,18 +33,39 @@ | |||||
<section class="radios form"> | <section class="radios form"> | ||||
<h3>Transaction Type</h3> | <h3>Transaction Type</h3> | ||||
<input selected type="radio" name="transaction_type" value="0" | |||||
<input selected type="radio" name="transaction_type" | |||||
:selected="estimate.transaction == 'Purchase'" | |||||
:value="estimate.transaction" | :value="estimate.transaction" | ||||
@input="() => $emit('update:transaction', 0)" | |||||
@input="() => $emit('update:transaction', 'Purchase')" | |||||
> | > | ||||
<label>Purchase</label> | <label>Purchase</label> | ||||
<input type="radio" name="transaction_type" value="1" | |||||
<input type="radio" name="transaction_type" | |||||
:selected="estimate.transaction == 'Refinance'" | |||||
:value="estimate.transaction" | :value="estimate.transaction" | ||||
@input="() => $emit('update:transaction', 1)" | |||||
@input="() => $emit('update:transaction', 'Refinance')" | |||||
> | > | ||||
<label>Refinance</label> | <label>Refinance</label> | ||||
</section> | </section> | ||||
<section class="radios form"> | |||||
<h3>Occupancy</h3> | |||||
<input type="radio" name="occupancy" | |||||
:value="estimate.occupancy" | |||||
@input="() => $emit('update:occupancy', 'Primary')" | |||||
> | |||||
<label>Primary</label> | |||||
<input type="radio" name="occupancy" | |||||
:value="estimate.occupancy" | |||||
@input="() => $emit('update:occupancy', 'Secondary')" | |||||
> | |||||
<label>Secondary</label> | |||||
<input type="radio" name="occupancy" | |||||
:value="estimate.occupancy" | |||||
@input="() => $emit('update:occupancy', 'Residential')" | |||||
> | |||||
<label>Residential</label> | |||||
</section> | |||||
<section class="form inputs"> | <section class="form inputs"> | ||||
<h3>Property Details</h3> | <h3>Property Details</h3> | ||||
<label>Price ($)</label> | <label>Price ($)</label> | ||||
@@ -53,10 +74,10 @@ | |||||
<select id="" name="" | <select id="" name="" | ||||
:value="estimate.property" | :value="estimate.property" | ||||
@change="(e) => $emit('update:property', e.target.value)"> | @change="(e) => $emit('update:property', e.target.value)"> | ||||
<option value="attched">Single Family Attached</option> | |||||
<option value="detached">Single Family Detached</option> | |||||
<option value="lorise">Lo-rise (4 stories or less)</option> | |||||
<option value="hirise">Hi-rise (over 4 stories)</option> | |||||
<option value="Single Attached">Single Family Attached</option> | |||||
<option value="Single Detached">Single Family Detached</option> | |||||
<option value="Condo Lo-rise">Lo-rise (4 stories or less)</option> | |||||
<option value="Condo Hi-rise">Hi-rise (over 4 stories)</option> | |||||
</select> | </select> | ||||
</section> | </section> | ||||
@@ -113,8 +134,8 @@ | |||||
<label>Total DTI (%) - Optional</label> | <label>Total DTI (%) - Optional</label> | ||||
<input :value="loan.dti" @input="(e) => $emit('update:dti', e)"> | <input :value="loan.dti" @input="(e) => $emit('update:dti', e)"> | ||||
<label>Home Owner's Association ($/month)</label> | <label>Home Owner's Association ($/month)</label> | ||||
<input :value="loan.hoa / 100" | |||||
@input="(e) => { $emit('update:hoa', strip(e)) }"> | |||||
<input :value="loan.hoi / 100" | |||||
@input="(e) => { $emit('update:hoi', strip(e)) }"> | |||||
<label>Interest Rate (%)</label> | <label>Interest Rate (%)</label> | ||||
<input :value="loan.interest" | <input :value="loan.interest" | ||||
@@ -256,6 +277,7 @@ export default { | |||||
'update:borrowerCredit', | 'update:borrowerCredit', | ||||
'update:borrowerIncome', | 'update:borrowerIncome', | ||||
'update:transaction', | 'update:transaction', | ||||
'update:occupancy', | |||||
'update:price', | 'update:price', | ||||
'update:property', | 'update:property', | ||||
@@ -267,7 +289,7 @@ export default { | |||||
'update:amount', | 'update:amount', | ||||
'update:housingDti', | 'update:housingDti', | ||||
'update:dti', | 'update:dti', | ||||
'update:hoa', | |||||
'update:hoi', | |||||
'update:interest', | 'update:interest', | ||||
'update:interestDays', | 'update:interestDays', | ||||
'update:hazardEscrow', | 'update:hazardEscrow', | ||||
@@ -27,29 +27,30 @@ class="bi bi-plus" viewBox="0 0 16 16"> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 | |||||
:loan="loan" | :loan="loan" | ||||
:token="token" | :token="token" | ||||
@update:name="(name) => loans[sel].title = name" | |||||
@update:name="(name) => loan.title = name" | |||||
@del="del" | @del="del" | ||||
@update:borrowerNum="(b) => estimate.borrower.num = b" | @update:borrowerNum="(b) => estimate.borrower.num = b" | ||||
@update:borrowerCredit="(c) => estimate.borrower.credit = c" | @update:borrowerCredit="(c) => estimate.borrower.credit = c" | ||||
@update:borrowerIncome="(m) => estimate.borrower.income = m*100" | |||||
@update:borrowerIncome="(m) => estimate.borrower.income = Math.round(m*100)" | |||||
@update:transaction="(t) => estimate.transaction = t" | @update:transaction="(t) => estimate.transaction = t" | ||||
@update:occupancy="(t) => estimate.occupancy = t" | |||||
@update:price="setPrice" | @update:price="setPrice" | ||||
@update:property="(p) => estimate.property = p" | @update:property="(p) => estimate.property = p" | ||||
@update:loanType="changeLoanType" | @update:loanType="changeLoanType" | ||||
@update:term="(lt) => loan.term = lt" | @update:term="(lt) => loan.term = lt" | ||||
@update:program="(p) => loans[sel].program = p" | |||||
@update:program="(p) => loan.program = p" | |||||
@update:ltv="setLtv" | @update:ltv="setLtv" | ||||
@update:amount="setAmount" | @update:amount="setAmount" | ||||
@update:housingDti="setHousingDti" | @update:housingDti="setHousingDti" | ||||
@update:dti="setDti" | @update:dti="setDti" | ||||
@update:hoa="(hoa) => loan.hoa = hoa*100" | |||||
@update:hoi="(hoi) => loan.hoi = Math.round(hoi*100)" | |||||
@update:interest="(i) => loan.interest = i" | @update:interest="(i) => loan.interest = i" | ||||
@update:interestDays="(d) => loans[sel].interestDays = d" | @update:interestDays="(d) => loans[sel].interestDays = d" | ||||
@update:hazardEscrow="(h) => loans[sel].hazardEscrow = h" | @update:hazardEscrow="(h) => loans[sel].hazardEscrow = h" | ||||
@update:hazard="(h) => loan.hazard = h * 100" | |||||
@update:hazard="(h) => loan.hazard = Math.round(h * 100)" | |||||
@update:taxEscrow="(t) => loans[sel].taxEscrow = t" | @update:taxEscrow="(t) => loans[sel].taxEscrow = t" | ||||
@update:tax="(t) => loan.tax = t*100" | |||||
@update:tax="(t) => loan.tax = Math.round(t*100)" | |||||
@update:manualMI="perc => loan.mi.rate = perc" | @update:manualMI="perc => loan.mi.rate = perc" | ||||
@toggle:manualMIMonthly= | @toggle:manualMIMonthly= | ||||
"() => loans[sel].mi.monthly = !loans[sel].mi.monthly" | "() => loans[sel].mi.monthly = !loans[sel].mi.monthly" | ||||
@@ -81,7 +82,7 @@ const example = { | |||||
hazardEscrow: 0, // Hazard insurance escrow in months (0 is none) | hazardEscrow: 0, // Hazard insurance escrow in months (0 is none) | ||||
tax: 0, // Real Estate taxes monthly payment | tax: 0, // Real Estate taxes monthly payment | ||||
taxEscrow: 0, // Months to escrow (0 is none) | taxEscrow: 0, // Months to escrow (0 is none) | ||||
hoa: 10000, // Home owner's association monthly fee | |||||
hoi: 10000, // Home owner's association monthly fee | |||||
program: "", | program: "", | ||||
pud: true, // Property under development | pud: true, // Property under development | ||||
zip: '', | zip: '', | ||||
@@ -97,7 +98,8 @@ const loans = [ | |||||
// Default estimate fields | // Default estimate fields | ||||
const estimate = { | const estimate = { | ||||
property: "", | property: "", | ||||
transaction: 0, | |||||
transaction: "", | |||||
occupancy: "", | |||||
price: 0, | price: 0, | ||||
borrower: {num: 0, credit: 0, income: 0}, | borrower: {num: 0, credit: 0, income: 0}, | ||||
loans: loans, | loans: loans, | ||||
@@ -8,7 +8,10 @@ | |||||
Mortgage insurance: ${{format(loan.amount*loan.mi.rate/100/12)}} | Mortgage insurance: ${{format(loan.amount*loan.mi.rate/100/12)}} | ||||
</label> | </label> | ||||
<label>Property taxes: ${{format(loan.tax)}}</label> | <label>Property taxes: ${{format(loan.tax)}}</label> | ||||
<label>Homeowner's Insurance: ${{format(loan.hoa)}}</label> | |||||
<label>Homeowner's Insurance: ${{format(loan.hoi)}}</label> | |||||
<label v-if="loan.hazard"> | |||||
Hazard insurance: ${{format(loan.hazard)}} | |||||
</label> | |||||
</section> | </section> | ||||
<section class="form inputs"> | <section class="form inputs"> | ||||
@@ -22,7 +25,7 @@ | |||||
</section> | </section> | ||||
<section class="form inputs"> | <section class="form inputs"> | ||||
<button>Save Estimate</button> | |||||
<button @click="create">Save Estimate</button> | |||||
<button>Generate PDF</button> | <button>Generate PDF</button> | ||||
</section> | </section> | ||||
@@ -32,6 +35,7 @@ | |||||
<script setup> | <script setup> | ||||
import { ref, computed, onMounted } from 'vue' | import { ref, computed, onMounted } from 'vue' | ||||
let valid = ref(false) | let valid = ref(false) | ||||
let saved = ref(true) | |||||
const props = defineProps(['downpayment', 'loan', 'token', 'estimate']) | const props = defineProps(['downpayment', 'loan', 'token', 'estimate']) | ||||
function amortize(principle, rate, periods) { | function amortize(principle, rate, periods) { | ||||
@@ -49,7 +53,7 @@ const loanPayment = computed(() => { | |||||
const totalMonthly = computed (() => { | const totalMonthly = computed (() => { | ||||
let total = loanPayment.value + | let total = loanPayment.value + | ||||
props.loan.tax + | props.loan.tax + | ||||
props.loan.hoa + | |||||
props.loan.hoi + | |||||
props.loan.hazard | props.loan.hazard | ||||
if (props.loan.mi.monthly) { | if (props.loan.mi.monthly) { | ||||
@@ -102,6 +106,28 @@ function validate() { | |||||
} | } | ||||
function create() { | |||||
fetch(`/api/estimate`, | |||||
{method: 'POST', | |||||
body: JSON.stringify(props.estimate), | |||||
headers: { | |||||
"Accept": "application/json", | |||||
"Authorization": `Bearer ${props.token}`, | |||||
}, | |||||
}).then(resp => { | |||||
if (resp.ok && resp.status == 200) { | |||||
console.log('saved') | |||||
saved.value = true | |||||
return | |||||
} else { | |||||
// resp.text().then(t => this.errors = [t]) | |||||
// window.location.hash = 'new' | |||||
resp.text().then(t => console.log(t)) | |||||
} | |||||
}) | |||||
} | |||||
// Print number of cents as a nice string of dollars | // Print number of cents as a nice string of dollars | ||||
function format(num) { | function format(num) { | ||||
return (num/100).toFixed(2) | return (num/100).toFixed(2) | ||||
@@ -86,10 +86,11 @@ CREATE TABLE loan ( | |||||
type_id INT NOT NULL, | type_id INT NOT NULL, | ||||
amount INT NOT NULL, | amount INT NOT NULL, | ||||
term INT, /* In years */ | term INT, /* In years */ | ||||
interest INT, /* Per year, precise to 2 decimals */ | |||||
interest FLOAT(5, 2) DEFAULT 0, | |||||
ltv FLOAT(5, 2) DEFAULT 0, | ltv FLOAT(5, 2) DEFAULT 0, | ||||
dti FLOAT(5, 2) DEFAULT 1, | dti FLOAT(5, 2) DEFAULT 1, | ||||
hoi INT DEFAULT 0, /* Hazard insurance annual payments */ | hoi INT DEFAULT 0, /* Hazard insurance annual payments */ | ||||
tax INT DEFAULT 0, /* Real estate taxes */ | |||||
name VARCHAR(30) DEFAULT '', | name VARCHAR(30) DEFAULT '', | ||||
PRIMARY KEY (`id`), | PRIMARY KEY (`id`), | ||||
FOREIGN KEY (estimate_id) REFERENCES estimate(id), | FOREIGN KEY (estimate_id) REFERENCES estimate(id), | ||||
@@ -94,10 +94,11 @@ type Loan struct { | |||||
Ltv float32 `json:"ltv"` | Ltv float32 `json:"ltv"` | ||||
Dti float32 `json:"dti"` | Dti float32 `json:"dti"` | ||||
Hoi int `json:"hoi"` | Hoi int `json:"hoi"` | ||||
Tax int `json:"hoi"` | |||||
Interest float32 `json:"interest"` | Interest float32 `json:"interest"` | ||||
Mi MI `json:"mi"` | Mi MI `json:"mi"` | ||||
Fees []Fee `json:"fees"` | Fees []Fee `json:"fees"` | ||||
Name string `json:"name"` | |||||
Name string `json:"title"` | |||||
} | } | ||||
type MI struct { | type MI struct { | ||||
@@ -130,7 +131,7 @@ type Estimate struct { | |||||
Id int `json:"id"` | Id int `json:"id"` | ||||
User int `json:"user"` | User int `json:"user"` | ||||
Borrower Borrower `json:"borrower"` | Borrower Borrower `json:"borrower"` | ||||
Transaction int `json:"transaction"` | |||||
Transaction string `json:"transaction"` | |||||
Price int `json:"price"` | Price int `json:"price"` | ||||
Property string `json:"property"` | Property string `json:"property"` | ||||
Occupancy string `json:"occupancy"` | Occupancy string `json:"occupancy"` | ||||
@@ -242,7 +243,6 @@ func getLoanType( | |||||
loans = append(loans, loan) | loans = append(loans, loan) | ||||
} | } | ||||
log.Printf("The loans: %v", loans) | |||||
return loans, nil | return loans, nil | ||||
} | } | ||||
@@ -499,9 +499,9 @@ func fetchMi(db *sql.DB, estimate *Estimate, pos int) []MI { | |||||
"Condo Hi-rise": "CON", | "Condo Hi-rise": "CON", | ||||
} | } | ||||
var purposeCodes = map[int]string { | |||||
1: "PUR", | |||||
2: "RRT", | |||||
var purposeCodes = map[string]string { | |||||
"Purchase": "PUR", | |||||
"Refinance": "RRT", | |||||
} | } | ||||
body, err := json.Marshal(map[string]any{ | body, err := json.Marshal(map[string]any{ | ||||
@@ -877,11 +877,281 @@ func createUser(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
json.NewEncoder(w).Encode(user) | json.NewEncoder(w).Encode(user) | ||||
} | } | ||||
func queryBorrower(db *sql.DB, id int) ( Borrower, error ) { | |||||
var borrower Borrower | |||||
var query string | |||||
var err error | |||||
query = `SELECT | |||||
l.id, | |||||
l.credit_score, | |||||
l.num, | |||||
l.monthly_income | |||||
FROM borrower l WHERE l.id = ? | |||||
` | |||||
row := db.QueryRow(query, id) | |||||
err = row.Scan( | |||||
borrower.Id, | |||||
borrower.Credit, | |||||
borrower.Num, | |||||
borrower.Income, | |||||
) | |||||
if err != nil { | |||||
return borrower, err | |||||
} | |||||
return borrower, nil | |||||
} | |||||
// Must have an estimate ID 'e', but not necessarily a loan id 'id' | |||||
func queryLoan(db *sql.DB, e int, id int) ( []Loan, error ) { | |||||
var loans []Loan | |||||
var query string | |||||
var rows *sql.Rows | |||||
var err error | |||||
fmt.Println(e, id) | |||||
query = `SELECT | |||||
l.id, | |||||
l.estimate_id, | |||||
l.amount, | |||||
l.term, | |||||
l.interest, | |||||
l.ltv, | |||||
l.dti, | |||||
l.hoi, | |||||
l.tax, | |||||
l.name | |||||
FROM loan l WHERE l.id = CASE @e := ? WHEN 0 THEN l.id ELSE @e END AND | |||||
l.estimate_id = ? | |||||
` | |||||
rows, err = db.Query(query, id, e) | |||||
if err != nil { | |||||
return loans, err | |||||
} | |||||
defer rows.Close() | |||||
for rows.Next() { | |||||
var loan Loan | |||||
if err := rows.Scan( | |||||
&loan.Id, | |||||
&loan.EstimateId, | |||||
&loan.Amount, | |||||
&loan.Term, | |||||
&loan.Interest, | |||||
&loan.Ltv, | |||||
&loan.Dti, | |||||
&loan.Hoi, | |||||
&loan.Tax, | |||||
&loan.Name, | |||||
) | |||||
err != nil { | |||||
return loans, err | |||||
} | |||||
loans = append(loans, loan) | |||||
} | |||||
// Prevents runtime panics | |||||
if len(loans) == 0 { return loans, errors.New("Loan not found.") } | |||||
return loans, nil | |||||
} | |||||
func queryEstimate(db *sql.DB, id int) ( []Estimate, error ) { | |||||
var estimates []Estimate | |||||
var query string | |||||
var rows *sql.Rows | |||||
var err error | |||||
query = `SELECT | |||||
u.id, | |||||
u.user_id, | |||||
u.borrower_id, | |||||
u.transaction, | |||||
u.price, | |||||
u.property, | |||||
u.occupancy, | |||||
u.zip, | |||||
u.pud | |||||
FROM estimate u WHERE u.id = CASE @e := ? WHEN 0 THEN u.id ELSE @e END | |||||
` | |||||
rows, err = db.Query(query, id) | |||||
if err != nil { | |||||
return estimates, err | |||||
} | |||||
defer rows.Close() | |||||
for rows.Next() { | |||||
var estimate Estimate | |||||
if err := rows.Scan( | |||||
&estimate.Id, | |||||
&estimate.User, | |||||
&estimate.Borrower.Id, | |||||
&estimate.Transaction, | |||||
&estimate.Price, | |||||
&estimate.Property, | |||||
&estimate.Occupancy, | |||||
&estimate.Zip, | |||||
&estimate.Pud, | |||||
) | |||||
err != nil { | |||||
return estimates, err | |||||
} | |||||
estimates = append(estimates, estimate) | |||||
} | |||||
// Prevents runtime panics | |||||
if len(estimates) == 0 { return estimates, errors.New("Estimate not found.") } | |||||
for _, e := range estimates { | |||||
fmt.Println("here's the estimate ID:", e.Id) | |||||
e.Loans, err = queryLoan(db, e.Id, 0) | |||||
if err != nil { return estimates, err } | |||||
} | |||||
return estimates, nil | |||||
} | |||||
// Accepts a borrower struct and returns the id of the inserted borrower and | |||||
// any related error. | |||||
func insertBorrower(db *sql.DB, borrower Borrower) (int, error) { | |||||
var query string | |||||
var row *sql.Row | |||||
var err error | |||||
var id int // Inserted loan's id | |||||
query = `INSERT INTO borrower | |||||
( | |||||
credit_score, | |||||
monthly_income, | |||||
num | |||||
) | |||||
VALUES (?, ?, ?) | |||||
RETURNING id | |||||
` | |||||
row = db.QueryRow(query, | |||||
borrower.Credit, | |||||
borrower.Income, | |||||
borrower.Num, | |||||
) | |||||
err = row.Scan(&id) | |||||
if err != nil { return 0, err } | |||||
return id, nil | |||||
} | |||||
func insertLoan(db *sql.DB, loan Loan) (Loan, error){ | |||||
var query string | |||||
var row *sql.Row | |||||
var err error | |||||
var id int // Inserted loan's id | |||||
query = `INSERT INTO loan | |||||
( | |||||
estimate_id, | |||||
type_id, | |||||
amount, | |||||
term, | |||||
interest, | |||||
ltv, | |||||
dti, | |||||
hoi, | |||||
tax, | |||||
name | |||||
) | |||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) | |||||
RETURNING id | |||||
` | |||||
row = db.QueryRow(query, | |||||
loan.EstimateId, | |||||
loan.Type.Id, | |||||
loan.Amount, | |||||
loan.Term, | |||||
loan.Interest, | |||||
loan.Ltv, | |||||
loan.Dti, | |||||
loan.Hoi, | |||||
loan.Tax, | |||||
loan.Name, | |||||
) | |||||
err = row.Scan(&id) | |||||
if err != nil { return Loan{}, err } | |||||
loans, err := queryLoan(db, id, 0) | |||||
if err != nil { return Loan{}, err } | |||||
return loans[0], nil | |||||
} | |||||
func insertEstimate(db *sql.DB, estimate Estimate) (Estimate, error){ | |||||
var query string | |||||
var row *sql.Row | |||||
var err error | |||||
// var id int // Inserted estimate's id | |||||
estimate.Borrower.Id, err = insertBorrower(db, estimate.Borrower) | |||||
if err != nil { return Estimate{}, err } | |||||
query = `INSERT INTO estimate | |||||
( | |||||
user_id, | |||||
borrower_id, | |||||
transaction, | |||||
price, | |||||
property, | |||||
occupancy, | |||||
zip, | |||||
pud | |||||
) | |||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?) | |||||
RETURNING id | |||||
` | |||||
row = db.QueryRow(query, | |||||
estimate.User, | |||||
estimate.Borrower.Id, | |||||
estimate.Transaction, | |||||
estimate.Price, | |||||
estimate.Property, | |||||
estimate.Occupancy, | |||||
estimate.Zip, | |||||
estimate.Pud, | |||||
) | |||||
err = row.Scan(&estimate.Id) | |||||
if err != nil { return Estimate{}, err } | |||||
for _, l := range estimate.Loans { | |||||
l.EstimateId = estimate.Id | |||||
_, err = insertLoan(db, l) | |||||
if err != nil { return estimate, err } | |||||
} | |||||
estimates, err := queryEstimate(db, estimate.Id) | |||||
if err != nil { return Estimate{}, err } | |||||
return estimates[0], nil | |||||
} | |||||
func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) { | func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) { | ||||
var estimate Estimate | var estimate Estimate | ||||
err := json.NewDecoder(r.Body).Decode(&estimate) | err := json.NewDecoder(r.Body).Decode(&estimate) | ||||
if err != nil { http.Error(w, "Invalid fields.", 422); return } | if err != nil { http.Error(w, "Invalid fields.", 422); return } | ||||
estimate, err = insertEstimate(db, estimate) | |||||
if err != nil { http.Error(w, err.Error(), 422); return } | |||||
json.NewEncoder(w).Encode(estimate) | json.NewEncoder(w).Encode(estimate) | ||||
} | } | ||||