The first something
parent
2f4473fca1
commit
a26782d6cd
@ -0,0 +1,71 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"fmt"
|
||||
"log"
|
||||
"io"
|
||||
"caj-larsson/bog/domain"
|
||||
"caj-larsson/bog/integration"
|
||||
)
|
||||
|
||||
type Bog struct {
|
||||
config *Configuration
|
||||
mux *http.ServeMux
|
||||
// file_service *domain.BogFileService
|
||||
fileDataRepo domain.FileDataRepository
|
||||
}
|
||||
|
||||
func New(config *Configuration) *Bog {
|
||||
b := new(Bog)
|
||||
b.config = config
|
||||
log.Print(config)
|
||||
|
||||
fsBogRepo := new(integration.FileSystemBogRepository)
|
||||
fsBogRepo.Root = "/tmp/datta"
|
||||
|
||||
b.fileDataRepo = *fsBogRepo
|
||||
// uaRepo *domain.UserAgentRepository
|
||||
|
||||
b.mux = http.NewServeMux()
|
||||
|
||||
b.mux.HandleFunc("/",func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/" {
|
||||
fmt.Fprintf(w, "Hi")
|
||||
return
|
||||
}
|
||||
|
||||
bog_file, err := b.fileDataRepo.OpenOrCreate(r.URL.Path, r.Header["User-Agent"][0])
|
||||
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
src := domainFileSource { bog_file.Path(),
|
||||
http.ServeContent(w, r, , bog_file.Modified(), bog_file)
|
||||
|
||||
case "POST":
|
||||
fallthrough
|
||||
case "PUT":
|
||||
|
||||
io.Copy(bog_file, r.Body)
|
||||
bog_file.Close()
|
||||
}
|
||||
return
|
||||
})
|
||||
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *Bog) Run() {
|
||||
http.ListenAndServe(b.config.address(), b.mux)
|
||||
}
|
||||
|
||||
|
||||
func CreateFileHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package application
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/BurntSushi/toml"
|
||||
|
||||
)
|
||||
|
||||
type Configuration struct {
|
||||
Port int64
|
||||
Host string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (c *Configuration) address() string {
|
||||
return fmt.Sprintf("%s:%d", c.Host, c.Port)
|
||||
}
|
||||
|
||||
func ConfigFromToml(toml_data string) (*Configuration, error) {
|
||||
var config Configuration
|
||||
|
||||
_, err := toml.Decode(toml_data, &config)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
port = 8002
|
||||
host = "127.0.0.1"
|
@ -0,0 +1,28 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"time"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type UserAgent struct {
|
||||
ID int64
|
||||
Name string
|
||||
LastSeen time.Time
|
||||
AllowanceDuration time.Duration
|
||||
FileQuota FileSizeQuota
|
||||
}
|
||||
|
||||
type BogFile struct {
|
||||
UserAgentId int64
|
||||
Path string
|
||||
Size int64
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
var (
|
||||
ErrDuplicate = errors.New("record already exists")
|
||||
ErrNotExists = errors.New("row not exists")
|
||||
ErrUpdateFailed = errors.New("update failed")
|
||||
ErrDeleteFailed = errors.New("delete failed")
|
||||
)
|
@ -0,0 +1,10 @@
|
||||
package domain
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSum(t *testing.T) {
|
||||
total := 3
|
||||
if total != 10 {
|
||||
t.Errorf("It failed ok")
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package domain
|
||||
|
||||
type UserAgentRepository interface{
|
||||
Migrate() error
|
||||
CreateUserAgent(useragent UserAgent) (*UserAgent, error)
|
||||
All() ([]UserAgent, error)
|
||||
GetByName(name string) (*UserAgent, error)
|
||||
Update(id int64, useragent UserAgent) (*UserAgent, error)
|
||||
Delete(id int64) error
|
||||
}
|
||||
|
||||
type FileDataRepository interface {
|
||||
OpenOrCreate(path string, user_agent string) (BogFileData, error)
|
||||
Delete(file_data *BogFileData)
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package domain
|
||||
|
||||
import "io"
|
||||
|
||||
type BogFileService struct {
|
||||
user_agent_repo UserAgentRepository
|
||||
file_data_repo FileDataRepository
|
||||
}
|
||||
|
||||
func (b *BogFileService) RecieveFile(src FileSource) error {
|
||||
f, err := b.file_data_repo.OpenOrCreate(src.Path(), src.UserAgent())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
io.Copy(f, src)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *BogFileService) SendFile(dst FileDestination) error {
|
||||
f, err := b.file_data_repo.OpenOrCreate(dst.Path(), dst.UserAgent())
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
io.Copy(dst, f)
|
||||
return nil
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
type FileSizeQuota struct {
|
||||
AllowanceKB int64
|
||||
CurrentUsage int64
|
||||
}
|
||||
|
||||
func (f *FileSizeQuota) Allows(size int64) bool {
|
||||
return f.CurrentUsage + size <= f.AllowanceKB
|
||||
}
|
||||
|
||||
func (f *FileSizeQuota) Add(size int64) {
|
||||
f.CurrentUsage += size
|
||||
}
|
||||
|
||||
func (f *FileSizeQuota) Remove(size int64) {
|
||||
f.CurrentUsage -= size
|
||||
}
|
||||
|
||||
type BogFileData interface {
|
||||
Path() string
|
||||
Size() int64
|
||||
Modified() time.Time
|
||||
io.Reader
|
||||
io.Seeker
|
||||
io.Writer
|
||||
io.Closer
|
||||
}
|
||||
|
||||
type FileSource struct {
|
||||
Path string
|
||||
UserAgent string
|
||||
Size int64
|
||||
Src io.ReadCloser
|
||||
}
|
||||
|
||||
type FileDestination struct {
|
||||
Path string
|
||||
UserAgent string
|
||||
Dst io.WriteCloser
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
module caj-larsson/bog
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.1.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.14.12 // indirect
|
||||
)
|
@ -0,0 +1,4 @@
|
||||
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
|
||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||
github.com/mattn/go-sqlite3 v1.14.12 h1:TJ1bhYJPV44phC+IMu1u2K/i5RriLTPe+yc68XDJ1Z0=
|
||||
github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
|
@ -0,0 +1,68 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"time"
|
||||
"os"
|
||||
"path"
|
||||
"caj-larsson/bog/domain"
|
||||
)
|
||||
|
||||
type FileSystemBogFileData struct {
|
||||
path string
|
||||
file *os.File
|
||||
}
|
||||
|
||||
|
||||
func (f FileSystemBogFileData) Read(p []byte) (int, error) {
|
||||
return f.file.Read(p)
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Write(p []byte) (int, error) {
|
||||
return f.file.Write(p)
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Close() error {
|
||||
return f.file.Close()
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Seek(offset int64, whence int) (int64, error) {
|
||||
return f.file.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Size() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (f FileSystemBogFileData) Modified() time.Time{
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
|
||||
type FileSystemBogRepository struct {
|
||||
Root string
|
||||
}
|
||||
|
||||
func (f FileSystemBogRepository) OpenOrCreate(open_path string, user_agent string) (domain.BogFileData, error) {
|
||||
//file_name := path.Base(open_path)
|
||||
abs_path := path.Join(f.Root, open_path)
|
||||
|
||||
dir := path.Dir(abs_path)
|
||||
os.MkdirAll(dir, 0750)
|
||||
file, err := os.OpenFile(abs_path, os.O_RDWR|os.O_CREATE, 0644)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bfd := FileSystemBogFileData {open_path, file}
|
||||
|
||||
return bfd, nil
|
||||
}
|
||||
|
||||
func (f FileSystemBogRepository) Delete(file *domain.BogFileData) {
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
"caj-larsson/bog/domain"
|
||||
)
|
||||
|
||||
type UserAgentDBRecord struct {
|
||||
ID int64
|
||||
Name string
|
||||
LastSeen string
|
||||
AllowanceSeconds int64
|
||||
QuotaKB int64
|
||||
QuotaUsedKB int64
|
||||
}
|
||||
|
||||
var ErrUnparseableRecord = errors.New("record could not be mapped to entity")
|
||||
|
||||
func (r *UserAgentDBRecord) toEntity() (*domain.UserAgent, error) {
|
||||
lastseen, err := time.Parse(time.RFC3339, r.LastSeen)
|
||||
|
||||
if err != nil {
|
||||
return nil, ErrUnparseableRecord
|
||||
}
|
||||
|
||||
var useragent = new(domain.UserAgent)
|
||||
useragent.ID = r.ID
|
||||
useragent.Name = r.Name
|
||||
useragent.LastSeen = lastseen
|
||||
useragent.AllowanceDuration = time.Duration(r.AllowanceSeconds * int64(time.Second))
|
||||
useragent.FileQuota = domain.FileSizeQuota { r.QuotaKB, r.QuotaUsedKB }
|
||||
return useragent, err
|
||||
}
|
||||
|
||||
func fromEntity(useragent domain.UserAgent) (*UserAgentDBRecord, error) {
|
||||
var record = new(UserAgentDBRecord)
|
||||
record.ID = useragent.ID
|
||||
record.Name = useragent.Name
|
||||
record.LastSeen = useragent.LastSeen.Format(time.RFC3339)
|
||||
record.AllowanceSeconds = int64(useragent.AllowanceDuration.Seconds())
|
||||
record.QuotaKB = useragent.FileQuota.AllowanceKB
|
||||
record.QuotaUsedKB = useragent.FileQuota.CurrentUsage
|
||||
return record, nil
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"caj-larsson/bog/domain"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type SQLiteUserAgentRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewSQLiteUserAgentRepository(db *sql.DB) *SQLiteUserAgentRepository {
|
||||
return &SQLiteUserAgentRepository{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *SQLiteUserAgentRepository) Migrate() error {
|
||||
query := `
|
||||
CREATE TABLE IF NOT EXISTS useragent(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
lastseen text,
|
||||
allowance_time bigint,
|
||||
quota_kb bigint,
|
||||
quota_usage_kb bigint
|
||||
);
|
||||
`
|
||||
|
||||
_, err := r.db.Exec(query)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *SQLiteUserAgentRepository) Create(useragent domain.UserAgent) (*domain.UserAgent, error) {
|
||||
var record, err = fromEntity(useragent)
|
||||
if err != nil {
|
||||
|
||||
}
|
||||
|
||||
res, err := r.db.Exec(
|
||||
"INSERT INTO useragent(name, lastseen, allowance_time, quota_kb, quota_usage_kb) values(?,?,?,?,?)",
|
||||
record.Name, record.LastSeen, record.AllowanceSeconds, record.QuotaKB, record.QuotaUsedKB,
|
||||
)
|
||||
if err != nil {
|
||||
var sqliteErr sqlite3.Error
|
||||
if errors.As(err, &sqliteErr) {
|
||||
if errors.Is(sqliteErr.ExtendedCode, sqlite3.ErrConstraintUnique) {
|
||||
return nil, domain.ErrDuplicate
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := res.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
useragent.ID = id
|
||||
|
||||
return &useragent, nil
|
||||
}
|
||||
|
||||
func (r *SQLiteUserAgentRepository) All() ([]domain.UserAgent, error) {
|
||||
rows, err := r.db.Query("SELECT * FROM useragent")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var all []domain.UserAgent
|
||||
for rows.Next() {
|
||||
var record UserAgentDBRecord
|
||||
if err := rows.Scan(&record.ID, &record.Name, &record.LastSeen, &record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var useragent, err = record.toEntity()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all = append(all, *useragent)
|
||||
}
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (r *SQLiteUserAgentRepository) GetByName(name string) (*domain.UserAgent, error) {
|
||||
row := r.db.QueryRow("SELECT id, name, lastseen, allowance_time, allowance_kb FROM useragent WHERE name = ?", name)
|
||||
|
||||
var record UserAgentDBRecord
|
||||
if err := row.Scan(&record.ID, &record.Name, &record.LastSeen, &record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB); err != nil {
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, domain.ErrNotExists
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var useragent, err = record.toEntity()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return useragent, nil
|
||||
}
|
||||
|
||||
func (r *SQLiteUserAgentRepository) Update(id int64, updated domain.UserAgent) (*domain.UserAgent, error) {
|
||||
if id == 0 {
|
||||
return nil, errors.New("invalid updated ID")
|
||||
}
|
||||
|
||||
var record, err = fromEntity(updated)
|
||||
|
||||
res, err := r.db.Exec("UPDATE useragent SET name = ?, lastseen = ?, allowance_time = ?, quota_kb = ?, quota_usage_kb = ? WHERE id = ?",
|
||||
record.Name, record.LastSeen, record.AllowanceSeconds, &record.QuotaKB, &record.QuotaUsedKB, id)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, domain.ErrUpdateFailed
|
||||
}
|
||||
|
||||
return &updated, nil
|
||||
}
|
||||
|
||||
func (r *SQLiteUserAgentRepository) Delete(id int64) error {
|
||||
res, err := r.db.Exec("DELETE FROM useragent WHERE id = ?", id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rowsAffected, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return domain.ErrDeleteFailed
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"caj-larsson/bog/application"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
func main() {
|
||||
content, err := ioutil.ReadFile("default.toml")
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
config, err := application.ConfigFromToml(string(content))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bog := application.New(config)
|
||||
bog.Run()
|
||||
}
|
Loading…
Reference in New Issue