You've already forked openaccounting-server
mirror of
https://github.com/openaccounting/oa-server.git
synced 2025-12-09 00:50:59 +13:00
initial commit
This commit is contained in:
302
core/api/account.go
Normal file
302
core/api/account.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/model"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
/**
|
||||
* @api {get} /orgs/:orgId/accounts Get Accounts by Org id
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetOrgAccounts
|
||||
* @apiGroup Account
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Account.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {Date} inserted Date Account was created
|
||||
* @apiSuccess {Date} updated Date Account was updated
|
||||
* @apiSuccess {String} name Name of the Account.
|
||||
* @apiSuccess {String} parent Id of the parent Account.
|
||||
* @apiSuccess {String} currency Three letter currency code.
|
||||
* @apiSuccess {Number} precision How many digits the currency goes out to.
|
||||
* @apiSuccess {Boolean} debitBalance True if Account has a debit balance.
|
||||
* @apiSuccess {Number} balance Current Account balance in this Account's currency
|
||||
* @apiSuccess {Number} nativeBalance Current Account balance in the Org's currency
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "id": "22222222222222222222222222222222",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "name": "Cash",
|
||||
* "parent": "11111111111111111111111111111111",
|
||||
* "currency": "USD",
|
||||
* "precision": 2,
|
||||
* "debitBalance": true,
|
||||
* "balance": 10000,
|
||||
* "nativeBalance": 10000
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetOrgAccounts(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
// TODO how do we make date an optional parameter
|
||||
// instead of resorting to this hack?
|
||||
date := time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
dateParam := r.URL.Query().Get("date")
|
||||
|
||||
if dateParam != "" {
|
||||
dateParamNumeric, err := strconv.ParseInt(dateParam, 10, 64)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, "invalid date", 400)
|
||||
return
|
||||
}
|
||||
date = time.Unix(0, dateParamNumeric*1000000)
|
||||
}
|
||||
|
||||
accounts, err := model.Instance.GetAccountsWithBalances(orgId, user.Id, "", date)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&accounts)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /orgs/:orgId/accounts Create a new Account
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PostAccount
|
||||
* @apiGroup Account
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} id Id 32 character hex string
|
||||
* @apiParam {String} name Name of the Account.
|
||||
* @apiParam {String} parent Id of the parent Account.
|
||||
* @apiParam {String} currency Three letter currency code.
|
||||
* @apiParam {Number} precision How many digits the currency goes out to.
|
||||
* @apiParam {Boolean} debitBalance True if account has a debit balance.
|
||||
* @apiParam {Number} balance Current Account balance in this Account's currency
|
||||
* @apiParam {Number} nativeBalance Current Account balance in the Org's currency
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Account.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {Date} inserted Date Account was created
|
||||
* @apiSuccess {Date} updated Date Account was updated
|
||||
* @apiSuccess {String} name Name of the Account.
|
||||
* @apiSuccess {String} parent Id of the parent Account.
|
||||
* @apiSuccess {String} currency Three letter currency code.
|
||||
* @apiSuccess {Number} precision How many digits the currency goes out to.
|
||||
* @apiSuccess {Boolean} debitBalance True if account has a debit balance.
|
||||
* @apiSuccess {Number} balance Current Account balance in this Account's currency
|
||||
* @apiSuccess {Number} nativeBalance Current Account balance in the Org's currency
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "22222222222222222222222222222222",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "name": "Cash",
|
||||
* "parent": "11111111111111111111111111111111",
|
||||
* "currency": "USD",
|
||||
* "precision": 2,
|
||||
* "debitBalance": true,
|
||||
* "balance": 10000,
|
||||
* "nativeBalance": 10000
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PostAccount(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
content, err := ioutil.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if len(content) == 0 {
|
||||
rest.Error(w, "JSON payload is empty", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
account := types.NewAccount()
|
||||
|
||||
err = json.Unmarshal(content, &account)
|
||||
|
||||
if err != nil {
|
||||
// Maybe it's an array of accounts?
|
||||
PostAccounts(w, r, content)
|
||||
return
|
||||
}
|
||||
|
||||
account.OrgId = orgId
|
||||
err = model.Instance.CreateAccount(account, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&account)
|
||||
}
|
||||
|
||||
func PostAccounts(w rest.ResponseWriter, r *rest.Request, content []byte) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
accounts := make([]*types.Account, 0)
|
||||
|
||||
err := json.Unmarshal(content, &accounts)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for _, account := range accounts {
|
||||
account.OrgId = orgId
|
||||
err = model.Instance.CreateAccount(account, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteJson(accounts)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {put} /orgs/:orgId/accounts/:accountId Modify an Account
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PutAccount
|
||||
* @apiGroup Account
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} id Id 32 character hex string
|
||||
* @apiParam {String} name Name of the Account.
|
||||
* @apiParam {String} parent Id of the parent Account.
|
||||
* @apiParam {String} currency Three letter currency code.
|
||||
* @apiParam {Number} precision How many digits the currency goes out to.
|
||||
* @apiParam {Boolean} debitBalance True if Account has a debit balance.
|
||||
* @apiParam {Number} balance Current Account balance in this Account's currency
|
||||
* @apiParam {Number} nativeBalance Current Account balance in the Org's currency
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Account.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {Date} inserted Date Account was created
|
||||
* @apiSuccess {Date} updated Date Account was updated
|
||||
* @apiSuccess {String} name Name of the Account.
|
||||
* @apiSuccess {String} parent Id of the parent Account.
|
||||
* @apiSuccess {String} currency Three letter currency code.
|
||||
* @apiSuccess {Number} precision How many digits the currency goes out to.
|
||||
* @apiSuccess {Boolean} debitBalance True if Account has a debit balance.
|
||||
* @apiSuccess {Number} balance Current Account balance in this Account's currency
|
||||
* @apiSuccess {Number} nativeBalance Current Account balance in the Org's currency
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "22222222222222222222222222222222",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "name": "Cash",
|
||||
* "parent": "11111111111111111111111111111111",
|
||||
* "currency": "USD",
|
||||
* "precision": 2,
|
||||
* "debitBalance": true,
|
||||
* "balance": 10000,
|
||||
* "nativeBalance": 10000
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PutAccount(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
accountId := r.PathParam("accountId")
|
||||
|
||||
account := types.Account{}
|
||||
err := r.DecodeJsonPayload(&account)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
account.Id = accountId
|
||||
account.OrgId = orgId
|
||||
|
||||
err = model.Instance.UpdateAccount(&account, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&account)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {delete} /orgs/:orgId/accounts/:accountId Delete an Account
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName DeleteAccount
|
||||
* @apiGroup Account
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func DeleteAccount(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
accountId := r.PathParam("accountId")
|
||||
|
||||
err := model.Instance.DeleteAccount(accountId, user.Id, orgId)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
77
core/api/api.go
Normal file
77
core/api/api.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
)
|
||||
|
||||
/**
|
||||
* @apiDefine NotAuthorizedError
|
||||
*
|
||||
* @apiError NotAuthorized API request does not have proper credentials
|
||||
*
|
||||
* @apiErrorExample Error-Response:
|
||||
* HTTP/1.1 403 Not Authorized
|
||||
*/
|
||||
|
||||
/**
|
||||
* @apiDefine InternalServerError
|
||||
*
|
||||
* @apiError InternalServer An internal error occurred
|
||||
*
|
||||
* @apiErrorExample Error-Response:
|
||||
* HTTP/1.1 500 Internal Server Error
|
||||
* {
|
||||
* "error": "id required"
|
||||
* }
|
||||
*
|
||||
*/
|
||||
|
||||
func Init() (*rest.Api, error) {
|
||||
rest.ErrorFieldName = "error"
|
||||
app := rest.NewApi()
|
||||
|
||||
logger := &LoggerMiddleware{}
|
||||
|
||||
var stack = []rest.Middleware{
|
||||
logger,
|
||||
&rest.RecorderMiddleware{},
|
||||
&rest.TimerMiddleware{},
|
||||
&rest.PoweredByMiddleware{},
|
||||
&rest.RecoverMiddleware{},
|
||||
&rest.GzipMiddleware{},
|
||||
&rest.ContentTypeCheckerMiddleware{},
|
||||
}
|
||||
|
||||
app.Use(stack...)
|
||||
|
||||
app.Use(&rest.CorsMiddleware{
|
||||
RejectNonCorsRequests: false,
|
||||
OriginValidator: func(origin string, request *rest.Request) bool {
|
||||
//return origin == "http://localhost:4200"
|
||||
return true
|
||||
},
|
||||
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
||||
AllowedHeaders: []string{
|
||||
"Accept", "Content-Type", "X-Custom-Header", "Origin", "Authorization", "Accept-Version"},
|
||||
AccessControlAllowCredentials: true,
|
||||
AccessControlMaxAge: 3600,
|
||||
})
|
||||
|
||||
auth := &AuthMiddleware{
|
||||
Realm: "openaccounting",
|
||||
}
|
||||
|
||||
version := &VersionMiddleware{}
|
||||
|
||||
app.Use(auth)
|
||||
app.Use(version)
|
||||
|
||||
router, err := GetRouter(auth)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
app.SetApp(router)
|
||||
|
||||
return app, nil
|
||||
}
|
||||
188
core/api/apikey.go
Normal file
188
core/api/apikey.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/model"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/**
|
||||
* @api {get} /apikeys Get API keys
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetApiKeys
|
||||
* @apiGroup ApiKey
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccess {String} id Id of the ApiKey.
|
||||
* @apiSuccess {Date} inserted Date ApiKey was created
|
||||
* @apiSuccess {Date} updated Date Last activity for the ApiKey
|
||||
* @apiSuccess {String} userId Id of the User
|
||||
* @apiSuccess {String} label Label
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "userId": "22222222222222222222222222222222",
|
||||
* "label": "Shopping Cart"
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetApiKeys(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
|
||||
keys, err := model.Instance.GetApiKeys(user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(keys)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /apikeys Create a new API key
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PostApiKey
|
||||
* @apiGroup ApiKey
|
||||
*
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
*
|
||||
* @apiParam {String} id 32 character hex string
|
||||
* @apiParam {String} label Label
|
||||
*
|
||||
* @apiSuccess {String} id Id of the ApiKey.
|
||||
* @apiSuccess {Date} inserted Date ApiKey was created
|
||||
* @apiSuccess {Date} updated Date Last activity for the ApiKey
|
||||
* @apiSuccess {String} userId Id of the User
|
||||
* @apiSuccess {String} label Label
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "userId": "22222222222222222222222222222222",
|
||||
* "label": "Shopping Cart"
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PostApiKey(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
key := &types.ApiKey{}
|
||||
|
||||
err := r.DecodeJsonPayload(key)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
key.UserId = user.Id
|
||||
|
||||
err = model.Instance.CreateApiKey(key)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {put} /apikeys Modify an API key
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PutApiKey
|
||||
* @apiGroup ApiKey
|
||||
*
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
*
|
||||
* @apiParam {String} id 32 character hex string
|
||||
* @apiParam {String} label Label
|
||||
*
|
||||
* @apiSuccess {String} id Id of the ApiKey.
|
||||
* @apiSuccess {Date} inserted Date ApiKey was created
|
||||
* @apiSuccess {Date} updated Date Last activity for the ApiKey
|
||||
* @apiSuccess {String} userId Id of the User
|
||||
* @apiSuccess {String} label Label
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "userId": "22222222222222222222222222222222",
|
||||
* "label": "Shopping Cart"
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PutApiKey(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
key := &types.ApiKey{}
|
||||
keyId := r.PathParam("apiKeyId")
|
||||
|
||||
err := r.DecodeJsonPayload(key)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
key.Id = keyId
|
||||
key.UserId = user.Id
|
||||
|
||||
err = model.Instance.UpdateApiKey(key)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {delete} /apikeys/:apiKeyId Delete an API key
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName DeleteApiKey
|
||||
* @apiGroup ApiKey
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func DeleteApiKey(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
id := r.PathParam("apiKeyId")
|
||||
|
||||
err := model.Instance.DeleteApiKey(id, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
93
core/api/auth.go
Normal file
93
core/api/auth.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/auth"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type AuthMiddleware struct {
|
||||
|
||||
// Realm name to display to the user. Required.
|
||||
Realm string
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes AuthMiddleware implement the Middleware interface.
|
||||
func (mw *AuthMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.HandlerFunc {
|
||||
|
||||
if mw.Realm == "" {
|
||||
log.Fatal("Realm is required")
|
||||
}
|
||||
|
||||
return func(writer rest.ResponseWriter, request *rest.Request) {
|
||||
|
||||
authHeader := request.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
request.Env["USER"] = nil
|
||||
handler(writer, request)
|
||||
return
|
||||
}
|
||||
|
||||
emailOrKey, password, err := mw.decodeBasicAuthHeader(authHeader)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(writer, "Invalid authentication", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// authenticate via session, apikey or user
|
||||
user, err := auth.Instance.Authenticate(emailOrKey, password)
|
||||
|
||||
if err == nil {
|
||||
request.Env["USER"] = user
|
||||
handler(writer, request)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Unauthorized " + emailOrKey)
|
||||
|
||||
mw.unauthorized(writer)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (mw *AuthMiddleware) unauthorized(writer rest.ResponseWriter) {
|
||||
writer.Header().Set("WWW-Authenticate", "Basic realm="+mw.Realm)
|
||||
rest.Error(writer, "Not Authorized", http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func (mw *AuthMiddleware) decodeBasicAuthHeader(header string) (user string, password string, err error) {
|
||||
|
||||
parts := strings.SplitN(header, " ", 2)
|
||||
if !(len(parts) == 2 && parts[0] == "Basic") {
|
||||
return "", "", errors.New("Invalid authentication")
|
||||
}
|
||||
|
||||
decoded, err := base64.StdEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return "", "", errors.New("Invalid base64")
|
||||
}
|
||||
|
||||
creds := strings.SplitN(string(decoded), ":", 2)
|
||||
if len(creds) != 2 {
|
||||
return "", "", errors.New("Invalid authentication")
|
||||
}
|
||||
|
||||
return creds[0], creds[1], nil
|
||||
}
|
||||
|
||||
func (mw *AuthMiddleware) RequireAuth(handler rest.HandlerFunc) rest.HandlerFunc {
|
||||
return func(writer rest.ResponseWriter, request *rest.Request) {
|
||||
|
||||
if request.Env["USER"] == nil {
|
||||
mw.unauthorized(writer)
|
||||
return
|
||||
}
|
||||
|
||||
handler(writer, request)
|
||||
}
|
||||
}
|
||||
89
core/api/logger.go
Normal file
89
core/api/logger.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LoggerMiddleware struct {
|
||||
Logger *log.Logger
|
||||
}
|
||||
|
||||
func (mw *LoggerMiddleware) MiddlewareFunc(h rest.HandlerFunc) rest.HandlerFunc {
|
||||
|
||||
// set the default Logger
|
||||
if mw.Logger == nil {
|
||||
mw.Logger = log.New(os.Stderr, "", 0)
|
||||
}
|
||||
|
||||
return func(w rest.ResponseWriter, r *rest.Request) {
|
||||
h(w, r)
|
||||
|
||||
message := getIp(r)
|
||||
|
||||
message = message + " " + getUser(r)
|
||||
message = message + " " + getTime(r)
|
||||
message = message + " " + getRequest(r)
|
||||
message = message + " " + getStatus(r)
|
||||
message = message + " " + getBytes(r)
|
||||
message = message + " " + getUserAgent(r)
|
||||
|
||||
mw.Logger.Print(message)
|
||||
}
|
||||
}
|
||||
|
||||
func getIp(r *rest.Request) string {
|
||||
remoteAddr := r.RemoteAddr
|
||||
if remoteAddr != "" {
|
||||
if ip, _, err := net.SplitHostPort(remoteAddr); err == nil {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getUser(r *rest.Request) string {
|
||||
if r.Env["USER"] != nil {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
return user.Email
|
||||
}
|
||||
|
||||
return "-"
|
||||
}
|
||||
|
||||
func getTime(r *rest.Request) string {
|
||||
if r.Env["START_TIME"] != nil {
|
||||
return r.Env["START_TIME"].(*time.Time).Format("02/Jan/2006:15:04:05 -0700")
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
func getRequest(r *rest.Request) string {
|
||||
return r.Method + " " + r.URL.RequestURI()
|
||||
}
|
||||
|
||||
func getStatus(r *rest.Request) string {
|
||||
if r.Env["STATUS_CODE"] != nil {
|
||||
return strconv.Itoa(r.Env["STATUS_CODE"].(int))
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
func getBytes(r *rest.Request) string {
|
||||
if r.Env["BYTES_WRITTEN"] != nil {
|
||||
return strconv.FormatInt(r.Env["BYTES_WRITTEN"].(int64), 10)
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
|
||||
func getUserAgent(r *rest.Request) string {
|
||||
if r.UserAgent() != "" {
|
||||
return r.UserAgent()
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
394
core/api/org.go
Normal file
394
core/api/org.go
Normal file
@@ -0,0 +1,394 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/model"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/**
|
||||
* @api {get} /org/:orgId Get Org by id
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetOrg
|
||||
* @apiGroup Org
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Org.
|
||||
* @apiSuccess {Date} inserted Date Org was created
|
||||
* @apiSuccess {Date} updated Date Org was updated
|
||||
* @apiSuccess {String} name Name of the Org.
|
||||
* @apiSuccess {String} currency Three letter currency code.
|
||||
* @apiSuccess {Number} precision How many digits the currency goes out to.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "name": "MyOrg",
|
||||
* "currency": "USD",
|
||||
* "precision": 2,
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetOrg(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
org, err := model.Instance.GetOrg(orgId, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&org)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /orgs Get a User's Orgs
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetOrgs
|
||||
* @apiGroup Org
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Org.
|
||||
* @apiSuccess {Date} inserted Date Org was created
|
||||
* @apiSuccess {Date} updated Date Org was updated
|
||||
* @apiSuccess {String} name Name of the Org.
|
||||
* @apiSuccess {String} currency Three letter currency code.
|
||||
* @apiSuccess {Number} precision How many digits the currency goes out to.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "name": "MyOrg",
|
||||
* "currency": "USD",
|
||||
* "precision": 2,
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetOrgs(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
|
||||
orgs, err := model.Instance.GetOrgs(user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&orgs)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /orgs Create a new Org
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PostOrg
|
||||
* @apiGroup Org
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} id Id 32 character hex string
|
||||
* @apiParam {String} name Name of the Org.
|
||||
* @apiParam {String} currency Three letter currency code.
|
||||
* @apiParam {Number} precision How many digits the currency goes out to.
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Org.
|
||||
* @apiSuccess {Date} inserted Date Org was created
|
||||
* @apiSuccess {Date} updated Date Org was updated
|
||||
* @apiSuccess {String} name Name of the Org.
|
||||
* @apiSuccess {String} currency Three letter currency code.
|
||||
* @apiSuccess {Number} precision How many digits the currency goes out to.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "name": "MyOrg",
|
||||
* "currency": "USD",
|
||||
* "precision": 2,
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PostOrg(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
org := types.Org{Precision: 2}
|
||||
err := r.DecodeJsonPayload(&org)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = model.Instance.CreateOrg(&org, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&org)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {put} /orgs/:orgId Modify an Org
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PutOrg
|
||||
* @apiGroup Org
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} name Name of the Org.
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Org.
|
||||
* @apiSuccess {Date} inserted Date Org was created
|
||||
* @apiSuccess {Date} updated Date Org was updated
|
||||
* @apiSuccess {String} name Name of the Org.
|
||||
* @apiSuccess {String} currency Three letter currency code.
|
||||
* @apiSuccess {Number} precision How many digits the currency goes out to.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "name": "MyOrg",
|
||||
* "currency": "USD",
|
||||
* "precision": 2,
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PutOrg(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
org := types.Org{}
|
||||
err := r.DecodeJsonPayload(&org)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
org.Id = orgId
|
||||
|
||||
err = model.Instance.UpdateOrg(&org, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&org)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /orgs/:orgId/invites Invite a user to an Org
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PostInvite
|
||||
* @apiGroup Org
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} email Email address of user
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Invite
|
||||
* @apiSuccess {orgId} id Id of the Org
|
||||
* @apiSuccess {Date} inserted Date Invite was created
|
||||
* @apiSuccess {Date} updated Date Invite was updated/accepted
|
||||
* @apiSuccess {String} email Email address of user
|
||||
* @apiSuccess {String} accepted true if user has accepted
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "a1b2c3d4",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "email": "johndoe@email.com",
|
||||
* "accepted": false
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PostInvite(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
invite := types.Invite{}
|
||||
err := r.DecodeJsonPayload(&invite)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
invite.OrgId = orgId
|
||||
|
||||
err = model.Instance.CreateInvite(&invite, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&invite)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {put} /orgs/:orgId/invites/:inviteId Accept an invitation
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PutInvite
|
||||
* @apiGroup Org
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} accepted true
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Invite
|
||||
* @apiSuccess {orgId} id Id of the Org
|
||||
* @apiSuccess {Date} inserted Date Invite was created
|
||||
* @apiSuccess {Date} updated Date Invite was updated/accepted
|
||||
* @apiSuccess {String} email Email address of user
|
||||
* @apiSuccess {String} accepted true if user has accepted
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "a1b2c3d4",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "email": "johndoe@email.com",
|
||||
* "accepted": true
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PutInvite(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
//orgId := r.PathParam("orgId")
|
||||
inviteId := r.PathParam("inviteId")
|
||||
|
||||
invite := types.Invite{}
|
||||
err := r.DecodeJsonPayload(&invite)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
invite.Id = inviteId
|
||||
|
||||
err = model.Instance.AcceptInvite(&invite, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&invite)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /orgs/:orgId/invites Get Org invites
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetInvites
|
||||
* @apiGroup Org
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Invite
|
||||
* @apiSuccess {orgId} id Id of the Org
|
||||
* @apiSuccess {Date} inserted Date Invite was created
|
||||
* @apiSuccess {Date} updated Date Invite was updated/accepted
|
||||
* @apiSuccess {String} email Email address of user
|
||||
* @apiSuccess {String} accepted true if user has accepted
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "id": "a1b2c3d4",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "email": "johndoe@email.com",
|
||||
* "accepted": true
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetInvites(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
invites, err := model.Instance.GetInvites(orgId, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&invites)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {delete} /orgs/:orgId/invites/:inviteId Delete Invite
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName DeleteInvite
|
||||
* @apiGroup Org
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func DeleteInvite(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
inviteId := r.PathParam("inviteId")
|
||||
|
||||
err := model.Instance.DeleteInvite(inviteId, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
183
core/api/price.go
Normal file
183
core/api/price.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/model"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
/**
|
||||
* @api {get} /org/:orgId/prices Get prices nearest in time or by currency
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetPrices
|
||||
* @apiGroup Price
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {Number} nearestDate Milliseconds since epoch
|
||||
* @apiParam {String} currency Currency code
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Price.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {String} currency Currency code.
|
||||
* @apiSuccess {Date} date Date of the Price.
|
||||
* @apiSuccess {Date} inserted Date when Price was posted.
|
||||
* @apiSuccess {Date} updated Date when Price was updated.
|
||||
* @apiSuccess {Number} price Price of currency measured in native Org currency.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "currency": "EUR",
|
||||
* "date": "2018-09-11T18:05:04.420Z",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "price": 1.16
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetPrices(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
// TODO how do we make date an optional parameter
|
||||
// instead of resorting to this hack?
|
||||
nearestDate := time.Date(2100, time.January, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
nearestDateParam := r.URL.Query().Get("nearestDate")
|
||||
currencyParam := r.URL.Query().Get("currency")
|
||||
|
||||
// If currency was specified, get all prices for that currency
|
||||
if currencyParam != "" {
|
||||
prices, err := model.Instance.GetPricesByCurrency(orgId, currencyParam, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(prices)
|
||||
return
|
||||
}
|
||||
|
||||
if nearestDateParam != "" {
|
||||
nearestDateParamNumeric, err := strconv.ParseInt(nearestDateParam, 10, 64)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, "invalid date", 400)
|
||||
return
|
||||
}
|
||||
nearestDate = time.Unix(0, nearestDateParamNumeric*1000000)
|
||||
}
|
||||
|
||||
// Get prices nearest in time
|
||||
prices, err := model.Instance.GetPricesNearestInTime(orgId, nearestDate, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(prices)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /orgs/:orgId/prices Create a new Price
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PostPrice
|
||||
* @apiGroup Price
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} id Id 32 character hex string.
|
||||
* @apiParam {String} orgId Id of the Org.
|
||||
* @apiParam {String} currency Currency code.
|
||||
* @apiParam {Date} date Date of the Price.
|
||||
* @apiParam {Number} price Price of currency measured in native Org currency.
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Price.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {String} currency Currency code.
|
||||
* @apiSuccess {Date} date Date of the Price.
|
||||
* @apiSuccess {Date} inserted Date when Price was posted.
|
||||
* @apiSuccess {Date} updated Date when Price was updated.
|
||||
* @apiSuccess {Number} price Price of currency measured in native Org currency.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "currency": "EUR",
|
||||
* "date": "2018-09-11T18:05:04.420Z",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "price": 1.16
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PostPrice(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
price := types.Price{}
|
||||
|
||||
err := r.DecodeJsonPayload(&price)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
price.OrgId = orgId
|
||||
err = model.Instance.CreatePrice(&price, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&price)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {delete} /orgs/:orgId/prices/:priceId Delete a Price
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName DeletePrice
|
||||
* @apiGroup Price
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func DeletePrice(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
priceId := r.PathParam("priceId")
|
||||
|
||||
err := model.Instance.DeletePrice(priceId, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
48
core/api/routes.go
Normal file
48
core/api/routes.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/ws"
|
||||
)
|
||||
|
||||
func GetRouter(auth *AuthMiddleware) (rest.App, error) {
|
||||
return rest.MakeRouter(
|
||||
rest.Get("/api/user", auth.RequireAuth(GetUser)),
|
||||
rest.Put("/api/user", PutUser),
|
||||
rest.Post("/api/user/verify", VerifyUser),
|
||||
rest.Post("/api/user/reset-password", ResetPassword),
|
||||
rest.Post("/api/users", PostUser),
|
||||
rest.Post("/api/orgs", auth.RequireAuth(PostOrg)),
|
||||
rest.Get("/api/orgs", auth.RequireAuth(GetOrgs)),
|
||||
rest.Get("/api/orgs/:orgId", auth.RequireAuth(GetOrg)),
|
||||
rest.Put("/api/orgs/:orgId", auth.RequireAuth(PutOrg)),
|
||||
rest.Get("/api/orgs/:orgId/ledgers", auth.RequireAuth(GetOrgAccounts)),
|
||||
rest.Post("/api/orgs/:orgId/ledgers", auth.RequireAuth(PostAccount)),
|
||||
rest.Put("/api/orgs/:orgId/ledgers/:accountId", auth.RequireAuth(PutAccount)),
|
||||
rest.Delete("/api/orgs/:orgId/ledgers/:accountId", auth.RequireAuth(DeleteAccount)),
|
||||
rest.Get("/api/orgs/:orgId/ledgers/:accountId/transactions", auth.RequireAuth(GetTransactionsByAccount)),
|
||||
rest.Get("/api/orgs/:orgId/accounts", auth.RequireAuth(GetOrgAccounts)),
|
||||
rest.Post("/api/orgs/:orgId/accounts", auth.RequireAuth(PostAccount)),
|
||||
rest.Put("/api/orgs/:orgId/accounts/:accountId", auth.RequireAuth(PutAccount)),
|
||||
rest.Delete("/api/orgs/:orgId/accounts/:accountId", auth.RequireAuth(DeleteAccount)),
|
||||
rest.Get("/api/orgs/:orgId/accounts/:accountId/transactions", auth.RequireAuth(GetTransactionsByAccount)),
|
||||
rest.Get("/api/orgs/:orgId/transactions", auth.RequireAuth(GetTransactionsByOrg)),
|
||||
rest.Post("/api/orgs/:orgId/transactions", auth.RequireAuth(PostTransaction)),
|
||||
rest.Put("/api/orgs/:orgId/transactions/:transactionId", auth.RequireAuth(PutTransaction)),
|
||||
rest.Delete("/api/orgs/:orgId/transactions/:transactionId", auth.RequireAuth(DeleteTransaction)),
|
||||
rest.Get("/api/orgs/:orgId/prices", auth.RequireAuth(GetPrices)),
|
||||
rest.Post("/api/orgs/:orgId/prices", auth.RequireAuth(PostPrice)),
|
||||
rest.Delete("/api/orgs/:orgId/prices/:priceId", auth.RequireAuth(DeletePrice)),
|
||||
rest.Get("/ws", ws.Handler),
|
||||
rest.Post("/api/sessions", auth.RequireAuth(PostSession)),
|
||||
rest.Delete("/api/sessions/:sessionId", auth.RequireAuth(DeleteSession)),
|
||||
rest.Get("/api/apikeys", auth.RequireAuth(GetApiKeys)),
|
||||
rest.Post("/api/apikeys", auth.RequireAuth(PostApiKey)),
|
||||
rest.Put("/api/apikeys/:apiKeyId", auth.RequireAuth(PutApiKey)),
|
||||
rest.Delete("/api/apikeys/:apiKeyId", auth.RequireAuth(DeleteApiKey)),
|
||||
rest.Get("/api/orgs/:orgId/invites", auth.RequireAuth(GetInvites)),
|
||||
rest.Post("/api/orgs/:orgId/invites", auth.RequireAuth(PostInvite)),
|
||||
rest.Put("/api/orgs/:orgId/invites/:inviteId", auth.RequireAuth(PutInvite)),
|
||||
rest.Delete("/api/orgs/:orgId/invites/:inviteId", auth.RequireAuth(DeleteInvite)),
|
||||
)
|
||||
}
|
||||
87
core/api/session.go
Normal file
87
core/api/session.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/model"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/**
|
||||
* @api {post} /sessions Create a new Session
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PostSession
|
||||
* @apiGroup Session
|
||||
*
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
*
|
||||
* @apiParam {String} id 32 character hex string
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Session.
|
||||
* @apiSuccess {Date} inserted Date Session was created
|
||||
* @apiSuccess {Date} updated Date Last activity for the Session
|
||||
* @apiSuccess {String} userId Id of the User
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "userId": "22222222222222222222222222222222"
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PostSession(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
session := &types.Session{}
|
||||
|
||||
err := r.DecodeJsonPayload(session)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
session.UserId = user.Id
|
||||
|
||||
err = model.Instance.CreateSession(session)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(session)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {delete} /sessions/:sessionId Log out of a Session
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName DeleteSession
|
||||
* @apiGroup Session
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func DeleteSession(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
sessionId := r.PathParam("sessionId")
|
||||
|
||||
err := model.Instance.DeleteSession(sessionId, user.Id)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
362
core/api/transaction.go
Normal file
362
core/api/transaction.go
Normal file
@@ -0,0 +1,362 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/model"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
/**
|
||||
* @api {get} /orgs/:orgId/accounts/:accountId/transactions Get Transactions by Account Id
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetAccountTransactions
|
||||
* @apiGroup Transaction
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Transaction.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {String} userId Id of the User who created the Transaction.
|
||||
* @apiSuccess {Date} date Date of the Transaction
|
||||
* @apiSuccess {Date} inserted Date Transaction was created
|
||||
* @apiSuccess {Date} updated Date Transaction was updated
|
||||
* @apiSuccess {String} description Description of Transaction
|
||||
* @apiSuccess {String} data Extra data field
|
||||
* @apiSuccess {Object[]} splits Array of Transaction Splits
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "userId": "11111111111111111111111111111111",
|
||||
* "date": "2018-06-08T20:12:29.720Z",
|
||||
* "inserted": "2018-06-08T20:12:29.720Z",
|
||||
* "updated": "2018-06-08T20:12:29.720Z",
|
||||
* "description": "Treat friend to lunch",
|
||||
* "data:": "{\"key\": \"value\"}",
|
||||
* "splits": [
|
||||
* {
|
||||
* "accountId": "11111111111111111111111111111111",
|
||||
* "amount": -2000,
|
||||
* "nativeAmount": -2000
|
||||
* },
|
||||
* {
|
||||
* "accountId": "22222222222222222222222222222222",
|
||||
* "amount": 1000,
|
||||
* "nativeAmount": 1000
|
||||
* },
|
||||
* {
|
||||
* "accountId": "33333333333333333333333333333333",
|
||||
* "amount": 1000,
|
||||
* "nativeAmount": 1000
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetTransactionsByAccount(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
accountId := r.PathParam("accountId")
|
||||
|
||||
queryOptions, err := types.QueryOptionsFromURLQuery(r.URL.Query())
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, "invalid query options", 400)
|
||||
return
|
||||
}
|
||||
|
||||
sTxs, err := model.Instance.GetTransactionsByAccount(orgId, user.Id, accountId, queryOptions)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&sTxs)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /orgs/:orgId/transactions Get Transactions by Org Id
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetOrgTransactions
|
||||
* @apiGroup Transaction
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Transaction.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {String} userId Id of the User who created the Transaction.
|
||||
* @apiSuccess {Date} date Date of the Transaction
|
||||
* @apiSuccess {Date} inserted Date Transaction was created
|
||||
* @apiSuccess {Date} updated Date Transaction was updated
|
||||
* @apiSuccess {String} description Description of Transaction
|
||||
* @apiSuccess {String} data Extra data field
|
||||
* @apiSuccess {Object[]} splits Array of Transaction Splits
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "userId": "11111111111111111111111111111111",
|
||||
* "date": "2018-06-08T20:12:29.720Z",
|
||||
* "inserted": "2018-06-08T20:12:29.720Z",
|
||||
* "updated": "2018-06-08T20:12:29.720Z",
|
||||
* "description": "Treat friend to lunch",
|
||||
* "data:": "{\"key\": \"value\"}",
|
||||
* "splits": [
|
||||
* {
|
||||
* "accountId": "11111111111111111111111111111111",
|
||||
* "amount": -2000,
|
||||
* "nativeAmount": -2000
|
||||
* },
|
||||
* {
|
||||
* "accountId": "22222222222222222222222222222222",
|
||||
* "amount": 1000,
|
||||
* "nativeAmount": 1000
|
||||
* },
|
||||
* {
|
||||
* "accountId": "33333333333333333333333333333333",
|
||||
* "amount": 1000,
|
||||
* "nativeAmount": 1000
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetTransactionsByOrg(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
queryOptions, err := types.QueryOptionsFromURLQuery(r.URL.Query())
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, "invalid query options", 400)
|
||||
return
|
||||
}
|
||||
|
||||
sTxs, err := model.Instance.GetTransactionsByOrg(orgId, user.Id, queryOptions)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(&sTxs)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /orgs/:orgId/transactions Create a new Transaction
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PostTransaction
|
||||
* @apiGroup Transaction
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} id Id 32 character hex string
|
||||
* @apiParam {Date} date Date of the Transaction
|
||||
* @apiParam {String} description Description of Transaction
|
||||
* @apiParam {String} data Extra data field
|
||||
* @apiParam {Object[]} splits Array of Transaction Splits. nativeAmounts must add up to 0.
|
||||
* @apiParam {String} splits.accountId Id of Account
|
||||
* @apiParam {Number} splits.amount Amount of split in Account currency
|
||||
* @apiParam {Number} splits.nativeAmount Amount of split in Org currency
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Transaction.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {String} userId Id of the User who created the Transaction.
|
||||
* @apiSuccess {Date} date Date of the Transaction
|
||||
* @apiSuccess {Date} inserted Date Transaction was created
|
||||
* @apiSuccess {Date} updated Date Transaction was updated
|
||||
* @apiSuccess {String} description Description of Transaction
|
||||
* @apiSuccess {String} data Extra data field
|
||||
* @apiSuccess {Object[]} splits Array of Transaction Splits
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "userId": "11111111111111111111111111111111",
|
||||
* "date": "2018-06-08T20:12:29.720Z",
|
||||
* "inserted": "2018-06-08T20:12:29.720Z",
|
||||
* "updated": "2018-06-08T20:12:29.720Z",
|
||||
* "description": "Treat friend to lunch",
|
||||
* "data:": "{\"key\": \"value\"}",
|
||||
* "splits": [
|
||||
* {
|
||||
* "accountId": "11111111111111111111111111111111",
|
||||
* "amount": -2000,
|
||||
* "nativeAmount": -2000
|
||||
* },
|
||||
* {
|
||||
* "accountId": "22222222222222222222222222222222",
|
||||
* "amount": 1000,
|
||||
* "nativeAmount": 1000
|
||||
* },
|
||||
* {
|
||||
* "accountId": "33333333333333333333333333333333",
|
||||
* "amount": 1000,
|
||||
* "nativeAmount": 1000
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PostTransaction(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
|
||||
sTx := types.Transaction{}
|
||||
err := r.DecodeJsonPayload(&sTx)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sTx.OrgId = orgId
|
||||
sTx.UserId = user.Id
|
||||
|
||||
err = model.Instance.CreateTransaction(&sTx)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(sTx)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {put} /orgs/:orgId/transactions/:transactionId Modify a Transaction
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PutTransaction
|
||||
* @apiGroup Transaction
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} id 32 character hex string
|
||||
* @apiParam {Date} date Date of the Transaction
|
||||
* @apiParam {String} description Description of Transaction
|
||||
* @apiParam {String} data Extra data field
|
||||
* @apiParam {Object[]} splits Array of Transaction Splits. nativeAmounts must add up to 0.
|
||||
* @apiParam {String} splits.accountId Id of Account
|
||||
* @apiParam {Number} splits.amount Amount of split in Account currency
|
||||
* @apiParam {Number} splits.nativeAmount Amount of split in Org currency
|
||||
*
|
||||
* @apiSuccess {String} id Id of the Transaction.
|
||||
* @apiSuccess {String} orgId Id of the Org.
|
||||
* @apiSuccess {String} userId Id of the User who created the Transaction.
|
||||
* @apiSuccess {Date} date Date of the Transaction
|
||||
* @apiSuccess {Date} inserted Date Transaction was created
|
||||
* @apiSuccess {Date} updated Date Transaction was updated
|
||||
* @apiSuccess {String} description Description of Transaction
|
||||
* @apiSuccess {String} data Extra data field
|
||||
* @apiSuccess {Object[]} splits Array of Transaction Splits
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "orgId": "11111111111111111111111111111111",
|
||||
* "userId": "11111111111111111111111111111111",
|
||||
* "date": "2018-06-08T20:12:29.720Z",
|
||||
* "inserted": "2018-06-08T20:12:29.720Z",
|
||||
* "updated": "2018-06-08T20:12:29.720Z",
|
||||
* "description": "Treat friend to lunch",
|
||||
* "data:": "{\"key\": \"value\"}",
|
||||
* "splits": [
|
||||
* {
|
||||
* "accountId": "11111111111111111111111111111111",
|
||||
* "amount": -2000,
|
||||
* "nativeAmount": -2000
|
||||
* },
|
||||
* {
|
||||
* "accountId": "22222222222222222222222222222222",
|
||||
* "amount": 1000,
|
||||
* "nativeAmount": 1000
|
||||
* },
|
||||
* {
|
||||
* "accountId": "33333333333333333333333333333333",
|
||||
* "amount": 1000,
|
||||
* "nativeAmount": 1000
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PutTransaction(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
transactionId := r.PathParam("transactionId")
|
||||
|
||||
sTx := types.Transaction{}
|
||||
err := r.DecodeJsonPayload(&sTx)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
sTx.OrgId = orgId
|
||||
sTx.UserId = user.Id
|
||||
|
||||
err = model.Instance.UpdateTransaction(transactionId, &sTx)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(sTx)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {delete} /orgs/:orgId/transactions/:transactionId Delete a Transaction
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName DeleteTransaction
|
||||
* @apiGroup Transaction
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func DeleteTransaction(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
orgId := r.PathParam("orgId")
|
||||
transactionId := r.PathParam("transactionId")
|
||||
|
||||
err := model.Instance.DeleteTransaction(transactionId, user.Id, orgId)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
265
core/api/user.go
Normal file
265
core/api/user.go
Normal file
@@ -0,0 +1,265 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"github.com/openaccounting/oa-server/core/model"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type VerifyUserParams struct {
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
type ConfirmResetPasswordParams struct {
|
||||
Code string `json:"code"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type ResetPasswordParams struct {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /user Get Authenticated User
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName GetUser
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiSuccess {String} id Id of the User.
|
||||
* @apiSuccess {Date} inserted Date User was created
|
||||
* @apiSuccess {Date} updated Date User was updated
|
||||
* @apiSuccess {String} firstName First name of the User.
|
||||
* @apiSuccess {String} lastName Last name of the User.
|
||||
* @apiSuccess {String} email Email of the User.
|
||||
* @apiSuccess {Boolean} agreeToTerms Agree to terms
|
||||
* @apiSuccess {Boolean} emailVerified True if email has been verified.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "firstName": "John",
|
||||
* "lastName": "Doe",
|
||||
* "email": "johndoe@email.com",
|
||||
* "agreeToTerms": true,
|
||||
* "emailVerified": true
|
||||
* }
|
||||
*
|
||||
* @apiUse NotAuthorizedError
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func GetUser(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := r.Env["USER"].(*types.User)
|
||||
|
||||
w.WriteJson(&user)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /users Create a new User
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PostUser
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} id 32 character hex string
|
||||
* @apiParam {String} firstName First name of the User.
|
||||
* @apiParam {String} lastName Last name of the User.
|
||||
* @apiParam {String} email Email of the User.
|
||||
* @apiParam {String} password Password of the User.
|
||||
* @apiParam {Boolean} agreeToTerms True if you agree to terms
|
||||
*
|
||||
* @apiSuccess {String} id Id of the User.
|
||||
* @apiSuccess {Date} inserted Date User was created
|
||||
* @apiSuccess {Date} updated Date User was updated
|
||||
* @apiSuccess {String} firstName First name of the User.
|
||||
* @apiSuccess {String} lastName Last name of the User.
|
||||
* @apiSuccess {String} email Email of the User.
|
||||
* @apiSuccess {Boolean} emailVerified True if email has been verified.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "firstName": "John",
|
||||
* "lastName": "Doe",
|
||||
* "email": "johndoe@email.com",
|
||||
* "agreeToTerms": true,
|
||||
* "emailVerified": true
|
||||
* }
|
||||
*
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PostUser(w rest.ResponseWriter, r *rest.Request) {
|
||||
user := &types.User{}
|
||||
err := r.DecodeJsonPayload(user)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = model.Instance.CreateUser(user)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(user)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {put} /user Modify User
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName PutUser
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiHeader {String} Authorization HTTP Basic Auth
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} password New password
|
||||
* @apiParam {String} code Password reset code. (Instead of Authorization header)
|
||||
*
|
||||
* @apiSuccess {String} id Id of the User.
|
||||
* @apiSuccess {Date} inserted Date User was created
|
||||
* @apiSuccess {Date} updated Date User was updated
|
||||
* @apiSuccess {String} firstName First name of the User.
|
||||
* @apiSuccess {String} lastName Last name of the User.
|
||||
* @apiSuccess {String} email Email of the User.
|
||||
* @apiSuccess {Boolean} emailVerified True if email has been verified.
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "id": "11111111111111111111111111111111",
|
||||
* "inserted": "2018-09-11T18:05:04.420Z",
|
||||
* "updated": "2018-09-11T18:05:04.420Z",
|
||||
* "firstName": "John",
|
||||
* "lastName": "Doe",
|
||||
* "email": "johndoe@email.com",
|
||||
* "agreeToTerms": true,
|
||||
* "emailVerified": true
|
||||
* }
|
||||
*
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func PutUser(w rest.ResponseWriter, r *rest.Request) {
|
||||
if r.Env["USER"] == nil {
|
||||
// password reset
|
||||
params := &ConfirmResetPasswordParams{}
|
||||
err := r.DecodeJsonPayload(params)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := model.Instance.ConfirmResetPassword(params.Password, params.Code)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(user)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise it's an authenticated PUT
|
||||
|
||||
user := r.Env["USER"].(*types.User)
|
||||
|
||||
newUser := &types.User{}
|
||||
err := r.DecodeJsonPayload(newUser)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
user.Password = newUser.Password
|
||||
|
||||
err = model.Instance.UpdateUser(user)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteJson(user)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /user/verify Verify user email address
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName VerifyUser
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} code Email verification code
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
*
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func VerifyUser(w rest.ResponseWriter, r *rest.Request) {
|
||||
params := &VerifyUserParams{}
|
||||
|
||||
err := r.DecodeJsonPayload(params)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = model.Instance.VerifyUser(params.Code)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /user/reset-password Send reset password email
|
||||
* @apiVersion 1.0.0
|
||||
* @apiName ResetPassword
|
||||
* @apiGroup User
|
||||
*
|
||||
* @apiHeader {String} Accept-Version ^1.0.0 semver versioning
|
||||
*
|
||||
* @apiParam {String} email Email address for user
|
||||
*
|
||||
* @apiSuccessExample Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
*
|
||||
* @apiUse InternalServerError
|
||||
*/
|
||||
func ResetPassword(w rest.ResponseWriter, r *rest.Request) {
|
||||
params := &ResetPasswordParams{}
|
||||
|
||||
err := r.DecodeJsonPayload(params)
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err = model.Instance.ResetPassword(params.Email)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
48
core/api/version.go
Normal file
48
core/api/version.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/ant0ine/go-json-rest/rest"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type VersionMiddleware struct {
|
||||
}
|
||||
|
||||
// MiddlewareFunc makes AuthMiddleware implement the Middleware interface.
|
||||
func (mw *VersionMiddleware) MiddlewareFunc(handler rest.HandlerFunc) rest.HandlerFunc {
|
||||
return func(writer rest.ResponseWriter, request *rest.Request) {
|
||||
version := request.Header.Get("Accept-Version")
|
||||
|
||||
// Don't require version header for websockets
|
||||
if request.URL.String() == "/ws" {
|
||||
handler(writer, request)
|
||||
return
|
||||
}
|
||||
|
||||
if version == "" {
|
||||
rest.Error(writer, "Accept-Version header required", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
constraint, err := semver.NewConstraint(version)
|
||||
|
||||
if err != nil {
|
||||
rest.Error(writer, "Invalid version", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
serverVersion, _ := semver.NewVersion("1.0.0")
|
||||
// Pre-release versions
|
||||
compatVersion, _ := semver.NewVersion("0.1.8")
|
||||
|
||||
versionMatch := constraint.Check(serverVersion)
|
||||
compatMatch := constraint.Check(compatVersion)
|
||||
|
||||
if versionMatch == false && compatMatch == false {
|
||||
rest.Error(writer, "Invalid version", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
handler(writer, request)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user