working commit

This commit is contained in:
2026-01-28 13:06:04 +02:00
parent 6db319088e
commit d647854e98
13 changed files with 293 additions and 45 deletions
+8
View File
@@ -3,6 +3,7 @@ package config
type Config struct { type Config struct {
Service Service `json:"service" yaml:"service"` Service Service `json:"service" yaml:"service"`
Database Database `json:"database" yaml:"database"` Database Database `json:"database" yaml:"database"`
Storage Storage `json:"storage" yaml:"storage"`
} }
type Service struct { type Service struct {
@@ -14,6 +15,10 @@ type Database struct {
Basepath string `json:"basepath" yaml:"basepath"` Basepath string `json:"basepath" yaml:"basepath"`
} }
type Storage struct {
Basepath string `json:"basepath" yaml:"basepath"`
}
func NewConfig() (*Config, error) { func NewConfig() (*Config, error) {
var err error var err error
return &Config{ return &Config{
@@ -24,5 +29,8 @@ func NewConfig() (*Config, error) {
Database: Database{ Database: Database{
Basepath: datadir, Basepath: datadir,
}, },
Storage: Storage{
Basepath: datadir,
},
}, err }, err
} }
+1
View File
@@ -4,6 +4,7 @@ type File struct {
ID string `db:"id" json:"id,omitempty" yaml:"id,omitempty"` ID string `db:"id" json:"id,omitempty" yaml:"id,omitempty"`
Collection string `db:"collection" json:"collection,omitempty" yaml:"collection,omitempty"` Collection string `db:"collection" json:"collection,omitempty" yaml:"collection,omitempty"`
Name string `db:"name" json:"name,omitempty" yaml:"name,omitempty"` Name string `db:"name" json:"name,omitempty" yaml:"name,omitempty"`
Type string `db:"type" json:"type,omitempty" yaml:"type,omitempty"`
Checksum string `db:"checksum" json:"checksum,omitempty" yaml:"checksum,omitempty"` Checksum string `db:"checksum" json:"checksum,omitempty" yaml:"checksum,omitempty"`
Size int64 `db:"size" json:"size,omitempty" yaml:"size,omitempty"` Size int64 `db:"size" json:"size,omitempty" yaml:"size,omitempty"`
CreatedAt string `db:"created_at" json:"createdAt,omitempty" yaml:"createdAt,omitempty"` CreatedAt string `db:"created_at" json:"createdAt,omitempty" yaml:"createdAt,omitempty"`
+17 -10
View File
@@ -6,7 +6,7 @@ import (
) )
func (hand *Handler) FileExists(rctx *router.Context) { func (hand *Handler) FileExists(rctx *router.Context) {
hand.logg.Debugf("handle FileExists") hand.logg.Debugf("Handle FileExists")
filepath := rctx.PathMap["filepath"] filepath := rctx.PathMap["filepath"]
params := &operator.FileExistsParams{ params := &operator.FileExistsParams{
@@ -19,21 +19,28 @@ func (hand *Handler) FileExists(rctx *router.Context) {
} }
func (hand *Handler) PutFile(rctx *router.Context) { func (hand *Handler) PutFile(rctx *router.Context) {
hand.logg.Debugf("handle PutFile") hand.logg.Debugf("Handle PutFile")
contentLength := rctx.GetHeader("Content-Length")
contentType := rctx.GetHeader("Content-Type")
filepath := rctx.PathMap["filepath"] filepath := rctx.PathMap["filepath"]
params := &operator.PutFileParams{
Filepath: filepath,
Source: rctx.Request.Body,
}
hand.logg.Debugf("filepath: %s", filepath)
code, _, _ := hand.oper.PutFile(params) params := &operator.PutFileParams{
Filepath: filepath,
ContentLength: contentLength,
ContentType: contentType,
Source: rctx.Request.Body,
}
code, res, err := hand.oper.PutFile(params)
if err != nil {
hand.logg.Errorf("Error: %v", err)
}
rctx.SetStatus(code) rctx.SetStatus(code)
hand.SendResult(rctx, res)
} }
func (hand *Handler) GetFile(rctx *router.Context) { func (hand *Handler) GetFile(rctx *router.Context) {
hand.logg.Debugf("handle GetFile") hand.logg.Debugf("Handle GetFile")
filepath := rctx.PathMap["filepath"] filepath := rctx.PathMap["filepath"]
params := &operator.GetFileParams{ params := &operator.GetFileParams{
@@ -48,7 +55,7 @@ func (hand *Handler) GetFile(rctx *router.Context) {
} }
func (hand *Handler) DeleteFile(rctx *router.Context) { func (hand *Handler) DeleteFile(rctx *router.Context) {
hand.logg.Debugf("handle DeleteFile") hand.logg.Debugf("Handle DeleteFile")
filepath := rctx.PathMap["filepath"] filepath := rctx.PathMap["filepath"]
params := &operator.DeleteFileParams{ params := &operator.DeleteFileParams{
+1 -1
View File
@@ -6,7 +6,7 @@ import (
) )
func (hand *Handler) SendHello(rctx *router.Context) { func (hand *Handler) SendHello(rctx *router.Context) {
hand.logg.Debugf("handle sendHello") hand.logg.Debugf("Handle SendHello")
params := &operator.SendHelloParams{} params := &operator.SendHelloParams{}
res, _ := hand.oper.SendHello(params) res, _ := hand.oper.SendHello(params)
+7 -7
View File
@@ -6,9 +6,9 @@ import (
func (db *Database) InsertFile(file *descr.File) error { func (db *Database) InsertFile(file *descr.File) error {
var err error var err error
request := `INSERT INTO file(id, collection, name, checksum, size, created_at, updated_at, created_by, updated_by) request := `INSERT INTO file(id, collection, name, type, checksum, size, created_at, updated_at, created_by, updated_by)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)` VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`
_, err = db.db.Exec(request, file.ID, file.Collection, file.Name, file.Checksum, file.Size, _, err = db.db.Exec(request, file.ID, file.Collection, file.Name, file.Type, file.Checksum, file.Size,
file.CreatedAt, file.UpdatedAt, file.CreatedBy, file.UpdatedBy) file.CreatedAt, file.UpdatedAt, file.CreatedBy, file.UpdatedBy)
if err != nil { if err != nil {
return err return err
@@ -18,10 +18,10 @@ func (db *Database) InsertFile(file *descr.File) error {
func (db *Database) UpdateFileByID(fileID string, file *descr.File) error { func (db *Database) UpdateFileByID(fileID string, file *descr.File) error {
var err error var err error
request := `UPDATE file SET id = $1, collection = $2, name = $3, checksum = $4, request := `UPDATE file SET id = $1, collection = $2, name = $3, type = $4, checksum = $5,
size = $5, updated_at = $6, created_by = $7, updated_by = $8 size = $6, updated_at = $7, created_by = $8, updated_by = $9
WHERE id = $9` WHERE id = $10`
_, err = db.db.Exec(request, file.ID, file.Collection, file.Name, file.Checksum, _, err = db.db.Exec(request, file.ID, file.Collection, file.Name, file.Type, file.Checksum,
file.Size, file.UpdatedAt, file.CreatedBy, file.UpdatedBy, fileID) file.Size, file.UpdatedAt, file.CreatedBy, file.UpdatedBy, fileID)
if err != nil { if err != nil {
return err return err
+4
View File
@@ -30,6 +30,7 @@ func TestFile(t *testing.T) {
ID: id, ID: id,
Collection: "foo", Collection: "foo",
Name: "bare", Name: "bare",
Type: "application/octet-stream",
CreatedAt: timenow, CreatedAt: timenow,
UpdatedAt: timenow, UpdatedAt: timenow,
CreatedBy: creator, CreatedBy: creator,
@@ -43,4 +44,7 @@ func TestFile(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, len(files), 1) require.Equal(t, len(files), 1)
require.Equal(t, files[0].ID, id) require.Equal(t, files[0].ID, id)
require.Equal(t, files[0].Type, "application/octet-stream")
require.Equal(t, files[0].Name, "bare")
require.Equal(t, files[0].Collection, "foo")
} }
+1
View File
@@ -5,6 +5,7 @@ const schema = `
id VARCHAR(255) NOT NULL, id VARCHAR(255) NOT NULL,
collection VARCHAR(255) NOT NULL, collection VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
type VARCHAR(255) NOT NULL,
checksum VARCHAR(255) NOT NULL, checksum VARCHAR(255) NOT NULL,
size INTEGER, size INTEGER,
created_at VARCHAR(255) NOT NULL, created_at VARCHAR(255) NOT NULL,
+87 -8
View File
@@ -3,7 +3,12 @@ package operator
import ( import (
"io" "io"
"net/http" "net/http"
//"path" "path"
"strconv"
"mstore/app/descr"
"mstore/pkg/auxtool"
"mstore/pkg/auxuuid"
) )
// File exists // File exists
@@ -12,16 +17,23 @@ type FileExistsParams struct {
Source string Source string
Dest string Dest string
} }
type FileExistsResult struct{} type FileExistsResult struct {
Descr *descr.File
}
func (oper *Operator) FileExists(param *FileExistsParams) (int, *FileExistsResult, error) { func (oper *Operator) FileExists(param *FileExistsParams) (int, *FileExistsResult, error) {
var err error var err error
code := http.StatusNotFound
//filename := path.Base(param.Filepath)
//dirname := path.Dir(filepath)
res := &FileExistsResult{} res := &FileExistsResult{}
code := http.StatusOK
filename := path.Base(param.Filepath)
collection := path.Dir(param.Filepath)
exist, file, err := oper.mdb.GetFileByCollection(collection, filename)
if exist {
code = http.StatusOK
res.Descr = file
}
return code, res, err return code, res, err
} }
@@ -32,11 +44,78 @@ type PutFileParams struct {
Filepath string Filepath string
Source io.ReadCloser Source io.ReadCloser
} }
type PutFileResult struct{} type PutFileResult struct {
Descr *descr.File
}
const defaultContentType = "application/octet-stream"
func (oper *Operator) PutFile(param *PutFileParams) (int, *PutFileResult, error) { func (oper *Operator) PutFile(param *PutFileParams) (int, *PutFileResult, error) {
var err error var err error
res := &PutFileResult{} res := &PutFileResult{}
size, err := strconv.ParseInt(param.ContentLength, 10, 64)
if err != nil {
code := http.StatusLengthRequired
return code, res, err
}
contentType := param.ContentType
if contentType == "" {
contentType = defaultContentType
}
filename := path.Base(param.Filepath)
collection := path.Dir(param.Filepath)
oper.logg.Debugf("Put file %s %s", collection, filename)
tmpname, size, checksum, err := oper.store.WriteTempFile(param.Source)
if err != nil {
code := http.StatusInternalServerError
return code, res, err
}
exists, fileDescr, err := oper.mdb.GetFileByCollection(collection, filename)
if err != nil {
code := http.StatusInternalServerError
return code, res, err
}
now := auxtool.TimeNow()
if exists {
fileDescr.Size = size
fileDescr.Checksum = checksum
fileDescr.UpdatedAt = now
fileDescr.Type = contentType
err = oper.mdb.UpdateFileByID(fileDescr.ID, fileDescr)
if err != nil {
code := http.StatusInternalServerError
return code, res, err
}
} else {
fileDescr = &descr.File{
ID: auxuuid.NewUUID(),
Name: filename,
Collection: collection,
Size: size,
Type: contentType,
Checksum: checksum,
CreatedAt: now,
UpdatedAt: now,
}
err = oper.mdb.InsertFile(fileDescr)
if err != nil {
code := http.StatusInternalServerError
return code, res, err
}
}
err = oper.store.LinkFile(tmpname, collection, filename)
if err != nil {
code := http.StatusInternalServerError
return code, res, err
}
res.Descr = fileDescr
code := http.StatusOK code := http.StatusOK
return code, res, err return code, res, err
} }
+9 -2
View File
@@ -1,21 +1,28 @@
package operator package operator
import ( import (
"mstore/app/logger"
"mstore/app/maindb" "mstore/app/maindb"
"mstore/app/storage"
) )
type OperatorParams struct { type OperatorParams struct {
MainDB *maindb.Database MainDB *maindb.Database
Store *storage.Storage
} }
type Operator struct { type Operator struct {
maindb *maindb.Database mdb *maindb.Database
store *storage.Storage
logg *logger.Logger
} }
func NewOperator(params *OperatorParams) (*Operator, error) { func NewOperator(params *OperatorParams) (*Operator, error) {
var err error var err error
oper := &Operator{ oper := &Operator{
maindb: params.MainDB, mdb: params.MainDB,
store: params.Store,
} }
oper.logg = logger.NewLogger("operator")
return oper, err return oper, err
} }
+1 -1
View File
@@ -43,5 +43,5 @@ func (rctx *Context) SendText(payload string) {
} }
func (rctx *Context) GetHeader(key string) string { func (rctx *Context) GetHeader(key string) string {
return rctx.Request.URL.Query().Get(key) return rctx.Request.Header.Get(key)
} }
+33 -11
View File
@@ -18,15 +18,17 @@ import (
"mstore/app/maindb" "mstore/app/maindb"
"mstore/app/operator" "mstore/app/operator"
"mstore/app/service" "mstore/app/service"
"mstore/app/storage"
) )
type Server struct { type Server struct {
conf *config.Config conf *config.Config
oper *operator.Operator oper *operator.Operator
svc *service.Service svc *service.Service
maindb *maindb.Database mdb *maindb.Database
hand *handler.Handler hand *handler.Handler
logg *logger.Logger logg *logger.Logger
stor *storage.Storage
} }
func NewServer() (*Server, error) { func NewServer() (*Server, error) {
@@ -56,31 +58,49 @@ func (srv *Server) Build() error {
srv.logg.Infof("Server build") srv.logg.Infof("Server build")
// Database create // Database create
srv.logg.Infof("Create database directory")
dbdir := srv.conf.Database.Basepath dbdir := srv.conf.Database.Basepath
err = os.MkdirAll(srv.conf.Database.Basepath, 0750) err = os.MkdirAll(dbdir, 0750)
if err != nil { if err != nil {
return err return err
} }
maindb := maindb.NewDatabase(dbdir) mdb := maindb.NewDatabase(dbdir)
err = maindb.OpenDatabase() srv.logg.Infof("Open database")
err = mdb.OpenDatabase()
if err != nil { if err != nil {
return err return err
} }
err = maindb.InitDatabase() srv.logg.Infof("Initialize database")
err = mdb.InitDatabase()
if err != nil { if err != nil {
return err return err
} }
srv.mdb = mdb
// Storage create
srv.logg.Infof("Create storage directory")
datadir := srv.conf.Database.Basepath
err = os.MkdirAll(datadir, 0750)
if err != nil {
return err
}
srv.logg.Infof("Create storage")
store := storage.NewStorage(datadir)
srv.stor = store
// Operator create // Operator create
srv.logg.Infof("Create operator")
operatorParams := &operator.OperatorParams{ operatorParams := &operator.OperatorParams{
MainDB: srv.maindb, MainDB: srv.mdb,
Store: srv.stor,
} }
srv.oper, err = operator.NewOperator(operatorParams) srv.oper, err = operator.NewOperator(operatorParams)
if err != nil { if err != nil {
return err return err
} }
// Handler create // Handler create
srv.logg.Infof("Create handler")
handlerParams := &handler.HandlerParams{ handlerParams := &handler.HandlerParams{
Operator: srv.oper, Operator: srv.oper,
} }
@@ -92,6 +112,7 @@ func (srv *Server) Build() error {
serviceParams := &service.ServiceParams{ serviceParams := &service.ServiceParams{
Handler: srv.hand, Handler: srv.hand,
} }
srv.logg.Infof("Create service")
srv.svc, err = service.NewService(serviceParams) srv.svc, err = service.NewService(serviceParams)
if err != nil { if err != nil {
return err return err
@@ -118,6 +139,7 @@ func (srv *Server) Run() error {
svcDone := make(chan error, 1) svcDone := make(chan error, 1)
// Service run // Service run
srv.logg.Infof("Start service")
startService := func(svc *service.Service, done chan error) { startService := func(svc *service.Service, done chan error) {
err = svc.Run() err = svc.Run()
if err != nil { if err != nil {
+75
View File
@@ -1 +1,76 @@
package storage package storage
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"path/filepath"
"mstore/app/logger"
"mstore/pkg/auxuuid"
)
type Storage struct {
basepath string
logg *logger.Logger
}
func NewStorage(basepath string) *Storage {
res := &Storage{
basepath: basepath,
}
res.logg = logger.NewLogger("storage")
return res
}
func (store *Storage) LinkFile(tmpname, collection, filename string) error {
var err error
dirname := filepath.Join(store.basepath, collection)
err = os.MkdirAll(dirname, 0750)
if err != nil {
return err
}
filename = filepath.Join(store.basepath, collection, filename)
os.Remove(filename)
err = os.Link(tmpname, filename)
if err != nil {
return err
}
err = os.Remove(tmpname)
if err != nil {
return err
}
return err
}
func (store *Storage) WriteTempFile(source io.Reader) (string, int64, string, error) {
var err error
var size int64
var digest string
tmpname := auxuuid.NewUUID()
tmpname = fmt.Sprintf("file-%s.tmp", tmpname)
tmppath := filepath.Join(store.basepath, tmpname)
file, err := os.OpenFile(tmppath, os.O_WRONLY|os.O_CREATE, 0640)
if err != nil {
return tmppath, size, digest, err
}
defer file.Close()
hasher := sha256.New()
writer := io.MultiWriter(file, hasher)
size, err = io.Copy(writer, source)
if err != nil {
return tmppath, size, digest, err
}
digest = hex.EncodeToString(hasher.Sum(nil))
digest = fmt.Sprintf("sha256:%s", digest)
return tmppath, size, digest, err
}
+49 -5
View File
@@ -1,20 +1,21 @@
package test package test
import ( import (
"github.com/stretchr/testify/require"
"bytes"
"fmt" "fmt"
"io" "io"
"math/rand"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"testing" "testing"
"mstore/app/router" "mstore/app/router"
"mstore/app/server" "mstore/app/server"
"github.com/stretchr/testify/require"
) )
func MakeServer(t *testing.T) *server.Server { func MakeServer(t *testing.T) *server.Server {
var err error var err error
srv, err := server.NewServer() srv, err := server.NewServer()
require.NoError(t, err) require.NoError(t, err)
@@ -28,7 +29,10 @@ func MakeServer(t *testing.T) *server.Server {
return srv return srv
} }
func TestFileExists(t *testing.T) { func TestFileLife(t *testing.T) {
}
func xxxTestFileExists(t *testing.T) {
var err error var err error
srv := MakeServer(t) srv := MakeServer(t)
@@ -46,6 +50,46 @@ func TestFileExists(t *testing.T) {
request, err := http.NewRequest("HEAD", reqPath, nil) request, err := http.NewRequest("HEAD", reqPath, nil)
require.NoError(t, err) require.NoError(t, err)
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusNotFound, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
}
func TestPutFile(t *testing.T) {
var err error
srv := MakeServer(t)
require.NotNil(t, srv)
reqPath := `/v3/api/file/foo/bare`
routePath := `/v3/api/file/{filepath}`
rout := router.NewRouter()
hand := srv.Handler()
require.NotNil(t, hand)
rout.Put(routePath, hand.PutFile)
datasize := 16
filedata := make([]byte, datasize)
_, err = rand.Read(filedata)
require.NoError(t, err)
source := bytes.NewReader(filedata)
request, err := http.NewRequest("PUT", reqPath, source)
require.NoError(t, err)
request.Header.Set("Content-Length", fmt.Sprintf("%d", datasize))
request.Header.Set("Content-Type", "application/octet-stream")
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request) rout.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code) require.Equal(t, http.StatusOK, recorder.Code)
@@ -58,7 +102,7 @@ func TestFileExists(t *testing.T) {
fmt.Printf("Response body: %s\n", string(bodyBytes)) fmt.Printf("Response body: %s\n", string(bodyBytes))
} }
func TestServiceHello(t *testing.T) { func xxxTestServiceHello(t *testing.T) {
var err error var err error
srv := MakeServer(t) srv := MakeServer(t)