Compare commits
26 Commits
Author | SHA1 | Date |
---|---|---|
|
8cc8e6a464 | 2 years ago |
|
e398bcb7b0 | 2 years ago |
|
96b44a3df9 | 3 years ago |
|
336e7e14d3 | 3 years ago |
|
73ba8fc3b8 | 3 years ago |
|
d25f03e80c | 3 years ago |
|
3c12794d9c | 3 years ago |
|
d516f0aaae | 3 years ago |
|
3e6855d429 | 3 years ago |
|
c609ca7b9a | 3 years ago |
|
573ef316b4 | 3 years ago |
|
adbb5dbc43 | 3 years ago |
|
d1e53c2337 | 3 years ago |
|
5bad2c5335 | 3 years ago |
|
ee58da4f2d | 3 years ago |
|
53d5794c96 | 3 years ago |
|
630f9220f3 | 3 years ago |
|
6736229185 | 3 years ago |
|
857a558544 | 3 years ago |
|
c8d5410936 | 3 years ago |
|
d688900bae | 3 years ago |
|
1c15742710 | 3 years ago |
|
443c9b9376 | 3 years ago |
|
7274e08b9a | 3 years ago |
|
9e41b707bf | 3 years ago |
|
99bdf4e320 | 3 years ago |
@ -0,0 +1,2 @@
|
|||||||
|
Dockerfile
|
||||||
|
test
|
@ -1,2 +1,3 @@
|
|||||||
sql.db
|
sql.db
|
||||||
bog
|
bog
|
||||||
|
test
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
pipeline:
|
||||||
|
build:
|
||||||
|
image: golang
|
||||||
|
commands:
|
||||||
|
- go test ./...
|
||||||
|
environment:
|
||||||
|
- GOPRIVATE=git.sg.caj.me/caj
|
@ -0,0 +1,41 @@
|
|||||||
|
FROM golang:alpine as builder
|
||||||
|
|
||||||
|
ENV GO111MODULE=on
|
||||||
|
|
||||||
|
ENV USER=appuser
|
||||||
|
ENV UID=1000
|
||||||
|
RUN adduser \
|
||||||
|
--disabled-password \
|
||||||
|
--gecos "" \
|
||||||
|
--home "/nonexistent" \
|
||||||
|
--shell "/sbin/nologin" \
|
||||||
|
--no-create-home \
|
||||||
|
--uid "${UID}" \
|
||||||
|
"${USER}"
|
||||||
|
|
||||||
|
RUN apk update && apk add --no-cache git ca-certificates tzdata sqlite build-base
|
||||||
|
RUN mkdir /build
|
||||||
|
COPY . /build/
|
||||||
|
WORKDIR /build
|
||||||
|
|
||||||
|
COPY ./ ./
|
||||||
|
RUN CGO_ENABLED=1 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags "-static"' -o bog .
|
||||||
|
|
||||||
|
FROM scratch AS final
|
||||||
|
LABEL author="Cajually <me@caj.me>"
|
||||||
|
|
||||||
|
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||||
|
COPY --from=builder /etc/passwd /etc/passwd
|
||||||
|
COPY --from=builder /etc/group /etc/group
|
||||||
|
|
||||||
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
COPY --from=builder /build/bog /
|
||||||
|
COPY --from=builder /build/server/views /server/views
|
||||||
|
COPY --from=builder /build/default.toml /
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
USER appuser:appuser
|
||||||
|
ENTRYPOINT ["/bog"]
|
||||||
|
|
||||||
|
EXPOSE 8002
|
@ -0,0 +1,7 @@
|
|||||||
|
package dataswamp
|
||||||
|
|
||||||
|
import (
|
||||||
|
// "caj-larsson/bog/dataswamp/namespace"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AdminService struct{}
|
@ -0,0 +1 @@
|
|||||||
|
package dataswamp
|
@ -0,0 +1,57 @@
|
|||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RepositoryContract(fac func() Repository, t *testing.T) {
|
||||||
|
basicNamespaceContract(fac, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func basicNamespaceContract(fac func() Repository, t *testing.T) {
|
||||||
|
is := is.New(t)
|
||||||
|
r := fac()
|
||||||
|
|
||||||
|
all, err := r.All()
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(len(all), 0)
|
||||||
|
|
||||||
|
ns := Namespace{
|
||||||
|
23,
|
||||||
|
"n1",
|
||||||
|
time.Now(),
|
||||||
|
time.Duration(time.Hour * 3),
|
||||||
|
FileSizeQuota{1000, 0},
|
||||||
|
FileStat{1, 2},
|
||||||
|
FileStat{3, 4},
|
||||||
|
}
|
||||||
|
|
||||||
|
ns1, err := r.Create(ns)
|
||||||
|
is.NoErr(err)
|
||||||
|
ns.Name = "n2"
|
||||||
|
ns2, err := r.Create(ns)
|
||||||
|
is.NoErr(err)
|
||||||
|
|
||||||
|
is.True(ns1.ID != ns2.ID)
|
||||||
|
|
||||||
|
all, err = r.All()
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(len(all), 2)
|
||||||
|
|
||||||
|
is.Equal(ns.ID, int64(23))
|
||||||
|
|
||||||
|
ns3, _ := r.GetByName("n2")
|
||||||
|
|
||||||
|
is.Equal(*ns3, *ns2)
|
||||||
|
|
||||||
|
is.NoErr(r.Delete(ns2.ID))
|
||||||
|
|
||||||
|
all, err = r.All()
|
||||||
|
|
||||||
|
is.NoErr(err)
|
||||||
|
is.Equal(len(all), 1)
|
||||||
|
}
|
@ -0,0 +1,111 @@
|
|||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"caj-larsson/bog/util"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Clock interface {
|
||||||
|
Now() time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamespaceService struct {
|
||||||
|
repo Repository
|
||||||
|
outboxes []func(util.Event)
|
||||||
|
logger util.Logger
|
||||||
|
clock Clock
|
||||||
|
default_ttl time.Duration
|
||||||
|
default_quota_bytes int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNamespaceService(repo Repository, logger util.Logger, clock Clock, default_ttl time.Duration, default_quota_bytes int64) *NamespaceService {
|
||||||
|
return &NamespaceService{
|
||||||
|
repo,
|
||||||
|
nil,
|
||||||
|
logger,
|
||||||
|
clock,
|
||||||
|
default_ttl,
|
||||||
|
default_quota_bytes,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NamespaceService) GetOrCreateNs(name string) *Namespace {
|
||||||
|
ns, err := s.repo.GetByName(name)
|
||||||
|
|
||||||
|
if err == ErrNotExists {
|
||||||
|
new_ns := Namespace{
|
||||||
|
0,
|
||||||
|
name,
|
||||||
|
s.clock.Now(),
|
||||||
|
s.default_ttl,
|
||||||
|
FileSizeQuota{s.default_quota_bytes, 0},
|
||||||
|
FileStat{0, 0},
|
||||||
|
FileStat{0, 0},
|
||||||
|
}
|
||||||
|
created_ns, err := s.repo.Create(new_ns)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return created_ns
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return ns
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NamespaceService) Wire(reg func(string, util.EventHandler), outbox func(ev util.Event)) {
|
||||||
|
reg("FileUsed", s.handleFileUsed)
|
||||||
|
s.outboxes = append(s.outboxes, outbox)
|
||||||
|
|
||||||
|
reg("FileUsed", s.handleFileUsed)
|
||||||
|
reg("FileDeleted", s.handleFileDeleted)
|
||||||
|
reg("FileRecieved", s.handleFileRecieved)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NamespaceService) All() []Namespace {
|
||||||
|
nss, err := s.repo.All()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return nss
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NamespaceService) handleFileUsed(payload interface{}) {
|
||||||
|
var payload_s = payload.(struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
})
|
||||||
|
fmt.Printf("file used %v\n", payload_s)
|
||||||
|
ns := s.GetOrCreateNs(payload_s.Name)
|
||||||
|
ns.FileQuota = ns.FileQuota.Add(payload_s.Size)
|
||||||
|
s.repo.Update(ns.ID, *ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NamespaceService) handleFileDeleted(payload interface{}) {
|
||||||
|
var payload_s = payload.(struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
})
|
||||||
|
fmt.Printf("file deleted %v\n", payload_s)
|
||||||
|
ns := s.GetOrCreateNs(payload_s.Name)
|
||||||
|
ns.FileQuota = ns.FileQuota.Add(-payload_s.Size)
|
||||||
|
fmt.Printf("file usage %v\n", ns.FileQuota)
|
||||||
|
s.repo.Update(ns.ID, *ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *NamespaceService) handleFileRecieved(payload interface{}) {
|
||||||
|
var payload_s = payload.(struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
})
|
||||||
|
fmt.Printf("file recieved %v\n", payload_s)
|
||||||
|
ns := s.GetOrCreateNs(payload_s.Name)
|
||||||
|
ns.FileQuota = ns.FileQuota.Add(payload_s.Size)
|
||||||
|
fmt.Printf("file usage %v\n", ns.FileQuota)
|
||||||
|
s.repo.Update(ns.ID, *ns)
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"caj-larsson/bog/util"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEventTest(t *testing.T) {
|
||||||
|
eb := util.NewEventBus()
|
||||||
|
svc := NamespaceService{}
|
||||||
|
|
||||||
|
svc.Wire(eb.Register, eb.Handle)
|
||||||
|
|
||||||
|
events := []util.Event{
|
||||||
|
*util.NewEvent("FileUsed", struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
}{
|
||||||
|
"asd",
|
||||||
|
int64(12),
|
||||||
|
}),
|
||||||
|
*util.NewEvent("FileDeleted", struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
}{
|
||||||
|
"asd",
|
||||||
|
int64(12),
|
||||||
|
}),
|
||||||
|
*util.NewEvent("FileRecieved", struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
}{
|
||||||
|
"asd",
|
||||||
|
int64(12),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
util.AcceptsMessage(t, eb, events)
|
||||||
|
}
|
@ -1,138 +0,0 @@
|
|||||||
package dataswamp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"caj-larsson/bog/dataswamp/namespace"
|
|
||||||
"caj-larsson/bog/dataswamp/swampfile"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
// "errors"
|
|
||||||
// "fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SwampFileService struct {
|
|
||||||
namespace_repo namespace.Repository
|
|
||||||
swamp_file_repo swampfile.Repository
|
|
||||||
default_allowance_bytes int64
|
|
||||||
default_allowance_duration time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSwampFileService(
|
|
||||||
namespace_repo namespace.Repository,
|
|
||||||
swamp_file_repo swampfile.Repository,
|
|
||||||
da_bytes int64,
|
|
||||||
da_duration time.Duration,
|
|
||||||
) SwampFileService {
|
|
||||||
return SwampFileService{namespace_repo, swamp_file_repo, da_bytes, da_duration}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SwampFileService) getOrCreateNs(namespace_in string) *namespace.Namespace {
|
|
||||||
ns, err := s.namespace_repo.GetByName(namespace_in)
|
|
||||||
|
|
||||||
if err == namespace.ErrNotExists {
|
|
||||||
new_ns := namespace.Namespace{
|
|
||||||
0,
|
|
||||||
namespace_in,
|
|
||||||
time.Now(),
|
|
||||||
s.default_allowance_duration,
|
|
||||||
namespace.FileSizeQuota{s.default_allowance_bytes, 0},
|
|
||||||
}
|
|
||||||
created_ns, err := s.namespace_repo.Create(new_ns)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return created_ns
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return ns
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SwampFileService) SaveFile(ref swampfile.FileReference, src io.Reader, size int64) error {
|
|
||||||
ns := s.getOrCreateNs(ref.UserAgent)
|
|
||||||
|
|
||||||
err := ref.Clean(true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ns.FileQuota.Allows(size) {
|
|
||||||
return namespace.ErrExceedQuota
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := s.swamp_file_repo.Create(ref.Path, strconv.FormatInt(ns.ID, 10))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
written, err := io.CopyN(f, src, size)
|
|
||||||
|
|
||||||
if written < size {
|
|
||||||
s.swamp_file_repo.Delete(ref.Path, strconv.FormatInt(ns.ID, 10))
|
|
||||||
return swampfile.ErrContentSizeExaggerated
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf = make([]byte, 1)
|
|
||||||
|
|
||||||
overread, err := src.Read(buf)
|
|
||||||
|
|
||||||
if overread > 0 || err != io.EOF {
|
|
||||||
s.swamp_file_repo.Delete(ref.Path, strconv.FormatInt(ns.ID, 10))
|
|
||||||
return swampfile.ErrContentSizeExceeded
|
|
||||||
}
|
|
||||||
|
|
||||||
f.Close()
|
|
||||||
ns.FileQuota.Add(size)
|
|
||||||
s.namespace_repo.Update(ns.ID, *ns)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SwampFileService) OpenOutFile(ref swampfile.FileReference) (swampfile.SwampOutFile, error) {
|
|
||||||
ns := s.getOrCreateNs(ref.UserAgent)
|
|
||||||
|
|
||||||
err := ref.Clean(true)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := s.swamp_file_repo.Open(ref.Path, strconv.FormatInt(ns.ID, 10))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return f, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s SwampFileService) CleanUpExpiredFiles() error {
|
|
||||||
nss, err := s.namespace_repo.All()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ns := range nss {
|
|
||||||
expiry := time.Now().Add(-ns.AllowanceDuration)
|
|
||||||
dfs, err := s.swamp_file_repo.DeleteOlderThan(strconv.FormatInt(ns.ID, 10), expiry)
|
|
||||||
|
|
||||||
for _, df := range dfs {
|
|
||||||
ns.FileQuota.Remove(df.Size)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.namespace_repo.Update(ns.ID, ns)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -0,0 +1,138 @@
|
|||||||
|
package dataswamp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"caj-larsson/bog/dataswamp/namespace"
|
||||||
|
"caj-larsson/bog/dataswamp/swampfile"
|
||||||
|
"caj-larsson/bog/util"
|
||||||
|
"io"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
// "errors"
|
||||||
|
// "fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DataSwampService struct {
|
||||||
|
ns_svc namespace.NamespaceService
|
||||||
|
swamp_file_repo swampfile.Repository
|
||||||
|
logger util.Logger
|
||||||
|
eventBus util.EventBus
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDataSwampService(
|
||||||
|
ns_svc namespace.NamespaceService,
|
||||||
|
swamp_file_repo swampfile.Repository,
|
||||||
|
logger util.Logger,
|
||||||
|
) *DataSwampService {
|
||||||
|
s := DataSwampService{ns_svc, swamp_file_repo, logger, *util.NewEventBus()}
|
||||||
|
ns_svc.Wire(s.eventBus.Register, s.eventBus.Handle)
|
||||||
|
return &s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DataSwampService) NamespaceStats() []namespace.Namespace {
|
||||||
|
return s.ns_svc.All()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DataSwampService) SaveFile(ref swampfile.FileReference, src io.Reader, size int64) error {
|
||||||
|
ns := s.ns_svc.GetOrCreateNs(ref.UserAgent)
|
||||||
|
|
||||||
|
r, err := ref.Clean(true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ns.FileQuota.Allows(size) {
|
||||||
|
return namespace.ErrExceedQuota
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := s.swamp_file_repo.Create(r.Path, strconv.FormatInt(ns.ID, 10))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
// TODO: convert this into a different error.
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.eventBus.Handle(*util.NewEvent("FileUsed", struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
}{
|
||||||
|
ns.Name,
|
||||||
|
f.Size(),
|
||||||
|
}))
|
||||||
|
|
||||||
|
// TODO: rewrite this into an interruptable loop that emits downloaded events
|
||||||
|
written, err := io.CopyN(f, src, size)
|
||||||
|
|
||||||
|
if written < size {
|
||||||
|
s.swamp_file_repo.Delete(r.Path, strconv.FormatInt(ns.ID, 10)) //
|
||||||
|
return swampfile.ErrContentSizeExaggerated
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf = make([]byte, 1)
|
||||||
|
|
||||||
|
overread, err := src.Read(buf)
|
||||||
|
|
||||||
|
if overread > 0 || err != io.EOF {
|
||||||
|
s.swamp_file_repo.Delete(r.Path, strconv.FormatInt(ns.ID, 10))
|
||||||
|
return swampfile.ErrContentSizeExceeded
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s.eventBus.Handle(*util.NewEvent("FileRecieved", struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
}{
|
||||||
|
ns.Name,
|
||||||
|
written,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DataSwampService) OpenOutFile(ref swampfile.FileReference) (swampfile.SwampOutFile, error) {
|
||||||
|
ns := s.ns_svc.GetOrCreateNs(ref.UserAgent)
|
||||||
|
|
||||||
|
r, err := ref.Clean(true)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := s.swamp_file_repo.Open(r.Path, strconv.FormatInt(ns.ID, 10))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.eventBus.Handle(*util.NewEvent("FileSent", f.Size()))
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s DataSwampService) CleanUpExpiredFiles() error {
|
||||||
|
s.logger.Info("Cleaning up expired files")
|
||||||
|
|
||||||
|
for _, ns := range s.ns_svc.All() {
|
||||||
|
expiry := time.Now().Add(-ns.AllowanceDuration)
|
||||||
|
dfs, err := s.swamp_file_repo.DeleteOlderThan(strconv.FormatInt(ns.ID, 10), expiry)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, df := range dfs {
|
||||||
|
s.eventBus.Handle(*util.NewEvent("FileDeleted", struct {
|
||||||
|
Name string
|
||||||
|
Size int64
|
||||||
|
}{
|
||||||
|
ns.Name,
|
||||||
|
df.Size,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -0,0 +1,31 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.13.0
|
||||||
|
|
||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type DBTX interface {
|
||||||
|
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||||
|
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||||
|
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||||
|
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(db DBTX) *Queries {
|
||||||
|
return &Queries{db: db}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Queries struct {
|
||||||
|
db DBTX
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||||
|
return &Queries{
|
||||||
|
db: tx,
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.13.0
|
||||||
|
|
||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FileStat struct {
|
||||||
|
ID int64
|
||||||
|
Num int64
|
||||||
|
SizeB int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type Namespace struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
Lastseen int64
|
||||||
|
AllowanceTime sql.NullInt64
|
||||||
|
QuotaKb sql.NullInt64
|
||||||
|
QuotaUsageKb sql.NullInt64
|
||||||
|
DownloadID int64
|
||||||
|
UploadID int64
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
-- name: CreateNamespace :one
|
||||||
|
INSERT INTO
|
||||||
|
namespace(
|
||||||
|
name,
|
||||||
|
lastseen,
|
||||||
|
allowance_time,
|
||||||
|
quota_kb,
|
||||||
|
quota_usage_kb,
|
||||||
|
download_id,
|
||||||
|
upload_id
|
||||||
|
)
|
||||||
|
values(?, ?, ?, ?, ?, ?, ?)
|
||||||
|
returning id;
|
||||||
|
|
||||||
|
-- name: CreateFileStats :one
|
||||||
|
INSERT INTO file_stats(num, size_b)
|
||||||
|
values(?, ?)
|
||||||
|
returning id;
|
||||||
|
|
||||||
|
-- name: AllNamespaces :many
|
||||||
|
SELECT
|
||||||
|
ns.id,
|
||||||
|
ns.name,
|
||||||
|
ns.lastseen,
|
||||||
|
ns.allowance_time,
|
||||||
|
ns.quota_kb,
|
||||||
|
ns.quota_usage_kb,
|
||||||
|
d.num as d_num,
|
||||||
|
d.size_b as d_size_b,
|
||||||
|
ul.num as ul_num,
|
||||||
|
ul.size_b as ul_size_b
|
||||||
|
FROM namespace as ns
|
||||||
|
JOIN file_stats as d
|
||||||
|
ON ns.download_id = d.id
|
||||||
|
JOIN file_stats as ul
|
||||||
|
ON ns.upload_id = ul.id;
|
||||||
|
|
||||||
|
-- name: GetNamespaceByName :one
|
||||||
|
SELECT
|
||||||
|
ns.id,
|
||||||
|
ns.name,
|
||||||
|
ns.lastseen,
|
||||||
|
ns.allowance_time,
|
||||||
|
ns.quota_kb,
|
||||||
|
ns.quota_usage_kb,
|
||||||
|
d.num as d_num,
|
||||||
|
d.size_b as d_size_b,
|
||||||
|
ul.num as ul_num,
|
||||||
|
ul.size_b as ul_size_b
|
||||||
|
FROM namespace as ns
|
||||||
|
JOIN file_stats as d
|
||||||
|
ON ns.download_id = d.id
|
||||||
|
JOIN file_stats as ul
|
||||||
|
ON ns.upload_id = ul.id
|
||||||
|
WHERE ns.name = ?;
|
||||||
|
|
||||||
|
|
||||||
|
-- name: GetFileStat :one
|
||||||
|
SELECT * FROM file_stats where id = ?;
|
||||||
|
|
||||||
|
-- name: UpdateFileStat :exec
|
||||||
|
UPDATE file_stats SET num = ?, size_b = ? where id = ?;
|
||||||
|
|
||||||
|
-- name: UpdateNamespace :one
|
||||||
|
UPDATE namespace SET
|
||||||
|
name = ?,
|
||||||
|
lastseen = ?,
|
||||||
|
allowance_time = ?,
|
||||||
|
quota_kb = ?,
|
||||||
|
quota_usage_kb = ?
|
||||||
|
WHERE id = ?
|
||||||
|
RETURNING download_id, upload_id;
|
||||||
|
|
||||||
|
-- name: DeleteNameSpace :exec
|
||||||
|
DELETE FROM namespace where id = ?;
|
@ -0,0 +1,260 @@
|
|||||||
|
// Code generated by sqlc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// sqlc v1.13.0
|
||||||
|
// source: queries.sql
|
||||||
|
|
||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
)
|
||||||
|
|
||||||
|
const allNamespaces = `-- name: AllNamespaces :many
|
||||||
|
SELECT
|
||||||
|
ns.id,
|
||||||
|
ns.name,
|
||||||
|
ns.lastseen,
|
||||||
|
ns.allowance_time,
|
||||||
|
ns.quota_kb,
|
||||||
|
ns.quota_usage_kb,
|
||||||
|
d.num as d_num,
|
||||||
|
d.size_b as d_size_b,
|
||||||
|
ul.num as ul_num,
|
||||||
|
ul.size_b as ul_size_b
|
||||||
|
FROM namespace as ns
|
||||||
|
JOIN file_stats as d
|
||||||
|
ON ns.download_id = d.id
|
||||||
|
JOIN file_stats as ul
|
||||||
|
ON ns.upload_id = ul.id
|
||||||
|
`
|
||||||
|
|
||||||
|
type AllNamespacesRow struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
Lastseen int64
|
||||||
|
AllowanceTime sql.NullInt64
|
||||||
|
QuotaKb sql.NullInt64
|
||||||
|
QuotaUsageKb sql.NullInt64
|
||||||
|
DNum int64
|
||||||
|
DSizeB int64
|
||||||
|
UlNum int64
|
||||||
|
UlSizeB int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) AllNamespaces(ctx context.Context) ([]AllNamespacesRow, error) {
|
||||||
|
rows, err := q.db.QueryContext(ctx, allNamespaces)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var items []AllNamespacesRow
|
||||||
|
for rows.Next() {
|
||||||
|
var i AllNamespacesRow
|
||||||
|
if err := rows.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Lastseen,
|
||||||
|
&i.AllowanceTime,
|
||||||
|
&i.QuotaKb,
|
||||||
|
&i.QuotaUsageKb,
|
||||||
|
&i.DNum,
|
||||||
|
&i.DSizeB,
|
||||||
|
&i.UlNum,
|
||||||
|
&i.UlSizeB,
|
||||||
|
); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
items = append(items, i)
|
||||||
|
}
|
||||||
|
if err := rows.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFileStats = `-- name: CreateFileStats :one
|
||||||
|
INSERT INTO file_stats(num, size_b)
|
||||||
|
values(?, ?)
|
||||||
|
returning id
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateFileStatsParams struct {
|
||||||
|
Num int64
|
||||||
|
SizeB int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateFileStats(ctx context.Context, arg CreateFileStatsParams) (int64, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createFileStats, arg.Num, arg.SizeB)
|
||||||
|
var id int64
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const createNamespace = `-- name: CreateNamespace :one
|
||||||
|
INSERT INTO
|
||||||
|
namespace(
|
||||||
|
name,
|
||||||
|
lastseen,
|
||||||
|
allowance_time,
|
||||||
|
quota_kb,
|
||||||
|
quota_usage_kb,
|
||||||
|
download_id,
|
||||||
|
upload_id
|
||||||
|
)
|
||||||
|
values(?, ?, ?, ?, ?, ?, ?)
|
||||||
|
returning id
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateNamespaceParams struct {
|
||||||
|
Name string
|
||||||
|
Lastseen int64
|
||||||
|
AllowanceTime sql.NullInt64
|
||||||
|
QuotaKb sql.NullInt64
|
||||||
|
QuotaUsageKb sql.NullInt64
|
||||||
|
DownloadID int64
|
||||||
|
UploadID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateNamespace(ctx context.Context, arg CreateNamespaceParams) (int64, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, createNamespace,
|
||||||
|
arg.Name,
|
||||||
|
arg.Lastseen,
|
||||||
|
arg.AllowanceTime,
|
||||||
|
arg.QuotaKb,
|
||||||
|
arg.QuotaUsageKb,
|
||||||
|
arg.DownloadID,
|
||||||
|
arg.UploadID,
|
||||||
|
)
|
||||||
|
var id int64
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteNameSpace = `-- name: DeleteNameSpace :exec
|
||||||
|
DELETE FROM namespace where id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) DeleteNameSpace(ctx context.Context, id int64) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, deleteNameSpace, id)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getFileStat = `-- name: GetFileStat :one
|
||||||
|
SELECT id, num, size_b FROM file_stats where id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetFileStat(ctx context.Context, id int64) (FileStat, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getFileStat, id)
|
||||||
|
var i FileStat
|
||||||
|
err := row.Scan(&i.ID, &i.Num, &i.SizeB)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getNamespaceByName = `-- name: GetNamespaceByName :one
|
||||||
|
SELECT
|
||||||
|
ns.id,
|
||||||
|
ns.name,
|
||||||
|
ns.lastseen,
|
||||||
|
ns.allowance_time,
|
||||||
|
ns.quota_kb,
|
||||||
|
ns.quota_usage_kb,
|
||||||
|
d.num as d_num,
|
||||||
|
d.size_b as d_size_b,
|
||||||
|
ul.num as ul_num,
|
||||||
|
ul.size_b as ul_size_b
|
||||||
|
FROM namespace as ns
|
||||||
|
JOIN file_stats as d
|
||||||
|
ON ns.download_id = d.id
|
||||||
|
JOIN file_stats as ul
|
||||||
|
ON ns.upload_id = ul.id
|
||||||
|
WHERE ns.name = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetNamespaceByNameRow struct {
|
||||||
|
ID int64
|
||||||
|
Name string
|
||||||
|
Lastseen int64
|
||||||
|
AllowanceTime sql.NullInt64
|
||||||
|
QuotaKb sql.NullInt64
|
||||||
|
QuotaUsageKb sql.NullInt64
|
||||||
|
DNum int64
|
||||||
|
DSizeB int64
|
||||||
|
UlNum int64
|
||||||
|
UlSizeB int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetNamespaceByName(ctx context.Context, name string) (GetNamespaceByNameRow, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, getNamespaceByName, name)
|
||||||
|
var i GetNamespaceByNameRow
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.Name,
|
||||||
|
&i.Lastseen,
|
||||||
|
&i.AllowanceTime,
|
||||||
|
&i.QuotaKb,
|
||||||
|
&i.QuotaUsageKb,
|
||||||
|
&i.DNum,
|
||||||
|
&i.DSizeB,
|
||||||
|
&i.UlNum,
|
||||||
|
&i.UlSizeB,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateFileStat = `-- name: UpdateFileStat :exec
|
||||||
|
UPDATE file_stats SET num = ?, size_b = ? where id = ?
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateFileStatParams struct {
|
||||||
|
Num int64
|
||||||
|
SizeB int64
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateFileStat(ctx context.Context, arg UpdateFileStatParams) error {
|
||||||
|
_, err := q.db.ExecContext(ctx, updateFileStat, arg.Num, arg.SizeB, arg.ID)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateNamespace = `-- name: UpdateNamespace :one
|
||||||
|
UPDATE namespace SET
|
||||||
|
name = ?,
|
||||||
|
lastseen = ?,
|
||||||
|
allowance_time = ?,
|
||||||
|
quota_kb = ?,
|
||||||
|
quota_usage_kb = ?
|
||||||
|
WHERE id = ?
|
||||||
|
RETURNING download_id, upload_id
|
||||||
|
`
|
||||||
|
|
||||||
|
type UpdateNamespaceParams struct {
|
||||||
|
Name string
|
||||||
|
Lastseen int64
|
||||||
|
AllowanceTime sql.NullInt64
|
||||||
|
QuotaKb sql.NullInt64
|
||||||
|
QuotaUsageKb sql.NullInt64
|
||||||
|
ID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateNamespaceRow struct {
|
||||||
|
DownloadID int64
|
||||||
|
UploadID int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) UpdateNamespace(ctx context.Context, arg UpdateNamespaceParams) (UpdateNamespaceRow, error) {
|
||||||
|
row := q.db.QueryRowContext(ctx, updateNamespace,
|
||||||
|
arg.Name,
|
||||||
|
arg.Lastseen,
|
||||||
|
arg.AllowanceTime,
|
||||||
|
arg.QuotaKb,
|
||||||
|
arg.QuotaUsageKb,
|
||||||
|
arg.ID,
|
||||||
|
)
|
||||||
|
var i UpdateNamespaceRow
|
||||||
|
err := row.Scan(&i.DownloadID, &i.UploadID)
|
||||||
|
return i, err
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package namespace
|
||||||
|
|
||||||
|
import (
|
||||||
|
"caj-larsson/bog/dataswamp/namespace"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFileRepo(t *testing.T) {
|
||||||
|
namespace.RepositoryContract(func() namespace.Repository { return NewRepository(":memory:") }, t)
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS file_stats(
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
num BIGINT NOT NULL,
|
||||||
|
size_b BIGINT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS namespace(
|
||||||
|
id BIGINT PRIMARY KEY,
|
||||||
|
name TEXT NOT NULL UNIQUE,
|
||||||
|
lastseen BIGINT NOT NULL,
|
||||||
|
allowance_time BIGINT,
|
||||||
|
quota_kb BIGINT,
|
||||||
|
quota_usage_kb BIGINT,
|
||||||
|
download_id BIGINT NOT NULL REFERENCES file_stats(Id) ON DELETE CASCADE,
|
||||||
|
upload_id BIGINT NOT NULL REFERENCES file_stats(Id) ON DELETE CASCADE
|
||||||
|
);
|
@ -0,0 +1,11 @@
|
|||||||
|
package system_time
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Clock struct{}
|
||||||
|
|
||||||
|
func (c Clock) Now() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Debug int = 0
|
||||||
|
Info = 1
|
||||||
|
Warn = 2
|
||||||
|
None = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
type ServerLogger struct {
|
||||||
|
level int
|
||||||
|
}
|
||||||
|
|
||||||
|
func logf(level string, format string, a ...interface{}) {
|
||||||
|
head := fmt.Sprintf("%s - [%s]: ", time.Now().Format(time.RFC3339), level)
|
||||||
|
fmt.Printf(head+format+"\n", a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ServerLogger) Debug(format string, a ...interface{}) {
|
||||||
|
if t.level <= Debug {
|
||||||
|
logf("DEBUG", format, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ServerLogger) Info(format string, a ...interface{}) {
|
||||||
|
if t.level <= Info {
|
||||||
|
logf("INFO", format, a...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t ServerLogger) Warn(format string, a ...interface{}) {
|
||||||
|
if t.level <= Warn {
|
||||||
|
logf("WARN", format, a...)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>bog</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Last Seen</th>
|
||||||
|
<th scope="col">Quota</th>
|
||||||
|
<th scope="col">Usage</th>
|
||||||
|
<th scope="col">Download</th>
|
||||||
|
<th scope="col">Upload</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range . }}
|
||||||
|
<tr>
|
||||||
|
<td>{{ .Name }}</td>
|
||||||
|
<td>{{ .LastSeen }} - {{ .AllowanceDuration }}</td>
|
||||||
|
<td>{{ .FileQuota.CurrentUsage }}/{{ .FileQuota.AllowanceKB}} B</td>
|
||||||
|
<td>{{ .Usage.SizeB }}B - {{ .Usage.Num }} </td>
|
||||||
|
<td>{{ .Download.SizeB }}B - {{ .Download.Num }} </td>
|
||||||
|
<td>{{ .Upload.SizeB }}B - {{ .Upload.Num }} </td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,25 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>bog</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
function upload() {
|
||||||
|
var file = document.getElementById('file').files[0];
|
||||||
|
var ajax = new XMLHttpRequest;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
ajax.open('POST', '', true);
|
||||||
|
|
||||||
|
ajax.overrideMimeType('text/plain; charset=x-user-defined-binary');
|
||||||
|
reader.onload = function(evt) {
|
||||||
|
ajax.send(evt.target.result);
|
||||||
|
};
|
||||||
|
reader.readAsBinaryString(file);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<input id="file" type="file" />
|
||||||
|
<input type="button" onClick="upload()" value="Upload">
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,8 @@
|
|||||||
|
version: "1"
|
||||||
|
|
||||||
|
packages:
|
||||||
|
- path: infrastructure/sqlite/namespace
|
||||||
|
name: namespace
|
||||||
|
engine: postgresql
|
||||||
|
schema: infrastructure/sqlite/namespace/schema.sql
|
||||||
|
queries: infrastructure/sqlite/namespace/queries.sql
|
@ -0,0 +1,41 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
type Event struct {
|
||||||
|
eventName string
|
||||||
|
payload interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEvent(eventName string, payload interface{}) *Event {
|
||||||
|
return &Event{
|
||||||
|
eventName,
|
||||||
|
payload,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Event) EventName() string {
|
||||||
|
return e.eventName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Event) Payload() interface{} {
|
||||||
|
return e.payload
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventHandler func(payload interface{})
|
||||||
|
|
||||||
|
type EventBus struct {
|
||||||
|
handlers map[string][]EventHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewEventBus() *EventBus {
|
||||||
|
return &EventBus{make(map[string][]EventHandler)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *EventBus) Register(eventName string, handler EventHandler) {
|
||||||
|
eb.handlers[eventName] = append(eb.handlers[eventName], handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (eb *EventBus) Handle(e Event) {
|
||||||
|
for _, handler := range eb.handlers[e.EventName()] {
|
||||||
|
handler(e.Payload())
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/matryer/is"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (eb *EventBus) Handled(e Event) bool {
|
||||||
|
// TODO: figure out how to verify the event signature here.
|
||||||
|
handlers, exists := eb.handlers[e.EventName()]
|
||||||
|
if !exists {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(handlers) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func AcceptsMessage(t *testing.T, eb *EventBus, es []Event) {
|
||||||
|
is := is.New(t)
|
||||||
|
for _, e := range es {
|
||||||
|
is.True(eb.Handled(e))
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
type Logger interface {
|
||||||
|
Debug(format string, a ...interface{})
|
||||||
|
Info(format string, a ...interface{})
|
||||||
|
Warn(format string, a ...interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type TestLogger struct{}
|
||||||
|
|
||||||
|
func (t TestLogger) Debug(format string, a ...interface{}) {}
|
||||||
|
func (t TestLogger) Info(format string, a ...interface{}) {}
|
||||||
|
func (t TestLogger) Warn(format string, a ...interface{}) {}
|
Loading…
Reference in New Issue