Compare commits
10 Commits
79600941be
...
aca2019552
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aca2019552 | ||
|
|
ddba8ff6a1 | ||
|
|
14692392e2 | ||
|
|
117ad922e2 | ||
|
|
eec859ed48 | ||
|
|
b43c8b1f7a | ||
|
|
3a8af0551e | ||
|
|
a255d391b5 | ||
|
|
1a3ba7c5e9 | ||
|
|
0b4ecd1916 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@ cheqbooq
|
|||||||
exec-cheqbooq
|
exec-cheqbooq
|
||||||
**/*.sw*
|
**/*.sw*
|
||||||
vendor
|
vendor
|
||||||
|
testdata
|
||||||
|
|||||||
40
config/config.go
Executable file
40
config/config.go
Executable file
@@ -0,0 +1,40 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"local/args"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Port string
|
||||||
|
Public string
|
||||||
|
StoreAddr string
|
||||||
|
StoreNS string
|
||||||
|
Page int
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() {
|
||||||
|
if strings.Contains(fmt.Sprint(os.Args), "-test") {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
as := args.NewArgSet()
|
||||||
|
as.Append(args.INT, "p", "port to listen on", 52222)
|
||||||
|
as.Append(args.INT, "page", "page size for requests", 20)
|
||||||
|
as.Append(args.STRING, "d", "dir with public files", "./public")
|
||||||
|
as.Append(args.STRING, "s", "mongodb address", "localhost:27017")
|
||||||
|
as.Append(args.STRING, "ns", "mongodb database", "cheqbooq")
|
||||||
|
if err := as.Parse(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
Port = fmt.Sprintf(":%d", as.GetInt("p"))
|
||||||
|
Page = as.GetInt("page")
|
||||||
|
Public = as.GetString("d")
|
||||||
|
StoreAddr = as.GetString("s")
|
||||||
|
StoreNS = as.GetString("ns")
|
||||||
|
}
|
||||||
15
main.go
Executable file
15
main.go
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/cheqbooq/server"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if s, err := server.New(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if err := s.Routes(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if err := s.Listen(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
8
public/index.html
Executable file
8
public/index.html
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hi</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
server/account/read.go
Executable file
7
server/account/read.go
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
package account
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func Read(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "not impl", http.StatusNotImplemented)
|
||||||
|
}
|
||||||
5
server/account/readrequest.go
Executable file
5
server/account/readrequest.go
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
package account
|
||||||
|
|
||||||
|
type ReadRequest struct {
|
||||||
|
Accounts []string `json:"accounts"`
|
||||||
|
}
|
||||||
8
server/account/readresponse.go
Executable file
8
server/account/readresponse.go
Executable file
@@ -0,0 +1,8 @@
|
|||||||
|
package account
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type ReadResponse map[string]struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Meta json.RawMessage `json:"meta"`
|
||||||
|
}
|
||||||
7
server/account/write.go
Executable file
7
server/account/write.go
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
package account
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func Write(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "not impl", http.StatusNotImplemented)
|
||||||
|
}
|
||||||
5
server/account/writerequest.go
Executable file
5
server/account/writerequest.go
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
package account
|
||||||
|
|
||||||
|
import "local/cheqbooq/server/transaction"
|
||||||
|
|
||||||
|
type WriteRequest transaction.WriteRequest
|
||||||
5
server/account/writeresponse.go
Executable file
5
server/account/writeresponse.go
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
package account
|
||||||
|
|
||||||
|
import "local/cheqbooq/server/transaction"
|
||||||
|
|
||||||
|
type WriteResponse transaction.WriteResponse
|
||||||
7
server/balance/read.go
Executable file
7
server/balance/read.go
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
package balance
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func Read(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "not impl", http.StatusNotImplemented)
|
||||||
|
}
|
||||||
7
server/balance/readrequest.go
Executable file
7
server/balance/readrequest.go
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
package balance
|
||||||
|
|
||||||
|
type ReadRequest struct {
|
||||||
|
Accounts []string `json:"accounts"`
|
||||||
|
Start int64 `json:"start"`
|
||||||
|
Stop int64 `json:"stop"`
|
||||||
|
}
|
||||||
3
server/balance/readresponse.go
Executable file
3
server/balance/readresponse.go
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
package balance
|
||||||
|
|
||||||
|
type ReadResponse map[string]map[int64]float32
|
||||||
1
server/balance/writerequest.go
Executable file
1
server/balance/writerequest.go
Executable file
@@ -0,0 +1 @@
|
|||||||
|
package balance
|
||||||
1
server/balance/writeresponse.go
Executable file
1
server/balance/writeresponse.go
Executable file
@@ -0,0 +1 @@
|
|||||||
|
package balance
|
||||||
10
server/listen.go
Executable file
10
server/listen.go
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"local/cheqbooq/config"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) Listen() error {
|
||||||
|
return http.ListenAndServe(config.Port, s)
|
||||||
|
}
|
||||||
62
server/routes.go
Executable file
62
server/routes.go
Executable file
@@ -0,0 +1,62 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"local/cheqbooq/config"
|
||||||
|
"local/cheqbooq/server/account"
|
||||||
|
"local/cheqbooq/server/balance"
|
||||||
|
"local/cheqbooq/server/transaction"
|
||||||
|
"local/router"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *Server) Routes() error {
|
||||||
|
routes := map[string]http.HandlerFunc{
|
||||||
|
"api/v1/balance": s.balance1,
|
||||||
|
"api/v1/transaction": s.transaction1,
|
||||||
|
"api/v1/account": s.account1,
|
||||||
|
fmt.Sprintf("%s%s", router.Wildcard, router.Wildcard): s.public,
|
||||||
|
}
|
||||||
|
for path, handler := range routes {
|
||||||
|
if err := s.Add(path, handler); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) balance1(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
balance.Read(w, r)
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) transaction1(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
transaction.Read(w, r)
|
||||||
|
case http.MethodPatch:
|
||||||
|
transaction.Write(w, r)
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) account1(w http.ResponseWriter, r *http.Request) {
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
account.Read(w, r)
|
||||||
|
case http.MethodPatch:
|
||||||
|
account.Write(w, r)
|
||||||
|
default:
|
||||||
|
http.NotFound(w, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) public(w http.ResponseWriter, r *http.Request) {
|
||||||
|
d := http.FileServer(http.Dir(config.Public))
|
||||||
|
d.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
13
server/server.go
Executable file
13
server/server.go
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import "local/router"
|
||||||
|
|
||||||
|
type Server struct {
|
||||||
|
*router.Router
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (*Server, error) {
|
||||||
|
return &Server{
|
||||||
|
Router: router.New(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
7
server/transaction/read.go
Executable file
7
server/transaction/read.go
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
package transaction
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func Read(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "not impl", http.StatusNotImplemented)
|
||||||
|
}
|
||||||
5
server/transaction/readrequest.go
Executable file
5
server/transaction/readrequest.go
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
package transaction
|
||||||
|
|
||||||
|
import "local/cheqbooq/server/balance"
|
||||||
|
|
||||||
|
type ReadRequest balance.ReadRequest
|
||||||
3
server/transaction/readresponse.go
Executable file
3
server/transaction/readresponse.go
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
package transaction
|
||||||
|
|
||||||
|
type ReadResponse map[string]map[string]Transaction
|
||||||
12
server/transaction/transaction.go
Executable file
12
server/transaction/transaction.go
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
package transaction
|
||||||
|
|
||||||
|
import "encoding/json"
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Amount float32 `json:"amount"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
Meta json.RawMessage `json:"meta"`
|
||||||
|
}
|
||||||
7
server/transaction/write.go
Executable file
7
server/transaction/write.go
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
package transaction
|
||||||
|
|
||||||
|
import "net/http"
|
||||||
|
|
||||||
|
func Write(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.Error(w, "not impl", http.StatusNotImplemented)
|
||||||
|
}
|
||||||
7
server/transaction/writerequest.go
Executable file
7
server/transaction/writerequest.go
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
package transaction
|
||||||
|
|
||||||
|
type WriteRequest map[string][]struct {
|
||||||
|
Op string `json:"op"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
Value interface{} `json:"value"`
|
||||||
|
}
|
||||||
6
server/transaction/writeresponse.go
Executable file
6
server/transaction/writeresponse.go
Executable file
@@ -0,0 +1,6 @@
|
|||||||
|
package transaction
|
||||||
|
|
||||||
|
type WriteResponse map[string][]struct {
|
||||||
|
OK bool `json:"ok"`
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
}
|
||||||
15
storage/account.go
Executable file
15
storage/account.go
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
ID string `json:"_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) Accounts(token string) ([]Account, error) {
|
||||||
|
return nil, errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) PrimaryAccounts(token string) ([]Account, error) {
|
||||||
|
return nil, errors.New("not impl")
|
||||||
|
}
|
||||||
11
storage/account_test.go
Executable file
11
storage/account_test.go
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestAccounts(t *testing.T) {
|
||||||
|
t.Fatal("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrimaryAccounts(t *testing.T) {
|
||||||
|
t.Fatal("not impl")
|
||||||
|
}
|
||||||
17
storage/balance.go
Executable file
17
storage/balance.go
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
type Balance struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
At int64 `json:"at"`
|
||||||
|
Is float32 `json:"is"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) CurrentBalances(accounts ...Account) ([]Balance, error) {
|
||||||
|
return nil, errors.New("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) BalancesOverTime(from, to int64, accounts ...Account) ([]Balance, error) {
|
||||||
|
return nil, errors.New("not impl")
|
||||||
|
}
|
||||||
11
storage/balance_test.go
Executable file
11
storage/balance_test.go
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestCurrentBalances(t *testing.T) {
|
||||||
|
t.Fatal("not impl")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBalancesOverTime(t *testing.T) {
|
||||||
|
t.Fatal("not impl")
|
||||||
|
}
|
||||||
108
storage/mongo.go
Executable file
108
storage/mongo.go
Executable file
@@ -0,0 +1,108 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Mongo struct {
|
||||||
|
client *mongo.Client
|
||||||
|
ns string
|
||||||
|
page int
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
go func() {
|
||||||
|
kick := func() error {
|
||||||
|
cmd := exec.Command("bash", "-c", "true; until [ $(basename $PWD) == cheqbooq ]; do cd ..; done; NOFORK=1 bash ./testdata/start_mdb.sh")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s", b)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
block := func() error {
|
||||||
|
cmd := exec.Command("bash", "-c", "true; tail --pid=$(ps aux | grep mongod | grep -v grep | awk '{print $2}') -f /dev/null")
|
||||||
|
b, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("%s", b)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
if err := kick(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
if err := block(); err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMongo(page int, ns, addr string) (*Mongo, error) {
|
||||||
|
ctx, can := context.WithTimeout(context.Background(), time.Second*5)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
opt := options.Client()
|
||||||
|
opt.ApplyURI(addr)
|
||||||
|
|
||||||
|
client, err := mongo.Connect(ctx, opt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Mongo{
|
||||||
|
client: client,
|
||||||
|
ns: ns,
|
||||||
|
}, client.Ping(ctx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mongo) Close() error {
|
||||||
|
return m.client.Disconnect(context.TODO())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mongo) Account() *mongo.Collection {
|
||||||
|
return m.client.Database(m.ns).Collection("account")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mongo) Balance() *mongo.Collection {
|
||||||
|
return m.client.Database(m.ns).Collection("balance")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mongo) Transaction() *mongo.Collection {
|
||||||
|
return m.client.Database(m.ns).Collection("transaction")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mongo) Find(c *mongo.Collection, where interface{}, next func() interface{}) error {
|
||||||
|
ctx, can := context.WithCancel(context.TODO())
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
cur, err := c.Find(ctx, where, options.Find().SetLimit(int64(m.page)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer cur.Close(ctx)
|
||||||
|
|
||||||
|
for cur.Next(ctx) {
|
||||||
|
ptr := next()
|
||||||
|
if err := cur.Decode(ptr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cur.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Mongo) Upsert(c *mongo.Collection, where, op interface{}) error {
|
||||||
|
ctx, can := context.WithCancel(context.TODO())
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
_, err := c.UpdateMany(ctx, where, op, options.Update().SetUpsert(true))
|
||||||
|
return err
|
||||||
|
}
|
||||||
101
storage/mongo_test.go
Executable file
101
storage/mongo_test.go
Executable file
@@ -0,0 +1,101 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testMongoNew(t *testing.T) (*Mongo, func()) {
|
||||||
|
b := make([]byte, 5)
|
||||||
|
rand.Read(b)
|
||||||
|
ns := "gotest_" + base64.URLEncoding.EncodeToString(b)
|
||||||
|
m, err := NewMongo(10, ns, "mongodb://localhost:27017")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return m, func() {
|
||||||
|
ctx, can := context.WithTimeout(context.Background(), time.Second*30)
|
||||||
|
defer can()
|
||||||
|
m.client.Database(ns).Drop(ctx)
|
||||||
|
m.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMongoNew(t *testing.T) {
|
||||||
|
_, can := testMongoNew(t)
|
||||||
|
can()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMongoFind(t *testing.T) {
|
||||||
|
m, can := testMongoNew(t)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
c := m.Account()
|
||||||
|
if _, err := c.InsertMany(context.TODO(), []interface{}{
|
||||||
|
map[string]interface{}{"_id": "1", "a": "b"},
|
||||||
|
map[string]interface{}{"_id": "2", "a": "b", "c": "d"},
|
||||||
|
map[string]interface{}{"_id": "3", "c": "d"},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt := 0
|
||||||
|
inc := func() interface{} {
|
||||||
|
cnt += 1
|
||||||
|
var v interface{}
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
if err := m.Find(c, map[string]interface{}{"a": "b"}, inc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if cnt != 2 {
|
||||||
|
t.Fatal(cnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = 0
|
||||||
|
if err := m.Find(c, map[string]interface{}{"_id": "1"}, inc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if cnt != 1 {
|
||||||
|
t.Fatal(cnt)
|
||||||
|
}
|
||||||
|
|
||||||
|
cnt = 0
|
||||||
|
if err := m.Find(c, map[string]interface{}{}, inc); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if cnt != 3 {
|
||||||
|
t.Fatal(cnt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMongoUpsert(t *testing.T) {
|
||||||
|
m, can := testMongoNew(t)
|
||||||
|
defer can()
|
||||||
|
|
||||||
|
c := m.Account()
|
||||||
|
if _, err := c.InsertMany(context.TODO(), []interface{}{
|
||||||
|
map[string]interface{}{"_id": "1", "a": "b"},
|
||||||
|
map[string]interface{}{"_id": "2", "a": "b", "c": "d"},
|
||||||
|
map[string]interface{}{"_id": "3", "c": "d"},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if n, err := c.EstimatedDocumentCount(context.TODO()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if n != 3 {
|
||||||
|
t.Fatal(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := m.Upsert(c, map[string]interface{}{"_id": "1"}, map[string]interface{}{"$set": map[string]interface{}{"c": "d"}}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mapped := map[string]string{}
|
||||||
|
if err := c.FindOne(context.TODO(), map[string]interface{}{"_id": "1"}).Decode(&mapped); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else if mapped["c"] != "d" {
|
||||||
|
t.Fatal(mapped)
|
||||||
|
}
|
||||||
|
}
|
||||||
18
storage/storage.go
Executable file
18
storage/storage.go
Executable file
@@ -0,0 +1,18 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"local/cheqbooq/config"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Storage struct {
|
||||||
|
mongo *Mongo
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() (*Storage, error) {
|
||||||
|
mongo, err := NewMongo(config.Page, config.StoreNS, fmt.Sprintf("mongodb://%s", strings.TrimPrefix(config.StoreAddr, "mongodb://")))
|
||||||
|
return &Storage{
|
||||||
|
mongo: mongo,
|
||||||
|
}, err
|
||||||
|
}
|
||||||
13
storage/storage_test.go
Executable file
13
storage/storage_test.go
Executable file
@@ -0,0 +1,13 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func testStorage(t *testing.T) (*Storage, func()) {
|
||||||
|
m, can := testMongoNew(t)
|
||||||
|
return &Storage{m}, can
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStorage(t *testing.T) {
|
||||||
|
_, can := testStorage(t)
|
||||||
|
can()
|
||||||
|
}
|
||||||
15
storage/transaction.go
Executable file
15
storage/transaction.go
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
type Transaction struct {
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
Category string `json:"category"`
|
||||||
|
Amount float32 `json:"amount"`
|
||||||
|
Timestamp int64 `json:"timestamp"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Storage) Transactions(from, to int64, accounts ...Account) ([]Transaction, error) {
|
||||||
|
return nil, errors.New("not impl")
|
||||||
|
}
|
||||||
7
storage/transaction_test.go
Executable file
7
storage/transaction_test.go
Executable file
@@ -0,0 +1,7 @@
|
|||||||
|
package storage
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestTransactions(t *testing.T) {
|
||||||
|
t.Fatal("not impl")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user