|
- package main
-
- import (
- "biukop.com/sfm/loan"
- "database/sql"
- "encoding/json"
- "errors"
- "fmt"
- "github.com/brianvoe/gofakeit/v6"
- log "github.com/sirupsen/logrus"
- "net/http"
- "net/http/httputil"
- "strings"
- "time"
- )
-
- const apiV1Prefix = "/api/v1/"
- const apiV1WebSocket = apiV1Prefix + "ws"
-
- type apiV1HandlerMap struct {
- Method string
- Path string //regex
- Handler func(http.ResponseWriter, *http.Request, *loan.Session)
- }
-
- var apiV1Handler = setupApiV1Handler()
-
- func setupApiV1Handler() []apiV1HandlerMap {
- if config.Debug { //debug only
- return []apiV1HandlerMap{
- {"POST", "login", apiV1Login},
- {"*", "logout", apiV1Logout},
- {"GET", "chart/type-of-loans", apiV1ChartTypeOfLoans},
- {"GET", "chart/amount-of-loans", apiV1ChartTypeOfLoans},
- {"GET", "chart/past-year-monthly", apiV1ChartPastYearMonthly},
- {"GET", "chart/recent-10-loans", apiV1ChartRecent10Loans},
- {"GET", "chart/top-broker", apiV1ChartTopBroker},
- {"POST", "grid/loan/full-loan-overview", apiV1GridLoanFullOverview},
- {"GET", "chart/reward-vs-income-monthly", apiV1ChartRewardVsIncomeMonthly},
- {"GET", "loan/", apiV1LoanSingleGet},
- {"DELETE", "loan/", apiV1LoanSingleDelete},
- {"GET", "loan-by-client/", apiV1LoanByClient},
-
- {"GET", "people/", apiV1PeopleGet},
- {"POST", "people/", apiV1PeoplePost},
- {"PUT", "people/", apiV1PeoplePut},
- {"DELETE", "people/", apiV1PeopleDelete},
- {"GET", "people-extra/", apiV1PeopleExtraGet},
-
- {"POST", "user/", apiV1UserPost},
- {"PUT", "user/", apiV1UserPut},
- {"DELETE", "user/", apiV1UserDelete},
- {"POST", "user-enable/", apiV1UserEnable},
-
- {"GET", "broker/", apiV1BrokerGet},
- {"POST", "broker/", apiV1BrokerPost},
- {"PUT", "broker/", apiV1BrokerPut},
- {"DELETE", "broker/", apiV1BrokerDelete},
-
- {"POST", "change-pass/", apiV1ChangePass},
- {"POST", "loan/basic/", apiV1LoanSinglePostBasic},
- {"GET", "avatar/", apiV1Avatar},
- {"POST", "avatar/", apiV1AvatarPost},
- {"POST", "reward/", apiV1RewardPost},
- {"DELETE", "reward/", apiV1RewardDelete},
- {"GET", "people-list/", apiV1PeopleList},
- {"GET", "broker-list/", apiV1BrokerList},
- {"POST", "sync-people/", apiV1SyncPeople},
-
- {"POST", "payIn/", apiV1PayInPost},
- {"DELETE", "payIn/", apiV1PayInDelete},
- {"POST", "pay-in-list/", apiV1PayInList},
-
- {"GET", "user-reward/", apiV1UserReward},
- {"GET", "login-available/", apiV1LoginAvailable},
-
- {"POST", "lender-upload/", apiV1UploadsPost},
- {"GET", "lender-upload/", apiV1UploadOriginalFileGet},
-
- {"GET", "upload-analysis/", apiV1UploadAnalysis},
- {"PUT", "upload-analysis/", apiV1UploadCreateAnalysis},
- {"GET", "upload-as-image/", apiV1UploadAsImage},
- {"PUT", "upload-as-image/", apiV1UploadCreateImage},
- {"GET", "upload-as-thumbnail/", apiV1UploadAsThumbnail},
- {"PUT", "upload-as-thumbnail/", apiV1UploadCreateThumbnail},
- {"GET", "upload-as-pdf/", apiV1UploadAsPDF},
- {"PUT", "upload-as-pdf/", apiV1UploadCreatePDF},
- {"GET", "upload-original/", apiV1UploadOriginalFileGet},
- {"GET", "upload/", apiV1UploadMetaGet},
- {"DELETE", "upload/", apiV1UploadDelete},
- {"POST", "upload-meta-list/", apiV1UploadMetaList},
-
- {"GET", "login", apiV1DumpRequest},
- }
- } else { //production
- return []apiV1HandlerMap{
- {"POST", "login", apiV1Login},
- {"*", "logout", apiV1Logout},
- {"GET", "chart/type-of-loans", apiV1ChartTypeOfLoans},
- {"GET", "chart/amount-of-loans", apiV1ChartTypeOfLoans},
- {"GET", "chart/past-year-monthly", apiV1ChartPastYearMonthly},
- {"GET", "chart/recent-10-loans", apiV1ChartRecent10Loans},
- {"GET", "chart/top-broker", apiV1ChartTopBroker},
- {"POST", "grid/loan/full-loan-overview", apiV1GridLoanFullOverview},
- {"GET", "chart/reward-vs-income-monthly", apiV1ChartRewardVsIncomeMonthly},
- {"GET", "loan/", apiV1LoanSingleGet},
- {"DELETE", "loan/", apiV1LoanSingleDelete},
- {"GET", "loan-by-client/", apiV1LoanByClient},
-
- {"GET", "people/", apiV1PeopleGet},
- {"POST", "people/", apiV1PeoplePost},
- {"PUT", "people/", apiV1PeoplePut},
- {"DELETE", "people/", apiV1PeopleDelete},
- {"GET", "people-extra/", apiV1PeopleExtraGet},
-
- {"POST", "user/", apiV1UserPost},
- {"PUT", "user/", apiV1UserPut},
- {"DELETE", "user/", apiV1UserDelete},
- {"POST", "user-enable/", apiV1UserEnable},
-
- {"GET", "broker/", apiV1BrokerGet},
- {"POST", "broker/", apiV1BrokerPost},
- {"PUT", "broker/", apiV1BrokerPut},
- {"DELETE", "broker/", apiV1BrokerDelete},
-
- {"POST", "change-pass/", apiV1ChangePass},
- {"POST", "loan/basic/", apiV1LoanSinglePostBasic},
-
- {"GET", "avatar/", apiV1Avatar},
- {"POST", "avatar/", apiV1AvatarPost},
- {"POST", "reward/", apiV1RewardPost},
- {"DELETE", "reward/", apiV1RewardDelete},
- {"GET", "people-list", apiV1PeopleList},
- {"GET", "broker-list/", apiV1BrokerList},
- {"POST", "sync-people/", apiV1SyncPeople},
-
- {"POST", "payIn/", apiV1PayInPost},
- {"DELETE", "payIn/", apiV1PayInDelete},
- {"POST", "pay-in-list/", apiV1PayInList},
-
- {"GET", "user-reward/", apiV1UserReward},
- {"GET", "login-available/", apiV1LoginAvailable},
-
- {"POST", "lender-upload/", apiV1UploadsPost},
- {"GET", "lender-upload/", apiV1UploadOriginalFileGet},
-
- {"GET", "upload-analysis/", apiV1UploadAnalysis},
- {"PUT", "upload-analysis/", apiV1UploadCreateAnalysis},
- {"GET", "upload-as-image/", apiV1UploadAsImage},
- {"PUT", "upload-as-image/", apiV1UploadCreateImage},
- {"GET", "upload-as-thumbnail/", apiV1UploadAsThumbnail},
- {"PUT", "upload-as-thumbnail/", apiV1UploadCreateThumbnail},
- {"GET", "upload-as-pdf/", apiV1UploadAsPDF},
- {"PUT", "upload-as-pdf/", apiV1UploadCreatePDF},
- {"GET", "upload-original/", apiV1UploadOriginalFileGet},
- {"GET", "upload-meta/", apiV1UploadMetaGet},
- {"DELETE", "upload/", apiV1UploadDelete},
- {"POST", "upload-meta-list/", apiV1UploadMetaList},
-
- {"GET", "login", apiV1EmptyResponse},
- }
- }
- }
-
- //
- //apiV1Main version 1 main entry for all REST API
- //
- func apiV1Main(w http.ResponseWriter, r *http.Request) {
-
- //CORS setup
- setupCrossOriginResponse(&w, r)
-
- //if its options then we don't bother with other issues
- if r.Method == "OPTIONS" {
- apiV1EmptyResponse(w, r, nil)
- return //stop processing
- }
-
- if config.Debug {
- logRequestDebug(httputil.DumpRequest(r, true))
- }
-
- session := apiV1InitSession(r)
- if config.Debug {
- log.Debugf("session : %+v", session)
- }
-
- //search through handler
- path := r.URL.Path[len(apiV1Prefix):] //strip API prefix
- for _, node := range apiV1Handler {
- //log.Println(node, path, strings.HasPrefix(path, node.Path))
- if (r.Method == node.Method || node.Method == "*") && strings.HasPrefix(path, node.Path) {
- node.Handler(w, r, &session)
- e := session.Write() //finish this session to DB
- if e != nil {
- log.Warnf("Failed to Save Session %+v \n reason \n%s\n", session, e.Error())
- }
- return
- }
- }
-
- //Catch for all UnHandled Request
- e := session.Write() //finish this session to DB
- if e != nil {
- log.Warnf("Failed to Save Session %+v \n reason \n%s\n", session, e.Error())
- }
- if config.Debug {
- apiV1DumpRequest(w, r, &session)
- } else {
- apiV1EmptyResponse(w, r, &session)
- }
-
- }
-
- func apiV1InitSession(r *http.Request) (session loan.Session) {
- session.MarkEmpty()
-
- //track browser, and take session from cookie
- cookieSession, e := apiV1InitSessionByBrowserId(r)
- if e == nil {
- session = cookieSession
- }
-
- //try session login first, if not an empty session will be created
- headerSession, e := apiV1InitSessionByHttpHeader(r)
- if e == nil {
- session = headerSession
- }
-
- if session.IsEmpty() {
- session.InitGuest(time.Now().Add(loan.DefaultSessionDuration))
- } else {
- session.RenewIfExpireSoon()
- }
- //we have a session anyway
- session.Add("Biukop-Mid", apiV1GetMachineId(r)) //set machine id
- session.SetRemote(r) //make sure they are using latest remote
- return
- }
-
- func setupCrossOriginResponse(w *http.ResponseWriter, r *http.Request) {
- origin := r.Header.Get("Origin")
- if origin == "" {
- origin = "*"
- }
- requestedHeaders := r.Header.Get("Access-control-Request-Headers")
- method := r.Header.Get("Access-Control-Request-Method")
- (*w).Header().Set("Access-Control-Allow-Origin", origin) //for that specific origin
- (*w).Header().Set("Access-Control-Allow-Credentials", "true")
- (*w).Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE, "+method)
- (*w).Header().Set("Access-Control-Allow-Headers", "Accept, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, Cookie, Biukop-Session, Biukop-Session-Token, Biukop-Session-Expire, "+requestedHeaders)
- }
-
- func apiV1GetMachineId(r *http.Request) string {
- var mid string
- inCookie, e := r.Cookie("Biukop-Mid")
- if e == nil {
- mid = inCookie.Value
- } else {
- mid = gofakeit.UUID()
- }
-
- headerMid := r.Header.Get("Biukop-Mid")
- if headerMid != "" {
- mid = headerMid
- }
- return mid
- }
-
- func apiV1InitSessionByBrowserId(r *http.Request) (session loan.Session, e error) {
- var sid string
- mid := apiV1GetMachineId(r)
- inCookie, e := r.Cookie("Biukop-Session")
- if e == nil {
- sid = inCookie.Value
- if sid != "" {
- e = session.Read(sid)
- if e == nil {
- if mid != session.Get("Biukop-Mid") {
- session.MarkEmpty()
- }
- }
- }
- }
- return
- }
-
- func apiV1AddTrackingCookie(w http.ResponseWriter, r *http.Request, session *loan.Session) {
- //set session header too.
- w.Header().Add("Access-Control-Expose-Headers", "Biukop-Session")
- if session == nil {
- w.Header().Add("Biukop-Session", "")
- } else {
- w.Header().Add("Biukop-Session", session.Id)
- }
-
- //add tracking cookie
- expiration := time.Now().Add(365 * 24 * time.Hour)
- mid := apiV1GetMachineId(r)
- cookie := http.Cookie{Name: "Biukop-Mid", Value: mid, Expires: expiration, Path: "/", Secure: true, SameSite: http.SameSiteNoneMode}
- http.SetCookie(w, &cookie)
-
- if session != nil {
- cookie = http.Cookie{Name: "Biukop-Session", Value: session.Id, Expires: expiration, Path: "/", Secure: true, SameSite: http.SameSiteNoneMode}
- http.SetCookie(w, &cookie)
- }
- }
-
- func apiV1InitSessionByHttpHeader(r *http.Request) (ss loan.Session, e error) {
- sid := r.Header.Get("Biukop-Session")
- ss.MarkEmpty()
- //make sure session id is given
- if sid != "" {
- e = ss.Retrieve(r)
- if e == sql.ErrNoRows { //db does not have required session.
- log.Warn("DB has no corresponding session ", sid)
- } else if e != nil { // retrieve DB has error
- log.Errorf("Retrieve Session %s encountered error %s", sid, e.Error())
- }
- } else {
- e = errors.New("session not found: " + sid)
- }
- return
- return
- }
-
- func apiV1ErrorCheck(e error) {
- if nil != e {
- panic(e.Error()) //TODO: detailed error check, truck all caller
- }
- }
-
- func apiV1Server500Error(w http.ResponseWriter, r *http.Request) {
- //general setup
- w.Header().Set("Content-Type", "application/json;charset=UTF-8")
-
- w.WriteHeader(500)
- apiV1AddTrackingCookie(w, r, nil) //always the last one to set cookies
- fmt.Fprintf(w, "Server Internal Error "+time.Now().Format(time.RFC1123))
-
- //write log
- dump := logRequestDebug(httputil.DumpRequest(r, true))
- dump = strings.TrimSpace(dump)
- log.Warnf("Unhandled Protocol = %s path= %s", r.Method, r.URL.Path)
- }
-
- func apiV1Client403Error(w http.ResponseWriter, r *http.Request, ss *loan.Session) {
- //general setup
- w.Header().Set("Content-Type", "application/json;charset=UTF-8")
-
- w.WriteHeader(403)
- type struct403 struct {
- Error int
- ErrorMsg string
- }
- e403 := struct403{Error: 403, ErrorMsg: "Not Authorized " + time.Now().Format(time.RFC1123)}
- msg403, _ := json.Marshal(e403)
-
- //before send out
- apiV1AddTrackingCookie(w, r, ss) //always the last one to set cookies
- fmt.Fprintln(w, string(msg403))
-
- //write log
- dump := logRequestDebug(httputil.DumpRequest(r, true))
- dump = strings.TrimSpace(dump)
- log.Warnf("Not authorized http(%s) path= %s, %s", r.Method, r.URL.Path, dump)
- }
-
- func apiV1Client404Error(w http.ResponseWriter, r *http.Request, ss *loan.Session) {
- //general setup
- w.Header().Set("Content-Type", "application/json;charset=UTF-8")
-
- w.WriteHeader(404)
- type struct404 struct {
- Error int
- ErrorMsg string
- }
- e404 := struct404{Error: 404, ErrorMsg: "Not Found " + time.Now().Format(time.RFC1123)}
- msg404, _ := json.Marshal(e404)
-
- //before send out
- apiV1AddTrackingCookie(w, r, ss) //always the last one to set cookies
- fmt.Fprintln(w, string(msg404))
-
- //write log
- dump := logRequestDebug(httputil.DumpRequest(r, true))
- dump = strings.TrimSpace(dump)
- log.Warnf("Not found http(%s) path= %s, %s", r.Method, r.URL.Path, dump)
- }
-
- func apiV1DumpRequest(w http.ResponseWriter, r *http.Request, ss *loan.Session) {
- dump := logRequestDebug(httputil.DumpRequest(r, true))
- dump = strings.TrimSpace(dump)
- msg := fmt.Sprintf("Unhandled Protocol = %s path= %s", r.Method, r.URL.Path)
- dumpLines := strings.Split(dump, "\r\n")
- ar := apiV1ResponseBlank()
- ar.Env.Msg = msg
- ar.Env.Session = *ss
- ar.Env.Session.Bin = []byte("masked data") //clear
- ar.Env.Session.Secret = "***********"
- ar.add("Body", dumpLines)
- ar.add("Biukop-Mid", ss.Get("Biukop-Mid"))
- b, _ := ar.toJson()
-
- apiV1AddTrackingCookie(w, r, ss) //always the last one to set cookies
- fmt.Fprintf(w, "%s\n", b)
- }
-
- func apiV1EmptyResponse(w http.ResponseWriter, r *http.Request, ss *loan.Session) {
- //general setup
- w.Header().Set("Content-Type", "application/json;charset=UTF-8")
-
- apiV1AddTrackingCookie(w, r, ss) //always the last one to set cookies
- fmt.Fprintf(w, "")
- }
|