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:
391
core/model/db/account.go
Normal file
391
core/model/db/account.go
Normal file
@@ -0,0 +1,391 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"github.com/openaccounting/oa-server/core/util"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const emptyAccountId = "00000000000000000000000000000000"
|
||||
|
||||
type AccountInterface interface {
|
||||
InsertAccount(account *types.Account) error
|
||||
UpdateAccount(account *types.Account) error
|
||||
GetAccount(string) (*types.Account, error)
|
||||
GetAccountsByOrgId(orgId string) ([]*types.Account, error)
|
||||
GetPermissionedAccountIds(string, string, string) ([]string, error)
|
||||
GetSplitCountByAccountId(id string) (int64, error)
|
||||
GetChildCountByAccountId(id string) (int64, error)
|
||||
DeleteAccount(id string) error
|
||||
AddBalances([]*types.Account, time.Time) error
|
||||
AddNativeBalancesCost([]*types.Account, time.Time) error
|
||||
AddNativeBalancesNearestInTime([]*types.Account, time.Time) error
|
||||
AddBalance(*types.Account, time.Time) error
|
||||
AddNativeBalanceCost(*types.Account, time.Time) error
|
||||
AddNativeBalanceNearestInTime(*types.Account, time.Time) error
|
||||
GetRootAccount(string) (*types.Account, error)
|
||||
}
|
||||
|
||||
func (db *DB) InsertAccount(account *types.Account) error {
|
||||
account.Inserted = time.Now()
|
||||
account.Updated = account.Inserted
|
||||
|
||||
query := "INSERT INTO account(id,orgId,inserted,updated,name,parent,currency,`precision`,debitBalance) VALUES(UNHEX(?),UNHEX(?),?,?,?,UNHEX(?),?,?,?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
account.Id,
|
||||
account.OrgId,
|
||||
util.TimeToMs(account.Inserted),
|
||||
util.TimeToMs(account.Updated),
|
||||
account.Name,
|
||||
account.Parent,
|
||||
account.Currency,
|
||||
account.Precision,
|
||||
account.DebitBalance)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) UpdateAccount(account *types.Account) error {
|
||||
account.Updated = time.Now()
|
||||
|
||||
query := "UPDATE account SET updated = ?, name = ?, parent = UNHEX(?), currency = ?, `precision` = ?, debitBalance = ? WHERE id = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(account.Updated),
|
||||
account.Name,
|
||||
account.Parent,
|
||||
account.Currency,
|
||||
account.Precision,
|
||||
account.DebitBalance,
|
||||
account.Id)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) GetAccount(id string) (*types.Account, error) {
|
||||
a := types.Account{}
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err := db.QueryRow("SELECT LOWER(HEX(id)),LOWER(HEX(orgId)),inserted,updated,name,LOWER(HEX(parent)),currency,`precision`,debitBalance FROM account WHERE id = UNHEX(?)", id).
|
||||
Scan(&a.Id, &a.OrgId, &inserted, &updated, &a.Name, &a.Parent, &a.Currency, &a.Precision, &a.DebitBalance)
|
||||
|
||||
if a.Parent == emptyAccountId {
|
||||
a.Parent = ""
|
||||
}
|
||||
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return nil, errors.New("Account not found")
|
||||
case err != nil:
|
||||
return nil, err
|
||||
default:
|
||||
a.Inserted = util.MsToTime(inserted)
|
||||
a.Updated = util.MsToTime(updated)
|
||||
return &a, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) GetAccountsByOrgId(orgId string) ([]*types.Account, error) {
|
||||
rows, err := db.Query("SELECT LOWER(HEX(id)),LOWER(HEX(orgId)),inserted,updated,name,LOWER(HEX(parent)),currency,`precision`,debitBalance FROM account WHERE orgId = UNHEX(?)", orgId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
accounts := make([]*types.Account, 0)
|
||||
|
||||
for rows.Next() {
|
||||
a := new(types.Account)
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err = rows.Scan(&a.Id, &a.OrgId, &inserted, &updated, &a.Name, &a.Parent, &a.Currency, &a.Precision, &a.DebitBalance)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if a.Parent == emptyAccountId {
|
||||
a.Parent = ""
|
||||
}
|
||||
|
||||
a.Inserted = util.MsToTime(inserted)
|
||||
a.Updated = util.MsToTime(updated)
|
||||
|
||||
accounts = append(accounts, a)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPermissionedAccountIds(orgId string, userId string, tokenId string) ([]string, error) {
|
||||
// Get user permissions
|
||||
// TODO incorporate tokens
|
||||
rows, err := db.Query("SELECT LOWER(HEX(accountId)) FROM permission WHERE orgId = UNHEX(?) AND userId = UNHEX(?)", orgId, userId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var permissionedAccounts []string
|
||||
|
||||
var id string
|
||||
|
||||
for rows.Next() {
|
||||
err := rows.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
permissionedAccounts = append(permissionedAccounts, id)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return permissionedAccounts, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetSplitCountByAccountId(id string) (int64, error) {
|
||||
var count int64
|
||||
|
||||
query := "SELECT COUNT(*) FROM split WHERE deleted = false AND accountId = UNHEX(?)"
|
||||
|
||||
err := db.QueryRow(query, id).Scan(&count)
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (db *DB) GetChildCountByAccountId(id string) (int64, error) {
|
||||
var count int64
|
||||
query := "SELECT COUNT(*) FROM account WHERE parent = UNHEX(?)"
|
||||
|
||||
err := db.QueryRow(query, id).Scan(&count)
|
||||
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (db *DB) DeleteAccount(id string) error {
|
||||
query := "DELETE FROM account WHERE id = UNHEX(?)"
|
||||
|
||||
_, err := db.Exec(query, id)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) AddBalances(accounts []*types.Account, date time.Time) error {
|
||||
// TODO optimize
|
||||
ids := make([]string, len(accounts))
|
||||
|
||||
for i, account := range accounts {
|
||||
ids[i] = "UNHEX(\"" + account.Id + "\")"
|
||||
}
|
||||
|
||||
balanceMap := make(map[string]*int64)
|
||||
|
||||
query := "SELECT LOWER(HEX(accountId)), SUM(amount) FROM split WHERE deleted = false AND accountId IN (" +
|
||||
strings.Join(ids, ",") + ")" +
|
||||
" AND date < ? GROUP BY accountId"
|
||||
|
||||
rows, err := db.Query(query, util.TimeToMs(date))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id string
|
||||
var balance int64
|
||||
err := rows.Scan(&id, &balance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
balanceMap[id] = &balance
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, account := range accounts {
|
||||
account.Balance = balanceMap[account.Id]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) AddNativeBalancesCost(accounts []*types.Account, date time.Time) error {
|
||||
// TODO optimize
|
||||
ids := make([]string, len(accounts))
|
||||
|
||||
for i, account := range accounts {
|
||||
ids[i] = "UNHEX(\"" + account.Id + "\")"
|
||||
}
|
||||
|
||||
balanceMap := make(map[string]*int64)
|
||||
|
||||
query := "SELECT LOWER(HEX(accountId)), SUM(nativeAmount) FROM split WHERE deleted = false AND accountId IN (" +
|
||||
strings.Join(ids, ",") + ")" +
|
||||
" AND date < ? GROUP BY accountId"
|
||||
|
||||
rows, err := db.Query(query, util.TimeToMs(date))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id string
|
||||
var balance int64
|
||||
err := rows.Scan(&id, &balance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
balanceMap[id] = &balance
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, account := range accounts {
|
||||
account.NativeBalance = balanceMap[account.Id]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) AddNativeBalancesNearestInTime(accounts []*types.Account, date time.Time) error {
|
||||
// TODO Don't look up org currency every single time
|
||||
|
||||
for _, account := range accounts {
|
||||
err := db.AddNativeBalanceNearestInTime(account, date)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) AddBalance(account *types.Account, date time.Time) error {
|
||||
var balance sql.NullInt64
|
||||
|
||||
query := "SELECT SUM(amount) FROM split WHERE deleted = false AND accountId = UNHEX(?) AND date < ?"
|
||||
|
||||
err := db.QueryRow(query, account.Id, util.TimeToMs(date)).Scan(&balance)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.Balance = &balance.Int64
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) AddNativeBalanceCost(account *types.Account, date time.Time) error {
|
||||
var nativeBalance sql.NullInt64
|
||||
|
||||
query := "SELECT SUM(nativeAmount) FROM split WHERE deleted = false AND accountId = UNHEX(?) AND date < ?"
|
||||
|
||||
err := db.QueryRow(query, account.Id, util.TimeToMs(date)).Scan(&nativeBalance)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
account.NativeBalance = &nativeBalance.Int64
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) AddNativeBalanceNearestInTime(account *types.Account, date time.Time) error {
|
||||
var orgCurrency string
|
||||
var orgPrecision int
|
||||
|
||||
query1 := "SELECT currency,`precision` FROM org WHERE id = UNHEX(?)"
|
||||
|
||||
err := db.QueryRow(query1, account.OrgId).Scan(&orgCurrency, &orgPrecision)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if account.Balance == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if orgCurrency == account.Currency {
|
||||
nativeBalance := int64(*account.Balance)
|
||||
account.NativeBalance = &nativeBalance
|
||||
return nil
|
||||
}
|
||||
|
||||
var tmp sql.NullInt64
|
||||
var price float64
|
||||
|
||||
query2 := "SELECT ABS(CAST(date AS SIGNED) - ?) AS datediff, price FROM price WHERE currency = ? ORDER BY datediff ASC LIMIT 1"
|
||||
|
||||
err = db.QueryRow(query2, util.TimeToMs(date), account.Currency).Scan(&tmp, &price)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
nativeBalance := int64(0)
|
||||
account.NativeBalance = &nativeBalance
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
precisionAdj := math.Pow(10, float64(account.Precision-orgPrecision))
|
||||
nativeBalance := int64(float64(*account.Balance) * price / precisionAdj)
|
||||
account.NativeBalance = &nativeBalance
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) GetRootAccount(orgId string) (*types.Account, error) {
|
||||
a := types.Account{}
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err := db.QueryRow(
|
||||
"SELECT LOWER(HEX(id)),LOWER(HEX(orgId)),inserted,updated,name,LOWER(HEX(parent)),currency,`precision`,debitBalance FROM account WHERE orgId = UNHEX(?) AND parent = UNHEX(?)",
|
||||
orgId,
|
||||
emptyAccountId).
|
||||
Scan(&a.Id, &a.OrgId, &inserted, &updated, &a.Name, &a.Parent, &a.Currency, &a.Precision, &a.DebitBalance)
|
||||
|
||||
a.Parent = ""
|
||||
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return nil, errors.New("Account not found")
|
||||
case err != nil:
|
||||
return nil, err
|
||||
default:
|
||||
a.Inserted = util.MsToTime(inserted)
|
||||
a.Updated = util.MsToTime(updated)
|
||||
return &a, nil
|
||||
}
|
||||
}
|
||||
132
core/model/db/apikey.go
Normal file
132
core/model/db/apikey.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"github.com/openaccounting/oa-server/core/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ApiKeyInterface interface {
|
||||
InsertApiKey(*types.ApiKey) error
|
||||
UpdateApiKey(*types.ApiKey) error
|
||||
DeleteApiKey(string, string) error
|
||||
GetApiKeys(string) ([]*types.ApiKey, error)
|
||||
UpdateApiKeyActivity(string) error
|
||||
}
|
||||
|
||||
const apiKeyFields = "LOWER(HEX(id)),inserted,updated,LOWER(HEX(userId)),label"
|
||||
|
||||
func (db *DB) InsertApiKey(key *types.ApiKey) error {
|
||||
key.Inserted = time.Now()
|
||||
key.Updated = key.Inserted
|
||||
|
||||
query := "INSERT INTO apikey(id,inserted,updated,userId,label) VALUES(UNHEX(?),?,?,UNHEX(?),?)"
|
||||
res, err := db.Exec(
|
||||
query,
|
||||
key.Id,
|
||||
util.TimeToMs(key.Inserted),
|
||||
util.TimeToMs(key.Updated),
|
||||
key.UserId,
|
||||
key.Label,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowCnt, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rowCnt < 1 {
|
||||
return errors.New("Unable to insert apikey into db")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) UpdateApiKey(key *types.ApiKey) error {
|
||||
key.Updated = time.Now()
|
||||
|
||||
query := "UPDATE apikey SET updated = ?, label = ? WHERE deleted IS NULL AND id = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(key.Updated),
|
||||
key.Label,
|
||||
key.Id,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var inserted int64
|
||||
|
||||
err = db.QueryRow("SELECT inserted FROM apikey WHERE id = UNHEX(?)", key.Id).Scan(&inserted)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
key.Inserted = util.MsToTime(inserted)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) DeleteApiKey(id string, userId string) error {
|
||||
query := "UPDATE apikey SET deleted = ? WHERE id = UNHEX(?) AND userId = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(time.Now()),
|
||||
id,
|
||||
userId,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) GetApiKeys(userId string) ([]*types.ApiKey, error) {
|
||||
rows, err := db.Query("SELECT "+apiKeyFields+" from apikey WHERE deleted IS NULL AND userId = UNHEX(?)", userId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
keys := make([]*types.ApiKey, 0)
|
||||
|
||||
for rows.Next() {
|
||||
k := new(types.ApiKey)
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err = rows.Scan(&k.Id, &inserted, &updated, &k.UserId, &k.Label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
k.Inserted = util.MsToTime(inserted)
|
||||
k.Updated = util.MsToTime(updated)
|
||||
|
||||
keys = append(keys, k)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (db *DB) UpdateApiKeyActivity(id string) error {
|
||||
query := "UPDATE apikey SET updated = ? WHERE id = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(time.Now()),
|
||||
id,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
76
core/model/db/db.go
Normal file
76
core/model/db/db.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
)
|
||||
|
||||
type DB struct {
|
||||
*sql.DB
|
||||
}
|
||||
|
||||
type Datastore interface {
|
||||
Escape(string) string
|
||||
UserInterface
|
||||
OrgInterface
|
||||
AccountInterface
|
||||
TransactionInterface
|
||||
PriceInterface
|
||||
SessionInterface
|
||||
ApiKeyInterface
|
||||
}
|
||||
|
||||
func NewDB(dataSourceName string) (*DB, error) {
|
||||
var err error
|
||||
db, err := sql.Open("mysql", dataSourceName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = db.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DB{db}, nil
|
||||
}
|
||||
|
||||
func (db *DB) Escape(sql string) string {
|
||||
dest := make([]byte, 0, 2*len(sql))
|
||||
var escape byte
|
||||
for i := 0; i < len(sql); i++ {
|
||||
c := sql[i]
|
||||
|
||||
escape = 0
|
||||
|
||||
switch c {
|
||||
case 0: /* Must be escaped for 'mysql' */
|
||||
escape = '0'
|
||||
break
|
||||
case '\n': /* Must be escaped for logs */
|
||||
escape = 'n'
|
||||
break
|
||||
case '\r':
|
||||
escape = 'r'
|
||||
break
|
||||
case '\\':
|
||||
escape = '\\'
|
||||
break
|
||||
case '\'':
|
||||
escape = '\''
|
||||
break
|
||||
case '"': /* Better safe than sorry */
|
||||
escape = '"'
|
||||
break
|
||||
case '\032': /* This gives problems on Win32 */
|
||||
escape = 'Z'
|
||||
}
|
||||
|
||||
if escape != 0 {
|
||||
dest = append(dest, '\\', escape)
|
||||
} else {
|
||||
dest = append(dest, c)
|
||||
}
|
||||
}
|
||||
|
||||
return string(dest)
|
||||
}
|
||||
370
core/model/db/org.go
Normal file
370
core/model/db/org.go
Normal file
@@ -0,0 +1,370 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"github.com/openaccounting/oa-server/core/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
type OrgInterface interface {
|
||||
CreateOrg(*types.Org, string, []*types.Account) error
|
||||
UpdateOrg(*types.Org) error
|
||||
GetOrg(string, string) (*types.Org, error)
|
||||
GetOrgs(string) ([]*types.Org, error)
|
||||
GetOrgUserIds(string) ([]string, error)
|
||||
InsertInvite(*types.Invite) error
|
||||
AcceptInvite(*types.Invite, string) error
|
||||
GetInvites(string) ([]*types.Invite, error)
|
||||
GetInvite(string) (*types.Invite, error)
|
||||
DeleteInvite(string) error
|
||||
}
|
||||
|
||||
const orgFields = "LOWER(HEX(o.id)),o.inserted,o.updated,o.name,o.currency,o.`precision`"
|
||||
const inviteFields = "i.id,LOWER(HEX(i.orgId)),i.inserted,i.updated,i.email,i.accepted"
|
||||
|
||||
func (db *DB) CreateOrg(org *types.Org, userId string, accounts []*types.Account) (err error) {
|
||||
tx, err := db.Begin()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
tx.Rollback()
|
||||
panic(p) // re-throw panic after Rollback
|
||||
} else if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
err = tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
org.Inserted = time.Now()
|
||||
org.Updated = org.Inserted
|
||||
|
||||
// create org
|
||||
query1 := "INSERT INTO org(id,inserted,updated,name,currency,`precision`) VALUES(UNHEX(?),?,?,?,?,?)"
|
||||
|
||||
res, err := tx.Exec(
|
||||
query1,
|
||||
org.Id,
|
||||
util.TimeToMs(org.Inserted),
|
||||
util.TimeToMs(org.Updated),
|
||||
org.Name,
|
||||
org.Currency,
|
||||
org.Precision,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// associate user with org
|
||||
query2 := "INSERT INTO userorg(userId,orgId,admin) VALUES(UNHEX(?),UNHEX(?), 1)"
|
||||
|
||||
res, err = tx.Exec(query2, userId, org.Id)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
_, err = res.LastInsertId()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// create Accounts: Root, Assets, Liabilities, Equity, Income, Expenses
|
||||
|
||||
for _, account := range accounts {
|
||||
|
||||
query := "INSERT INTO account(id,orgId,inserted,updated,name,parent,currency,`precision`,debitBalance) VALUES (UNHEX(?),UNHEX(?),?,?,?,UNHEX(?),?,?,?)"
|
||||
|
||||
if _, err = tx.Exec(
|
||||
query,
|
||||
account.Id,
|
||||
org.Id,
|
||||
util.TimeToMs(org.Inserted),
|
||||
util.TimeToMs(org.Updated),
|
||||
account.Name,
|
||||
account.Parent,
|
||||
account.Currency,
|
||||
account.Precision,
|
||||
account.DebitBalance,
|
||||
); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
permissionId, err := util.NewGuid()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Grant root permission to user
|
||||
|
||||
query3 := "INSERT INTO permission (id,userId,orgId,accountId,type,inserted,updated) VALUES(UNHEX(?),UNHEX(?),UNHEX(?),UNHEX(?),?,?,?)"
|
||||
|
||||
_, err = tx.Exec(
|
||||
query3,
|
||||
permissionId,
|
||||
userId,
|
||||
org.Id,
|
||||
accounts[0].Id,
|
||||
0,
|
||||
util.TimeToMs(org.Inserted),
|
||||
util.TimeToMs(org.Updated),
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) UpdateOrg(org *types.Org) error {
|
||||
org.Updated = time.Now()
|
||||
|
||||
query := "UPDATE org SET updated = ?, name = ? WHERE id = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(org.Updated),
|
||||
org.Name,
|
||||
org.Id,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) GetOrg(orgId string, userId string) (*types.Org, error) {
|
||||
var o types.Org
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err := db.QueryRow("SELECT "+orgFields+" FROM org o JOIN userorg ON userorg.orgId = o.id WHERE o.id = UNHEX(?) AND userorg.userId = UNHEX(?)", orgId, userId).
|
||||
Scan(&o.Id, &inserted, &updated, &o.Name, &o.Currency, &o.Precision)
|
||||
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return nil, errors.New("Org not found")
|
||||
case err != nil:
|
||||
return nil, err
|
||||
default:
|
||||
o.Inserted = util.MsToTime(inserted)
|
||||
o.Updated = util.MsToTime(updated)
|
||||
return &o, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) GetOrgs(userId string) ([]*types.Org, error) {
|
||||
rows, err := db.Query("SELECT "+orgFields+" from org o JOIN userorg ON userorg.orgId = o.id WHERE userorg.userId = UNHEX(?)", userId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
orgs := make([]*types.Org, 0)
|
||||
|
||||
for rows.Next() {
|
||||
o := new(types.Org)
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err = rows.Scan(&o.Id, &inserted, &updated, &o.Name, &o.Currency, &o.Precision)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
o.Inserted = util.MsToTime(inserted)
|
||||
o.Updated = util.MsToTime(updated)
|
||||
|
||||
orgs = append(orgs, o)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return orgs, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetOrgUserIds(orgId string) ([]string, error) {
|
||||
rows, err := db.Query("SELECT LOWER(HEX(userId)) FROM userorg WHERE orgId = UNHEX(?)", orgId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
userIds := make([]string, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var userId string
|
||||
err = rows.Scan(&userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userIds = append(userIds, userId)
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return userIds, nil
|
||||
}
|
||||
|
||||
func (db *DB) InsertInvite(invite *types.Invite) error {
|
||||
invite.Inserted = time.Now()
|
||||
invite.Updated = invite.Inserted
|
||||
|
||||
query := "INSERT INTO invite(id,orgId,inserted,updated,email,accepted) VALUES(?,UNHEX(?),?,?,?,?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
invite.Id,
|
||||
invite.OrgId,
|
||||
util.TimeToMs(invite.Inserted),
|
||||
util.TimeToMs(invite.Updated),
|
||||
invite.Email,
|
||||
false,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) AcceptInvite(invite *types.Invite, userId string) error {
|
||||
invite.Updated = time.Now()
|
||||
|
||||
// Get root account for permission
|
||||
rootAccount, err := db.GetRootAccount(invite.OrgId)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tx, err := db.Begin()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
tx.Rollback()
|
||||
panic(p) // re-throw panic after Rollback
|
||||
} else if err != nil {
|
||||
tx.Rollback()
|
||||
} else {
|
||||
err = tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
// associate user with org
|
||||
query1 := "INSERT INTO userorg(userId,orgId,admin) VALUES(UNHEX(?),UNHEX(?), 0)"
|
||||
|
||||
_, err = tx.Exec(query1, userId, invite.OrgId)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query2 := "UPDATE invite SET accepted = 1, updated = ? WHERE id = ?"
|
||||
|
||||
_, err = tx.Exec(query2, util.TimeToMs(invite.Updated), invite.Id)
|
||||
|
||||
// Grant root permission to user
|
||||
|
||||
permissionId, err := util.NewGuid()
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query3 := "INSERT INTO permission (id,userId,orgId,accountId,type,inserted,updated) VALUES(UNHEX(?),UNHEX(?),UNHEX(?),UNHEX(?),?,?,?)"
|
||||
|
||||
_, err = tx.Exec(
|
||||
query3,
|
||||
permissionId,
|
||||
userId,
|
||||
invite.OrgId,
|
||||
rootAccount.Id,
|
||||
0,
|
||||
util.TimeToMs(invite.Updated),
|
||||
util.TimeToMs(invite.Updated),
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) GetInvites(orgId string) ([]*types.Invite, error) {
|
||||
// don't include expired invoices
|
||||
cutoff := util.TimeToMs(time.Now()) - 7*24*60*60*1000
|
||||
|
||||
rows, err := db.Query("SELECT "+inviteFields+" FROM invite i WHERE orgId = UNHEX(?) AND inserted > ?", orgId, cutoff)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
invites := make([]*types.Invite, 0)
|
||||
|
||||
for rows.Next() {
|
||||
i := new(types.Invite)
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err = rows.Scan(&i.Id, &i.OrgId, &inserted, &updated, &i.Email, &i.Accepted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
i.Inserted = util.MsToTime(inserted)
|
||||
i.Updated = util.MsToTime(updated)
|
||||
|
||||
invites = append(invites, i)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return invites, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetInvite(id string) (*types.Invite, error) {
|
||||
var i types.Invite
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err := db.QueryRow("SELECT "+inviteFields+" FROM invite i WHERE i.id = ?", id).
|
||||
Scan(&i.Id, &i.OrgId, &inserted, &updated, &i.Email, &i.Accepted)
|
||||
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return nil, errors.New("Invite not found")
|
||||
case err != nil:
|
||||
return nil, err
|
||||
default:
|
||||
i.Inserted = util.MsToTime(inserted)
|
||||
i.Updated = util.MsToTime(updated)
|
||||
return &i, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) DeleteInvite(id string) error {
|
||||
query := "DELETE FROM invite WHERE id = ?"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
id,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
156
core/model/db/price.go
Normal file
156
core/model/db/price.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"github.com/openaccounting/oa-server/core/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
type PriceInterface interface {
|
||||
InsertPrice(*types.Price) error
|
||||
GetPriceById(string) (*types.Price, error)
|
||||
DeletePrice(string) error
|
||||
GetPricesNearestInTime(string, time.Time) ([]*types.Price, error)
|
||||
GetPricesByCurrency(string, string) ([]*types.Price, error)
|
||||
}
|
||||
|
||||
const priceFields = "LOWER(HEX(p.id)),LOWER(HEX(p.orgId)),p.currency,p.date,p.inserted,p.updated,p.price"
|
||||
|
||||
func (db *DB) InsertPrice(price *types.Price) error {
|
||||
price.Inserted = time.Now()
|
||||
price.Updated = price.Inserted
|
||||
|
||||
if price.Date.IsZero() {
|
||||
price.Date = price.Inserted
|
||||
}
|
||||
|
||||
query := "INSERT INTO price(id,orgId,currency,date,inserted,updated,price) VALUES(UNHEX(?),UNHEX(?),?,?,?,?,?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
price.Id,
|
||||
price.OrgId,
|
||||
price.Currency,
|
||||
util.TimeToMs(price.Date),
|
||||
util.TimeToMs(price.Inserted),
|
||||
util.TimeToMs(price.Updated),
|
||||
price.Price,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) GetPriceById(id string) (*types.Price, error) {
|
||||
var p types.Price
|
||||
var date int64
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err := db.QueryRow("SELECT "+priceFields+" FROM price p WHERE id = UNHEX(?)", id).
|
||||
Scan(&p.Id, &p.OrgId, &p.Currency, &date, &inserted, &updated, &p.Price)
|
||||
|
||||
switch {
|
||||
case err == sql.ErrNoRows:
|
||||
return nil, errors.New("Price not found")
|
||||
case err != nil:
|
||||
return nil, err
|
||||
default:
|
||||
p.Date = util.MsToTime(date)
|
||||
p.Inserted = util.MsToTime(inserted)
|
||||
p.Updated = util.MsToTime(updated)
|
||||
return &p, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (db *DB) DeletePrice(id string) error {
|
||||
query := "DELETE FROM price WHERE id = UNHEX(?)"
|
||||
|
||||
_, err := db.Exec(query, id)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) GetPricesNearestInTime(orgId string, date time.Time) ([]*types.Price, error) {
|
||||
qSelect := "SELECT " + priceFields
|
||||
qFrom := " FROM price p"
|
||||
qJoin := " LEFT OUTER JOIN price p2 ON p.currency = p2.currency AND p.orgId = p2.orgId AND ABS(CAST(p.date AS SIGNED) - ?) > ABS(CAST(p2.date AS SIGNED) - ?)"
|
||||
qWhere := " WHERE p2.id IS NULL AND p.orgId = UNHEX(?)"
|
||||
|
||||
query := qSelect + qFrom + qJoin + qWhere
|
||||
|
||||
rows, err := db.Query(query, util.TimeToMs(date), util.TimeToMs(date), orgId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
prices := make([]*types.Price, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var date int64
|
||||
var inserted int64
|
||||
var updated int64
|
||||
p := new(types.Price)
|
||||
err = rows.Scan(&p.Id, &p.OrgId, &p.Currency, &date, &inserted, &updated, &p.Price)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.Date = util.MsToTime(date)
|
||||
p.Inserted = util.MsToTime(inserted)
|
||||
p.Updated = util.MsToTime(updated)
|
||||
|
||||
prices = append(prices, p)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return prices, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetPricesByCurrency(orgId string, currency string) ([]*types.Price, error) {
|
||||
qSelect := "SELECT " + priceFields
|
||||
qFrom := " FROM price p"
|
||||
qWhere := " WHERE p.orgId = UNHEX(?) AND p.currency = ?"
|
||||
pOrder := " ORDER BY date ASC"
|
||||
|
||||
query := qSelect + qFrom + qWhere + pOrder
|
||||
|
||||
rows, err := db.Query(query, orgId, currency)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
prices := make([]*types.Price, 0)
|
||||
|
||||
for rows.Next() {
|
||||
var date int64
|
||||
var inserted int64
|
||||
var updated int64
|
||||
p := new(types.Price)
|
||||
err = rows.Scan(&p.Id, &p.OrgId, &p.Currency, &date, &inserted, &updated, &p.Price)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p.Date = util.MsToTime(date)
|
||||
p.Inserted = util.MsToTime(inserted)
|
||||
p.Updated = util.MsToTime(updated)
|
||||
|
||||
prices = append(prices, p)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return prices, nil
|
||||
}
|
||||
65
core/model/db/session.go
Normal file
65
core/model/db/session.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"github.com/openaccounting/oa-server/core/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SessionInterface interface {
|
||||
InsertSession(*types.Session) error
|
||||
DeleteSession(string, string) error
|
||||
UpdateSessionActivity(string) error
|
||||
}
|
||||
|
||||
func (db *DB) InsertSession(session *types.Session) error {
|
||||
session.Inserted = time.Now()
|
||||
session.Updated = session.Inserted
|
||||
|
||||
query := "INSERT INTO session(id,inserted,updated,userId) VALUES(UNHEX(?),?,?,UNHEX(?))"
|
||||
res, err := db.Exec(
|
||||
query,
|
||||
session.Id,
|
||||
util.TimeToMs(session.Inserted),
|
||||
util.TimeToMs(session.Updated),
|
||||
session.UserId,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowCnt, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rowCnt < 1 {
|
||||
return errors.New("Unable to insert session into db")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) DeleteSession(id string, userId string) error {
|
||||
query := "UPDATE session SET `terminated` = ? WHERE id = UNHEX(?) AND userId = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(time.Now()),
|
||||
id,
|
||||
userId,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) UpdateSessionActivity(id string) error {
|
||||
query := "UPDATE session SET updated = ? WHERE id = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(time.Now()),
|
||||
id,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
558
core/model/db/transaction.go
Normal file
558
core/model/db/transaction.go
Normal file
@@ -0,0 +1,558 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"github.com/openaccounting/oa-server/core/util"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const txFields = "LOWER(HEX(id)),LOWER(HEX(orgId)),LOWER(HEX(userId)),date,inserted,updated,description,data,deleted"
|
||||
const splitFields = "id,LOWER(HEX(transactionId)),LOWER(HEX(accountId)),date,inserted,updated,amount,nativeAmount,deleted"
|
||||
|
||||
type TransactionInterface interface {
|
||||
InsertTransaction(*types.Transaction) error
|
||||
GetTransactionById(string) (*types.Transaction, error)
|
||||
GetTransactionsByAccount(string, *types.QueryOptions) ([]*types.Transaction, error)
|
||||
GetTransactionsByOrg(string, *types.QueryOptions, []string) ([]*types.Transaction, error)
|
||||
DeleteTransaction(string) error
|
||||
DeleteAndInsertTransaction(string, *types.Transaction) error
|
||||
}
|
||||
|
||||
func (db *DB) InsertTransaction(transaction *types.Transaction) (err error) {
|
||||
// Save to db
|
||||
dbTx, err := db.Begin()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
dbTx.Rollback()
|
||||
panic(p) // re-throw panic after Rollback
|
||||
} else if err != nil {
|
||||
dbTx.Rollback()
|
||||
} else {
|
||||
err = dbTx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
// save tx
|
||||
query1 := "INSERT INTO transaction(id,orgId,userId,date,inserted,updated,description,data) VALUES(UNHEX(?),UNHEX(?),UNHEX(?),?,?,?,?,?)"
|
||||
|
||||
_, err = dbTx.Exec(
|
||||
query1,
|
||||
transaction.Id,
|
||||
transaction.OrgId,
|
||||
transaction.UserId,
|
||||
util.TimeToMs(transaction.Date),
|
||||
util.TimeToMs(transaction.Inserted),
|
||||
util.TimeToMs(transaction.Updated),
|
||||
transaction.Description,
|
||||
transaction.Data,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// save splits
|
||||
for _, split := range transaction.Splits {
|
||||
query := "INSERT INTO split(transactionId,accountId,date,inserted,updated,amount,nativeAmount) VALUES (UNHEX(?),UNHEX(?),?,?,?,?,?)"
|
||||
|
||||
_, err = dbTx.Exec(
|
||||
query,
|
||||
transaction.Id,
|
||||
split.AccountId,
|
||||
util.TimeToMs(transaction.Date),
|
||||
util.TimeToMs(transaction.Inserted),
|
||||
util.TimeToMs(transaction.Updated),
|
||||
split.Amount,
|
||||
split.NativeAmount)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) GetTransactionById(id string) (*types.Transaction, error) {
|
||||
row := db.QueryRow("SELECT "+txFields+" FROM transaction WHERE id = UNHEX(?)", id)
|
||||
|
||||
t, err := db.unmarshalTransaction(row)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rows, err := db.Query("SELECT "+splitFields+" FROM split WHERE transactionId = UNHEX(?) ORDER BY id", t.Id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Splits, err = db.unmarshalSplits(rows)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetTransactionsByAccount(accountId string, options *types.QueryOptions) ([]*types.Transaction, error) {
|
||||
query := "SELECT LOWER(HEX(s.transactionId)) FROM split s"
|
||||
|
||||
if options.DescriptionStartsWith != "" {
|
||||
query = query + " JOIN transaction t ON t.id = s.transactionId"
|
||||
}
|
||||
|
||||
query = query + " WHERE s.accountId = UNHEX(?)"
|
||||
|
||||
query = db.addOptionsToQuery(query, options)
|
||||
|
||||
rows, err := db.Query(query, accountId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
var ids []string
|
||||
|
||||
for rows.Next() {
|
||||
var id string
|
||||
err = rows.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids = append(ids, "UNHEX(\""+id+"\")")
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
return make([]*types.Transaction, 0), nil
|
||||
}
|
||||
|
||||
query = "SELECT " + txFields + " FROM transaction WHERE id IN (" + strings.Join(ids, ",") + ")"
|
||||
|
||||
query = db.addSortToQuery(query, options)
|
||||
|
||||
rows, err = db.Query(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transactions, err := db.unmarshalTransactions(rows)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transactionMap := make(map[string]*types.Transaction)
|
||||
|
||||
for _, t := range transactions {
|
||||
transactionMap[t.Id] = t
|
||||
}
|
||||
|
||||
rows, err = db.Query("SELECT " + splitFields + " FROM split WHERE transactionId IN (" + strings.Join(ids, ",") + ") ORDER BY id")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
splits, err := db.unmarshalSplits(rows)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, s := range splits {
|
||||
transaction := transactionMap[s.TransactionId]
|
||||
transaction.Splits = append(transaction.Splits, s)
|
||||
}
|
||||
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetTransactionsByOrg(orgId string, options *types.QueryOptions, accountIds []string) ([]*types.Transaction, error) {
|
||||
if len(accountIds) == 0 {
|
||||
return make([]*types.Transaction, 0), nil
|
||||
}
|
||||
|
||||
for i, accountId := range accountIds {
|
||||
accountIds[i] = "UNHEX(\"" + accountId + "\")"
|
||||
}
|
||||
|
||||
query := "SELECT DISTINCT LOWER(HEX(s.transactionId)),s.date,s.inserted,s.updated FROM split s"
|
||||
|
||||
if options.DescriptionStartsWith != "" {
|
||||
query = query + " JOIN transaction t ON t.id = s.transactionId"
|
||||
}
|
||||
|
||||
query = query + " WHERE s.accountId IN (" + strings.Join(accountIds, ",") + ")"
|
||||
|
||||
query = db.addOptionsToQuery(query, options)
|
||||
|
||||
rows, err := db.Query(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
ids := []string{}
|
||||
|
||||
for rows.Next() {
|
||||
var id string
|
||||
var date int64
|
||||
var inserted int64
|
||||
var updated int64
|
||||
err = rows.Scan(&id, &date, &inserted, &updated)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ids = append(ids, "UNHEX(\""+id+"\")")
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(ids) == 0 {
|
||||
return make([]*types.Transaction, 0), nil
|
||||
}
|
||||
|
||||
query = "SELECT " + txFields + " FROM transaction WHERE id IN (" + strings.Join(ids, ",") + ")"
|
||||
|
||||
query = db.addSortToQuery(query, options)
|
||||
|
||||
rows, err = db.Query(query)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transactions, err := db.unmarshalTransactions(rows)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transactionMap := make(map[string]*types.Transaction)
|
||||
|
||||
for _, t := range transactions {
|
||||
transactionMap[t.Id] = t
|
||||
}
|
||||
|
||||
rows, err = db.Query("SELECT " + splitFields + " FROM split WHERE transactionId IN (" + strings.Join(ids, ",") + ") ORDER BY id")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
splits, err := db.unmarshalSplits(rows)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, s := range splits {
|
||||
transaction := transactionMap[s.TransactionId]
|
||||
transaction.Splits = append(transaction.Splits, s)
|
||||
}
|
||||
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
func (db *DB) DeleteTransaction(id string) (err error) {
|
||||
dbTx, err := db.Begin()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
dbTx.Rollback()
|
||||
panic(p) // re-throw panic after Rollback
|
||||
} else if err != nil {
|
||||
dbTx.Rollback()
|
||||
} else {
|
||||
err = dbTx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
updatedTime := util.TimeToMs(time.Now())
|
||||
|
||||
// mark splits as deleted
|
||||
|
||||
query1 := "UPDATE split SET updated = ?, deleted = true WHERE transactionId = UNHEX(?)"
|
||||
|
||||
_, err = dbTx.Exec(
|
||||
query1,
|
||||
updatedTime,
|
||||
id,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// mark transaction as deleted
|
||||
|
||||
query2 := "UPDATE transaction SET updated = ?, deleted = true WHERE id = UNHEX(?)"
|
||||
|
||||
_, err = dbTx.Exec(
|
||||
query2,
|
||||
updatedTime,
|
||||
id,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) DeleteAndInsertTransaction(oldId string, transaction *types.Transaction) (err error) {
|
||||
// Save to db
|
||||
dbTx, err := db.Begin()
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
dbTx.Rollback()
|
||||
panic(p) // re-throw panic after Rollback
|
||||
} else if err != nil {
|
||||
dbTx.Rollback()
|
||||
} else {
|
||||
err = dbTx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
updatedTime := util.TimeToMs(transaction.Updated)
|
||||
|
||||
// mark splits as deleted
|
||||
|
||||
query1 := "UPDATE split SET updated = ?, deleted = true WHERE transactionId = UNHEX(?)"
|
||||
|
||||
_, err = dbTx.Exec(
|
||||
query1,
|
||||
updatedTime,
|
||||
oldId,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// mark transaction as deleted
|
||||
|
||||
query2 := "UPDATE transaction SET updated = ?, deleted = true WHERE id = UNHEX(?)"
|
||||
|
||||
_, err = dbTx.Exec(
|
||||
query2,
|
||||
updatedTime,
|
||||
oldId,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// save new tx
|
||||
query3 := "INSERT INTO transaction(id,orgId,userId,date,inserted,updated,description,data) VALUES(UNHEX(?),UNHEX(?),UNHEX(?),?,?,?,?,?)"
|
||||
|
||||
_, err = dbTx.Exec(
|
||||
query3,
|
||||
transaction.Id,
|
||||
transaction.OrgId,
|
||||
transaction.UserId,
|
||||
util.TimeToMs(transaction.Date),
|
||||
util.TimeToMs(transaction.Inserted),
|
||||
updatedTime,
|
||||
transaction.Description,
|
||||
transaction.Data,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// save splits
|
||||
for _, split := range transaction.Splits {
|
||||
query := "INSERT INTO split(transactionId,accountId,date,inserted,updated,amount,nativeAmount) VALUES (UNHEX(?),UNHEX(?),?,?,?,?,?)"
|
||||
|
||||
_, err = dbTx.Exec(
|
||||
query,
|
||||
transaction.Id,
|
||||
split.AccountId,
|
||||
util.TimeToMs(transaction.Date),
|
||||
util.TimeToMs(transaction.Inserted),
|
||||
updatedTime,
|
||||
split.Amount,
|
||||
split.NativeAmount)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalTransaction(row *sql.Row) (*types.Transaction, error) {
|
||||
t := new(types.Transaction)
|
||||
|
||||
var date int64
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err := row.Scan(&t.Id, &t.OrgId, &t.UserId, &date, &inserted, &updated, &t.Description, &t.Data, &t.Deleted)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Date = util.MsToTime(date)
|
||||
t.Inserted = util.MsToTime(inserted)
|
||||
t.Updated = util.MsToTime(updated)
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalTransactions(rows *sql.Rows) ([]*types.Transaction, error) {
|
||||
defer rows.Close()
|
||||
|
||||
transactions := make([]*types.Transaction, 0)
|
||||
|
||||
for rows.Next() {
|
||||
t := new(types.Transaction)
|
||||
var date int64
|
||||
var inserted int64
|
||||
var updated int64
|
||||
err := rows.Scan(&t.Id, &t.OrgId, &t.UserId, &date, &inserted, &updated, &t.Description, &t.Data, &t.Deleted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t.Date = util.MsToTime(date)
|
||||
t.Inserted = util.MsToTime(inserted)
|
||||
t.Updated = util.MsToTime(updated)
|
||||
transactions = append(transactions, t)
|
||||
}
|
||||
|
||||
err := rows.Err()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalSplits(rows *sql.Rows) ([]*types.Split, error) {
|
||||
defer rows.Close()
|
||||
|
||||
splits := make([]*types.Split, 0)
|
||||
|
||||
for rows.Next() {
|
||||
s := new(types.Split)
|
||||
var id int64
|
||||
var date int64
|
||||
var inserted int64
|
||||
var updated int64
|
||||
var deleted bool
|
||||
err := rows.Scan(&id, &s.TransactionId, &s.AccountId, &date, &inserted, &updated, &s.Amount, &s.NativeAmount, &deleted)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
splits = append(splits, s)
|
||||
}
|
||||
|
||||
err := rows.Err()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return splits, nil
|
||||
}
|
||||
|
||||
func (db *DB) addOptionsToQuery(query string, options *types.QueryOptions) string {
|
||||
if options.IncludeDeleted != true {
|
||||
query += " AND s.deleted = false"
|
||||
}
|
||||
|
||||
if options.SinceInserted != 0 {
|
||||
query += " AND s.inserted > " + strconv.Itoa(options.SinceInserted)
|
||||
}
|
||||
|
||||
if options.SinceUpdated != 0 {
|
||||
query += " AND s.updated > " + strconv.Itoa(options.SinceUpdated)
|
||||
}
|
||||
|
||||
if options.BeforeInserted != 0 {
|
||||
query += " AND s.inserted < " + strconv.Itoa(options.BeforeInserted)
|
||||
}
|
||||
|
||||
if options.BeforeUpdated != 0 {
|
||||
query += " AND s.updated < " + strconv.Itoa(options.BeforeUpdated)
|
||||
}
|
||||
|
||||
if options.StartDate != 0 {
|
||||
query += " AND s.date >= " + strconv.Itoa(options.StartDate)
|
||||
}
|
||||
|
||||
if options.EndDate != 0 {
|
||||
query += " AND s.date < " + strconv.Itoa(options.EndDate)
|
||||
}
|
||||
|
||||
if options.DescriptionStartsWith != "" {
|
||||
query += " AND t.description LIKE '" + db.Escape(options.DescriptionStartsWith) + "%'"
|
||||
}
|
||||
|
||||
if options.Sort == "updated-asc" {
|
||||
query += " ORDER BY s.updated ASC"
|
||||
} else {
|
||||
query += " ORDER BY s.date DESC, s.inserted DESC"
|
||||
}
|
||||
|
||||
if options.Limit != 0 && options.Skip != 0 {
|
||||
query += " LIMIT " + strconv.Itoa(options.Skip) + ", " + strconv.Itoa(options.Limit)
|
||||
} else if options.Limit != 0 {
|
||||
query += " LIMIT " + strconv.Itoa(options.Limit)
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
|
||||
func (db *DB) addSortToQuery(query string, options *types.QueryOptions) string {
|
||||
if options.Sort == "updated-asc" {
|
||||
query += " ORDER BY updated ASC"
|
||||
} else {
|
||||
query += " ORDER BY date DESC, inserted DESC"
|
||||
}
|
||||
|
||||
return query
|
||||
}
|
||||
264
core/model/db/user.go
Normal file
264
core/model/db/user.go
Normal file
@@ -0,0 +1,264 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/openaccounting/oa-server/core/model/types"
|
||||
"github.com/openaccounting/oa-server/core/util"
|
||||
"time"
|
||||
)
|
||||
|
||||
const userFields = "LOWER(HEX(u.id)),u.inserted,u.updated,u.firstName,u.lastName,u.email,u.passwordHash,u.agreeToTerms,u.passwordReset,u.emailVerified,u.emailVerifyCode"
|
||||
|
||||
type UserInterface interface {
|
||||
InsertUser(*types.User) error
|
||||
VerifyUser(string) error
|
||||
UpdateUser(*types.User) error
|
||||
UpdateUserResetPassword(*types.User) error
|
||||
GetVerifiedUserByEmail(string) (*types.User, error)
|
||||
GetUserByActiveSession(string) (*types.User, error)
|
||||
GetUserByApiKey(string) (*types.User, error)
|
||||
GetUserByResetCode(string) (*types.User, error)
|
||||
GetOrgAdmins(string) ([]*types.User, error)
|
||||
}
|
||||
|
||||
func (db *DB) InsertUser(user *types.User) error {
|
||||
user.Inserted = time.Now()
|
||||
user.Updated = user.Inserted
|
||||
user.PasswordReset = ""
|
||||
|
||||
query := "INSERT INTO user(id,inserted,updated,firstName,lastName,email,passwordHash,agreeToTerms,passwordReset,emailVerified,emailVerifyCode) VALUES(UNHEX(?),?,?,?,?,?,?,?,?,?,?)"
|
||||
res, err := db.Exec(
|
||||
query,
|
||||
user.Id,
|
||||
util.TimeToMs(user.Inserted),
|
||||
util.TimeToMs(user.Updated),
|
||||
user.FirstName,
|
||||
user.LastName,
|
||||
user.Email,
|
||||
user.PasswordHash,
|
||||
user.AgreeToTerms,
|
||||
user.PasswordReset,
|
||||
user.EmailVerified,
|
||||
user.EmailVerifyCode,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowCnt, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rowCnt < 1 {
|
||||
return errors.New("Unable to insert user into db")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) VerifyUser(code string) error {
|
||||
query := "UPDATE user SET updated = ?, emailVerified = 1 WHERE emailVerifyCode = ?"
|
||||
res, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(time.Now()),
|
||||
code,
|
||||
)
|
||||
|
||||
count, err := res.RowsAffected()
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if count == 0 {
|
||||
return errors.New("Invalid code")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *DB) UpdateUser(user *types.User) error {
|
||||
user.Updated = time.Now()
|
||||
|
||||
query := "UPDATE user SET updated = ?, passwordHash = ?, passwordReset = ? WHERE id = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(user.Updated),
|
||||
user.PasswordHash,
|
||||
"",
|
||||
user.Id,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) UpdateUserResetPassword(user *types.User) error {
|
||||
user.Updated = time.Now()
|
||||
|
||||
query := "UPDATE user SET updated = ?, passwordReset = ? WHERE id = UNHEX(?)"
|
||||
_, err := db.Exec(
|
||||
query,
|
||||
util.TimeToMs(user.Updated),
|
||||
user.PasswordReset,
|
||||
user.Id,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (db *DB) GetVerifiedUserByEmail(email string) (*types.User, error) {
|
||||
query := "SELECT " + userFields + " FROM user u WHERE email = ? AND emailVerified = 1"
|
||||
|
||||
row := db.QueryRow(query, email)
|
||||
u, err := db.unmarshalUser(row)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetUserByActiveSession(sessionId string) (*types.User, error) {
|
||||
qSelect := "SELECT " + userFields
|
||||
qFrom := " FROM user u"
|
||||
qJoin := " JOIN session s ON s.userId = u.id"
|
||||
qWhere := " WHERE s.terminated IS NULL AND s.id = UNHEX(?)"
|
||||
|
||||
query := qSelect + qFrom + qJoin + qWhere
|
||||
|
||||
row := db.QueryRow(query, sessionId)
|
||||
u, err := db.unmarshalUser(row)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetUserByApiKey(keyId string) (*types.User, error) {
|
||||
qSelect := "SELECT " + userFields
|
||||
qFrom := " FROM user u"
|
||||
qJoin := " JOIN apikey a ON a.userId = u.id"
|
||||
qWhere := " WHERE a.deleted IS NULL AND a.id = UNHEX(?)"
|
||||
|
||||
query := qSelect + qFrom + qJoin + qWhere
|
||||
|
||||
row := db.QueryRow(query, keyId)
|
||||
u, err := db.unmarshalUser(row)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetUserByResetCode(code string) (*types.User, error) {
|
||||
qSelect := "SELECT " + userFields
|
||||
qFrom := " FROM user u"
|
||||
qWhere := " WHERE u.passwordReset = ?"
|
||||
|
||||
query := qSelect + qFrom + qWhere
|
||||
|
||||
row := db.QueryRow(query, code)
|
||||
u, err := db.unmarshalUser(row)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmt.Println(u)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (db *DB) GetOrgAdmins(orgId string) ([]*types.User, error) {
|
||||
qSelect := "SELECT " + userFields
|
||||
qFrom := " FROM user u"
|
||||
qJoin := " JOIN userorg uo ON uo.userId = u.id"
|
||||
qWhere := " WHERE uo.admin = true AND uo.orgId = UNHEX(?)"
|
||||
|
||||
query := qSelect + qFrom + qJoin + qWhere
|
||||
|
||||
rows, err := db.Query(query, orgId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return db.unmarshalUsers(rows)
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalUser(row *sql.Row) (*types.User, error) {
|
||||
u := new(types.User)
|
||||
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err := row.Scan(
|
||||
&u.Id,
|
||||
&inserted,
|
||||
&updated,
|
||||
&u.FirstName,
|
||||
&u.LastName,
|
||||
&u.Email,
|
||||
&u.PasswordHash,
|
||||
&u.AgreeToTerms,
|
||||
&u.PasswordReset,
|
||||
&u.EmailVerified,
|
||||
&u.EmailVerifyCode,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u.Inserted = util.MsToTime(inserted)
|
||||
u.Updated = util.MsToTime(updated)
|
||||
|
||||
return u, nil
|
||||
}
|
||||
|
||||
func (db *DB) unmarshalUsers(rows *sql.Rows) ([]*types.User, error) {
|
||||
defer rows.Close()
|
||||
|
||||
users := make([]*types.User, 0)
|
||||
|
||||
for rows.Next() {
|
||||
u := new(types.User)
|
||||
var inserted int64
|
||||
var updated int64
|
||||
|
||||
err := rows.Scan(
|
||||
&u.Id,
|
||||
&inserted,
|
||||
&updated,
|
||||
&u.FirstName,
|
||||
&u.LastName,
|
||||
&u.Email,
|
||||
&u.PasswordHash,
|
||||
&u.AgreeToTerms,
|
||||
&u.PasswordReset,
|
||||
&u.EmailVerified,
|
||||
&u.EmailVerifyCode,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
u.Inserted = util.MsToTime(inserted)
|
||||
u.Updated = util.MsToTime(updated)
|
||||
|
||||
users = append(users, u)
|
||||
}
|
||||
|
||||
err := rows.Err()
|
||||
|
||||
return users, err
|
||||
}
|
||||
Reference in New Issue
Block a user