64
internal/config/config.go
Normal file
64
internal/config/config.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package config
|
||||
|
||||
import "time"
|
||||
|
||||
type AppConfig struct{}
|
||||
|
||||
type ServerConfig struct {
|
||||
Name string `json:"name" yaml:"name"`
|
||||
Version string `json:"-" yaml:"-"`
|
||||
Addr string `json:"addr" yaml:"addr" default:":9090"`
|
||||
Crt string `json:"crt" yaml:"crt"`
|
||||
Key string `json:"key" yaml:"key"`
|
||||
ID string `json:"-" yaml:"-" default:"micro:generate uuid"`
|
||||
LoggerLevel string `json:"logger_level" yaml:"logger_level"`
|
||||
}
|
||||
|
||||
type TracerConfig struct {
|
||||
Metadata map[string]string `json:"metadata" yaml:"metadata"`
|
||||
AgentHost string `env:"JAEGER_AGENT_HOST" json:"host" yaml:"host" default:"127.0.0.1"`
|
||||
AgentPort string `env:"JAEGER_AGENT_PORT" json:"port" yaml:"port" default:"6831"`
|
||||
Collector string `env:"JAEGER_ENDPOINT,TRACER_ENDPOINT" json:"endpoint" yaml:"endpoint"`
|
||||
}
|
||||
|
||||
type VaultConfig struct {
|
||||
Addr string `env:"VAULT_ADDR" json:"addr" yaml:"addr" default:"http://127.0.0.1:8200"`
|
||||
Token string `env:"VAULT_TOKEN" json:"-" yaml:"-"`
|
||||
Path string `env:"VAULT_PATH" json:"-" yaml:"-" default:"apigw/data/authn"`
|
||||
}
|
||||
|
||||
type MeterConfig struct {
|
||||
Addr string `json:"addr" yaml:"addr" default:"0.0.0.0:8080"`
|
||||
Path string `json:"path" yaml:"path" default:"/metrics"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
DSN string `json:"dsn" yaml:"dsn"`
|
||||
Type string `json:"-" yaml:"-"`
|
||||
Migrate string `json:"-" yaml:"-"`
|
||||
ConnStr string `json:"-" yaml:"-"`
|
||||
MaxOpenConns int `json:"-" yaml:"-"`
|
||||
MaxIdleConns int `json:"-" yaml:"-"`
|
||||
ConnMaxLifetime time.Duration `json:"-" yaml:"-"`
|
||||
ConnMaxIdleTime time.Duration `json:"-" yaml:"-"`
|
||||
MigrateForce bool `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
App *AppConfig `json:"app" yaml:"app"`
|
||||
Database *DatabaseConfig `json:"database" yaml:"database"`
|
||||
Server *ServerConfig `json:"server" yaml:"server"`
|
||||
Meter *MeterConfig `json:"meter" yaml:"meter"`
|
||||
Vault *VaultConfig `json:"-" yaml:"-"`
|
||||
Tracer *TracerConfig `json:"tracer" yaml:"tracer"`
|
||||
}
|
||||
|
||||
func NewConfig(name, version string) *Config {
|
||||
return &Config{
|
||||
App: &AppConfig{},
|
||||
Server: &ServerConfig{Name: name, Version: version},
|
||||
Tracer: &TracerConfig{},
|
||||
Meter: &MeterConfig{},
|
||||
Vault: &VaultConfig{},
|
||||
}
|
||||
}
|
||||
40
internal/handler/comments_create.go
Normal file
40
internal/handler/comments_create.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) CommentsCreate(ctx context.Context, req *pb.CommentsCreateReq, rsp *pb.CommentsCreateRsp) error {
|
||||
logger := h.svc.Logger()
|
||||
logger.Debug(ctx, "Start AddComment")
|
||||
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
|
||||
return httpsrv.SetError(NewValidationError(err))
|
||||
}
|
||||
|
||||
var com *models.Comment
|
||||
if com, err = h.store.CommentsCreate(ctx, req); err != nil {
|
||||
logger.Error(ctx, err)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpsrv.SetRspCode(ctx, http.StatusNotFound)
|
||||
return httpsrv.SetError(NewNotFoundError(err))
|
||||
}
|
||||
httpsrv.SetRspCode(ctx, http.StatusInternalServerError)
|
||||
return httpsrv.SetError(NewInternalError(err))
|
||||
}
|
||||
|
||||
rsp.Comment = models.NewComment(com)
|
||||
|
||||
logger.Debug(ctx, "Success finish addComment")
|
||||
return nil
|
||||
}
|
||||
36
internal/handler/comments_delete.go
Normal file
36
internal/handler/comments_delete.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) CommentsDelete(ctx context.Context, req *pb.CommentsDeleteReq, rsp *pb.CommentsDeleteRsp) error {
|
||||
logger := h.svc.Logger()
|
||||
logger.Debug(ctx, "Start AddComment")
|
||||
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
|
||||
return httpsrv.SetError(NewValidationError(err))
|
||||
}
|
||||
|
||||
if err = h.store.CommentsDelete(ctx, req); err != nil {
|
||||
logger.Error(ctx, err)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
httpsrv.SetRspCode(ctx, http.StatusNotFound)
|
||||
return httpsrv.SetError(NewNotFoundError(err))
|
||||
}
|
||||
httpsrv.SetRspCode(ctx, http.StatusInternalServerError)
|
||||
return httpsrv.SetError(NewInternalError(err))
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "Success finish addComment")
|
||||
return nil
|
||||
}
|
||||
36
internal/handler/comments_list.go
Normal file
36
internal/handler/comments_list.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) CommentsList(ctx context.Context, req *pb.CommentsListReq, rsp *pb.CommentsListRsp) error {
|
||||
logger := h.svc.Logger()
|
||||
logger.Debug(ctx, "Start GetModule")
|
||||
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
|
||||
return httpsrv.SetError(NewValidationError(err))
|
||||
}
|
||||
|
||||
comments, err := h.store.CommentsList(ctx, req)
|
||||
if err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusInternalServerError)
|
||||
return httpsrv.SetError(NewInternalError(err))
|
||||
}
|
||||
|
||||
for _, com := range comments {
|
||||
rsp.Comments = append(rsp.Comments, models.NewComment(com))
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "Success finish getModule")
|
||||
return nil
|
||||
}
|
||||
11
internal/handler/comments_lookup.go
Normal file
11
internal/handler/comments_lookup.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) CommentsLookup(ctx context.Context, req *pb.CommentsLookupReq, rsp *pb.CommentsLookupRsp) error {
|
||||
return nil
|
||||
}
|
||||
76
internal/handler/handler.go
Normal file
76
internal/handler/handler.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"go.unistack.org/micro/v4"
|
||||
cligit "git.unistack.org/unistack-org/pkgdash/internal/service/client_git"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/storage"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
svc micro.Service
|
||||
store storage.Storage
|
||||
|
||||
protojson.MarshalOptions
|
||||
protojson.UnmarshalOptions
|
||||
|
||||
git cligit.Client
|
||||
chanUrl chan *pb.PackagesCreateReq
|
||||
}
|
||||
|
||||
func NewNotFoundError(err error) *pb.ErrorRsp {
|
||||
return &pb.ErrorRsp{
|
||||
Code: strconv.Itoa(http.StatusBadRequest),
|
||||
Title: "NotFound",
|
||||
Uuid: uuid.New().String(),
|
||||
Details: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewInternalError(err error) *pb.ErrorRsp {
|
||||
return &pb.ErrorRsp{
|
||||
Code: strconv.Itoa(http.StatusInternalServerError),
|
||||
Title: "InternalServerError",
|
||||
Uuid: uuid.New().String(),
|
||||
Details: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewValidationError(err error) *pb.ErrorRsp {
|
||||
return &pb.ErrorRsp{
|
||||
Code: strconv.Itoa(http.StatusBadRequest),
|
||||
Title: "BadRequest",
|
||||
Uuid: uuid.New().String(),
|
||||
Details: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func NewHandler(svc micro.Service, client cligit.Client) *Handler {
|
||||
h := &Handler{
|
||||
svc: svc,
|
||||
git: client,
|
||||
}
|
||||
h.EmitUnpopulated = true
|
||||
h.UseProtoNames = false
|
||||
return h
|
||||
}
|
||||
|
||||
func (h *Handler) Init(ctx context.Context) error {
|
||||
store, err := storage.FromContext(h.svc.Options().Context)
|
||||
if err != nil {
|
||||
return errors.New("missing storage")
|
||||
}
|
||||
|
||||
h.chanUrl = h.git.Run(ctx, store)
|
||||
|
||||
h.store = store
|
||||
|
||||
return nil
|
||||
}
|
||||
35
internal/handler/modules_list.go
Normal file
35
internal/handler/modules_list.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) ModulesList(ctx context.Context, req *pb.ModulesListReq, rsp *pb.ModulesListRsp) error {
|
||||
logger := h.svc.Logger()
|
||||
logger.Debug(ctx, "Start GetModule")
|
||||
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
|
||||
return httpsrv.SetError(NewValidationError(err))
|
||||
}
|
||||
|
||||
modules, err := h.store.ModulesList(ctx, req)
|
||||
if err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusInternalServerError)
|
||||
return httpsrv.SetError(NewInternalError(err))
|
||||
}
|
||||
|
||||
for _, mod := range modules {
|
||||
rsp.Modules = append(rsp.Modules, models.NewModule(mod))
|
||||
}
|
||||
logger.Debug(ctx, "Success finish getModule")
|
||||
return nil
|
||||
}
|
||||
32
internal/handler/packages_create.go
Normal file
32
internal/handler/packages_create.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) PackagesCreate(ctx context.Context, req *pb.PackagesCreateReq, rsp *pb.PackagesCreateRsp) error {
|
||||
logger := h.svc.Logger()
|
||||
logger.Debug(ctx, "Start AddPackage")
|
||||
|
||||
err := req.Validate()
|
||||
if err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
|
||||
return httpsrv.SetError(NewValidationError(err))
|
||||
}
|
||||
|
||||
if h.git.IsClose() {
|
||||
logger.Error(ctx, "chan is closed")
|
||||
} else {
|
||||
h.chanUrl <- req
|
||||
}
|
||||
|
||||
rsp.Status = "Sent"
|
||||
|
||||
logger.Debug(ctx, "Success finish addPackage")
|
||||
return nil
|
||||
}
|
||||
29
internal/handler/packages_delete.go
Normal file
29
internal/handler/packages_delete.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) PackagesDelete(ctx context.Context, req *pb.PackagesDeleteReq, rsp *pb.PackagesDeleteRsp) error {
|
||||
logger := h.svc.Logger()
|
||||
logger.Debug(ctx, "Start UpdatePackage")
|
||||
|
||||
if err := req.Validate(); err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
|
||||
return httpsrv.SetError(NewValidationError(err))
|
||||
}
|
||||
|
||||
if err := h.store.PackagesDelete(ctx, req); err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusInternalServerError)
|
||||
return httpsrv.SetError(NewInternalError(err))
|
||||
}
|
||||
|
||||
logger.Debug(ctx, "Success finish UpdatePackage")
|
||||
return nil
|
||||
}
|
||||
28
internal/handler/packages_list.go
Normal file
28
internal/handler/packages_list.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) PackagesList(ctx context.Context, req *pb.PackagesListReq, rsp *pb.PackagesListRsp) error {
|
||||
logger := h.svc.Logger()
|
||||
logger.Debug(ctx, "Start getListPackage")
|
||||
|
||||
packages, err := h.store.PackagesList(ctx, req)
|
||||
if err != nil {
|
||||
logger.Errorf(ctx, "error db response: %v", err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusInternalServerError)
|
||||
return httpsrv.SetError(NewInternalError(err))
|
||||
}
|
||||
|
||||
for _, pkg := range packages {
|
||||
rsp.Packages = append(rsp.Packages, models.NewPackage(pkg))
|
||||
}
|
||||
logger.Debug(ctx, "Success finish getListPackage")
|
||||
return nil
|
||||
}
|
||||
31
internal/handler/packages_update.go
Normal file
31
internal/handler/packages_update.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func (h *Handler) PackagesUpdate(ctx context.Context, req *pb.PackagesUpdateReq, rsp *pb.PackagesUpdateRsp) error {
|
||||
logger := h.svc.Logger()
|
||||
logger.Debug(ctx, "Start UpdatePackage")
|
||||
|
||||
if err := req.Validate(); err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
|
||||
return httpsrv.SetError(NewValidationError(err))
|
||||
}
|
||||
|
||||
if err := h.store.PackagesUpdate(ctx, req); err != nil {
|
||||
logger.Error(ctx, err)
|
||||
httpsrv.SetRspCode(ctx, http.StatusInternalServerError)
|
||||
return httpsrv.SetError(NewInternalError(err))
|
||||
}
|
||||
|
||||
// rsp.Id = req.Id
|
||||
|
||||
logger.Debug(ctx, "Success finish UpdatePackage")
|
||||
return nil
|
||||
}
|
||||
78
internal/models/models.go
Normal file
78
internal/models/models.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
)
|
||||
|
||||
type Package struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
URL string `db:"url" json:"url"`
|
||||
Modules []uint64 `db:"modules" json:"modules"`
|
||||
Issues []uint64 `db:"issues" json:"issues,omitempty"`
|
||||
Comments []uint64 `db:"comments" json:"comments,omitempty"`
|
||||
ID uint64 `db:"id" json:"id"`
|
||||
Created time.Time `db:"created" json:"created"`
|
||||
Updated time.Time `db:"updated" json:"updated,omitempty"`
|
||||
}
|
||||
|
||||
func NewPackage(pkg *Package) *pb.Package {
|
||||
return &pb.Package{
|
||||
Name: pkg.Name,
|
||||
Url: pkg.URL,
|
||||
Modules: pkg.Modules,
|
||||
Issues: pkg.Issues,
|
||||
Comments: pkg.Comments,
|
||||
Id: pkg.ID,
|
||||
Created: timestamppb.New(pkg.Created),
|
||||
Updated: timestamppb.New(pkg.Updated),
|
||||
}
|
||||
}
|
||||
|
||||
type Module struct {
|
||||
Name string `db:"name"`
|
||||
Version string `db:"version"`
|
||||
LastVersion string `db:"last_version"`
|
||||
ID uint64 `db:"id"`
|
||||
Package uint64 `db:"package"`
|
||||
Created time.Time `db:"created" json:"created"`
|
||||
Updated time.Time `db:"updated" json:"updated,omitempty"`
|
||||
}
|
||||
|
||||
func NewModule(mod *Module) *pb.Module {
|
||||
return &pb.Module{
|
||||
Name: mod.Name,
|
||||
Version: mod.Version,
|
||||
LastVersion: mod.LastVersion,
|
||||
Package: mod.Package,
|
||||
Id: mod.ID,
|
||||
Created: timestamppb.New(mod.Created),
|
||||
Updated: timestamppb.New(mod.Updated),
|
||||
}
|
||||
}
|
||||
|
||||
type Issue struct {
|
||||
Desc string `db:"desc"`
|
||||
Modules []int64 `db:"modules"`
|
||||
ID uint64 `db:"id"`
|
||||
Status uint64 `db:"status"`
|
||||
Package int64 `db:"package"`
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
Created time.Time `db:"created" json:"created"`
|
||||
Updated time.Time `db:"updated" json:"updated,omitempty"`
|
||||
Text string `db:"value" json:"text"`
|
||||
ID uint64 `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func NewComment(com *Comment) *pb.Comment {
|
||||
return &pb.Comment{
|
||||
Id: com.ID,
|
||||
Text: com.Text,
|
||||
Created: timestamppb.New(com.Created),
|
||||
Updated: timestamppb.New(com.Updated),
|
||||
}
|
||||
}
|
||||
218
internal/service/client_git/client.go
Normal file
218
internal/service/client_git/client.go
Normal file
@@ -0,0 +1,218 @@
|
||||
package client_git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing/filemode"
|
||||
"github.com/go-git/go-git/v5/plumbing/object"
|
||||
"github.com/go-git/go-git/v5/storage/memory"
|
||||
"github.com/pkg/errors"
|
||||
"go.unistack.org/micro/v4/logger"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/storage"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
"golang.org/x/mod/modfile"
|
||||
"golang.org/x/mod/module"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
Run(ctx context.Context, st storage.Storage) chan *pb.PackagesCreateReq
|
||||
IsClose() bool
|
||||
Done() <-chan struct{}
|
||||
}
|
||||
|
||||
type client struct {
|
||||
worker chan *pb.PackagesCreateReq
|
||||
closed bool
|
||||
lock chan struct{}
|
||||
}
|
||||
|
||||
func NewClient(cap uint) Client {
|
||||
return &client{
|
||||
make(chan *pb.PackagesCreateReq, cap),
|
||||
false,
|
||||
make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) Run(ctx context.Context, st storage.Storage) chan *pb.PackagesCreateReq {
|
||||
go func() {
|
||||
defer close(c.worker)
|
||||
for {
|
||||
select {
|
||||
case data := <-c.worker:
|
||||
go func() {
|
||||
runner(ctx, st, data)
|
||||
}()
|
||||
case <-ctx.Done():
|
||||
logger.Info(ctx, ctx.Err())
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return c.worker
|
||||
}
|
||||
|
||||
func (c *client) IsClose() bool {
|
||||
return c.closed
|
||||
}
|
||||
|
||||
// Done for locked goroutine
|
||||
func (c *client) Done() <-chan struct{} {
|
||||
return c.lock
|
||||
}
|
||||
|
||||
func runner(ctx context.Context, st storage.Storage, req *pb.PackagesCreateReq) {
|
||||
modules, err := getGoModule(ctx, req.Url)
|
||||
if err != nil {
|
||||
logger.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
logger.Infof(ctx, "success get list mod", modules)
|
||||
|
||||
if req.Modules, err = st.InsertButchModules(ctx, modules); err != nil {
|
||||
logger.Error(ctx, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = st.PackagesCreate(ctx, req); err != nil {
|
||||
logger.Error(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
func getGoModule(ctx context.Context, gitUrl string) ([]models.Module, error) {
|
||||
u, err := url.Parse(gitUrl)
|
||||
if err != nil {
|
||||
logger.Fatal(ctx, err)
|
||||
}
|
||||
|
||||
var rev string
|
||||
if idx := strings.Index(u.Path, "@"); idx > 0 {
|
||||
rev = u.Path[idx+1:]
|
||||
}
|
||||
|
||||
cloneOpts := &git.CloneOptions{
|
||||
URL: gitUrl,
|
||||
Progress: os.Stdout,
|
||||
}
|
||||
|
||||
if len(rev) == 0 {
|
||||
cloneOpts.SingleBranch = true
|
||||
cloneOpts.Depth = 1
|
||||
}
|
||||
|
||||
if err = cloneOpts.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repo, err := git.CloneContext(ctx, memory.NewStorage(), nil, cloneOpts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref, err := repo.Head()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get head: %v", err)
|
||||
}
|
||||
|
||||
commit, err := repo.CommitObject(ref.Hash())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get commit: %v", err)
|
||||
}
|
||||
|
||||
tree, err := commit.Tree()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unique := make(map[string]models.Module)
|
||||
var mvs []module.Version
|
||||
err = tree.Files().ForEach(func(file *object.File) error {
|
||||
if file == nil {
|
||||
err = errors.New("file pointer is nil")
|
||||
logger.Error(ctx, err)
|
||||
return err
|
||||
}
|
||||
|
||||
switch file.Mode {
|
||||
case filemode.Regular:
|
||||
if strings.HasSuffix(file.Name, "go.mod") {
|
||||
if mvs, err = Direct(file); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range mvs {
|
||||
unique[mvs[i].Path] = models.Module{
|
||||
Name: mvs[i].Path,
|
||||
Version: mvs[i].Version,
|
||||
LastVersion: mvs[i].Version,
|
||||
}
|
||||
}
|
||||
internal.Updates(internal.UpdateOptions{
|
||||
Pre: false,
|
||||
Major: false,
|
||||
Cached: false,
|
||||
Modules: mvs,
|
||||
OnUpdate: func(u internal.Update) {
|
||||
if u.Err != nil {
|
||||
logger.Errorf(ctx, "%s: failed: %v\n", u.Module.Path, u.Err)
|
||||
} else {
|
||||
val := unique[u.Module.Path]
|
||||
val.LastVersion = u.Version
|
||||
unique[u.Module.Path] = val
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
result := make([]models.Module, 0, len(unique))
|
||||
for _, v := range unique {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Name < result[j].Name
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func Direct(file *object.File) ([]module.Version, error) {
|
||||
r, err := file.Reader()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Close()
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modfile, err := modfile.ParseLax("go.mod", data, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var mods []module.Version
|
||||
for _, req := range modfile.Require {
|
||||
// if !req.Indirect {
|
||||
mods = append(mods, req.Mod)
|
||||
//}
|
||||
}
|
||||
/*
|
||||
sort.Slice(mods, func(i, j int) bool {
|
||||
return mods[i].Path < mods[j].Path
|
||||
})
|
||||
*/
|
||||
return mods, nil
|
||||
}
|
||||
81
internal/service/client_git/client_test.go
Normal file
81
internal/service/client_git/client_test.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package client_git
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/storage"
|
||||
// "git.unistack.org/unistack-org/pkgdash/internal/storage/postgres"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/storage/sqlite"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func TestClientPG(t *testing.T) {
|
||||
dsn := fmt.Sprintf("file:///database.db")
|
||||
conn, err := sql.Open("sqlite", dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if err = conn.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fucntion := sqlite.NewStorage()
|
||||
st := fucntion(conn, embed.FS{})
|
||||
s, ok := st.(storage.Storage)
|
||||
if !ok {
|
||||
t.Fatal("typecast error")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
_ = cancel
|
||||
cli := NewClient(1)
|
||||
|
||||
ch := cli.Run(ctx, s)
|
||||
|
||||
data := &pb.AddPackageReq{
|
||||
Name: "test",
|
||||
Url: "https://github.com/dantedenis/service_history.git",
|
||||
}
|
||||
|
||||
ch <- data
|
||||
|
||||
<-cli.Done()
|
||||
}
|
||||
|
||||
func TestClientLite(t *testing.T) {
|
||||
conn, err := sql.Open("sqlite3", "../../identifier.sqlite")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if err = conn.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
function := sqlite.NewStorage()
|
||||
st := function(conn, embed.FS{})
|
||||
s, ok := st.(storage.Storage)
|
||||
if !ok {
|
||||
t.Fatal("typecast error")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
_ = cancel
|
||||
cli := NewClient(1)
|
||||
|
||||
ch := cli.Run(ctx, s)
|
||||
|
||||
data := &pb.AddPackageReq{
|
||||
Name: "test",
|
||||
Url: "https://github.com/dantedenis/service_history.git",
|
||||
}
|
||||
|
||||
ch <- data
|
||||
|
||||
<-cli.Done()
|
||||
}
|
||||
154
internal/service/service.go
Normal file
154
internal/service/service.go
Normal file
@@ -0,0 +1,154 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
httpsrv "go.unistack.org/micro-server-http/v4" // TODO
|
||||
"go.unistack.org/micro/v4"
|
||||
"go.unistack.org/micro/v4/config"
|
||||
microcfg "go.unistack.org/micro/v4/config"
|
||||
"go.unistack.org/micro/v4/logger"
|
||||
"go.unistack.org/micro/v4/options"
|
||||
"go.unistack.org/micro/v4/register"
|
||||
"go.unistack.org/micro/v4/server"
|
||||
intcfg "git.unistack.org/unistack-org/pkgdash/config"
|
||||
"git.unistack.org/unistack-org/pkgdash/handler"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
"git.unistack.org/unistack-org/pkgdash/service/client_git"
|
||||
"git.unistack.org/unistack-org/pkgdash/storage"
|
||||
)
|
||||
|
||||
func NewService(ctx context.Context) (micro.Service, error) {
|
||||
var reg register.Register
|
||||
|
||||
cfg := intcfg.NewConfig(ServiceName, Service)
|
||||
|
||||
cs := microcfg.NewConfig(config.Struct(cfg))
|
||||
|
||||
// TODO
|
||||
mgsrv := httpsrv.NewServer(
|
||||
options.Register(reg),
|
||||
)
|
||||
|
||||
svc := micro.NewService(
|
||||
micro.Config(cs),
|
||||
)
|
||||
|
||||
h := handler.NewHandler(svc, client_git.NewClient(5))
|
||||
|
||||
if err := svc.Init(
|
||||
micro.AfterStart(func(_ context.Context) error {
|
||||
return h.Init(svc.Options().Context)
|
||||
}),
|
||||
micro.BeforeStart(func(ctx context.Context) error {
|
||||
if err := config.Load(ctx, []config.Config{cs}, config.LoadOverride(true)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := config.Validate(ctx, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := svc.Init(
|
||||
micro.Name(intcfg.ServiceName),
|
||||
micro.Version(intcfg.ServiceVersion),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := svc.Server("http").Init(
|
||||
options.Address(cfg.Address),
|
||||
options.Name(cfg.App.Name),
|
||||
server.Version(cfg.App.Version),
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}),
|
||||
micro.BeforeStart(func(_ context.Context) error {
|
||||
log := logger.NewLogger(
|
||||
logger.WithLevel(logger.ParseLevel(cfg.LogLevel)),
|
||||
logger.WithCallerSkipCount(3),
|
||||
)
|
||||
return svc.Init(micro.Logger(log))
|
||||
}),
|
||||
micro.BeforeStart(func(ctx context.Context) error {
|
||||
var connstr string
|
||||
if v, ok := cfg.StorageDSN[cfg.App.Name]; ok {
|
||||
connstr = v
|
||||
} else if v, ok = cfg.StorageDSN["all"]; ok {
|
||||
connstr = v
|
||||
}
|
||||
|
||||
scheme, dsn, err := storageOptions(connstr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
conn, err := connectDataBase(scheme, dsn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
store, err := storage.NewStorage(scheme, conn)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctx = storage.InContext(ctx, store)
|
||||
return svc.Init(micro.Context(ctx))
|
||||
}),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := pb.RegisterPkgdashServiceServer(mgsrv, h); err != nil {
|
||||
logger.Fatalf(ctx, "failed to register handler: %v", err)
|
||||
}
|
||||
|
||||
intsvc := httpsrv.NewServer(
|
||||
server.Codec("application/json", jsoncodec.NewCodec()),
|
||||
server.Address(cfg.Meter.Addr), server.Context(ctx),
|
||||
)
|
||||
|
||||
if err := intsvc.Init(); err != nil {
|
||||
logger.Fatalf(ctx, "failed to init http srv: %v", err)
|
||||
}
|
||||
|
||||
if err := healthhandler.RegisterHealthServiceServer(intsvc, healthhandler.NewHandler()); err != nil {
|
||||
logger.Fatalf(ctx, "failed to set http handler: %v", err)
|
||||
}
|
||||
if err := meterhandler.RegisterMeterServiceServer(intsvc, meterhandler.NewHandler()); err != nil {
|
||||
logger.Fatalf(ctx, "failed to set http handler: %v", err)
|
||||
}
|
||||
|
||||
if err := intsvc.Start(); err != nil {
|
||||
logger.Fatalf(ctx, "failed to run http srv: %v", err)
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func storageOptions(dsn string) (string, string, error) {
|
||||
u, err := url.Parse(dsn)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
scheme := u.Scheme
|
||||
if idx := strings.Index(u.Scheme, "+"); idx > 0 {
|
||||
scheme = u.Scheme[:idx]
|
||||
u.Scheme = u.Scheme[idx+1:]
|
||||
}
|
||||
return scheme, u.String(), nil
|
||||
}
|
||||
|
||||
func connectDataBase(driverName, dsn string) (*sql.DB, error) {
|
||||
conn, err := sql.Open(driverName, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = conn.Ping(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, err
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
drop table if exists dashboard, package, module, issue, comment;
|
||||
@@ -0,0 +1,40 @@
|
||||
create table if not exists dashboard (
|
||||
id serial not null unique primary key ,
|
||||
"uuid" uuid not null unique default gen_random_uuid() ,
|
||||
package integer[] default '{}'::integer[]
|
||||
);
|
||||
|
||||
create table if not exists comment (
|
||||
id serial not null unique primary key ,
|
||||
"text" text ,
|
||||
package integer not null,
|
||||
created timestamp not null default current_timestamp ,
|
||||
updated timestamp default current_timestamp
|
||||
);
|
||||
|
||||
create table if not exists module (
|
||||
id serial not null unique primary key ,
|
||||
name varchar not null ,
|
||||
version varchar not null ,
|
||||
last_version varchar not null
|
||||
);
|
||||
|
||||
create table if not exists issue (
|
||||
id serial not null unique primary key ,
|
||||
--package integer references package(id) ,
|
||||
modules integer[] default '{}'::integer[],
|
||||
status integer default 0 ,
|
||||
"desc" varchar
|
||||
);
|
||||
|
||||
create table if not exists package (
|
||||
id serial not null unique primary key ,
|
||||
name varchar not null ,
|
||||
url varchar ,
|
||||
modules integer[] default '{}'::integer[],
|
||||
issues integer[] default '{}'::integer[],
|
||||
comments integer[] default '{}'::integer[]
|
||||
);
|
||||
|
||||
create unique index module_info on module(name, version);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
drop table if exists dashboard ;
|
||||
drop table if exists package ;
|
||||
drop table if exists module ;
|
||||
drop table if exists issue ;
|
||||
drop table if exists comment;
|
||||
39
internal/storage/migrations/sqlite/000001_init_schema.up.sql
Normal file
39
internal/storage/migrations/sqlite/000001_init_schema.up.sql
Normal file
@@ -0,0 +1,39 @@
|
||||
create table if not exists dashboard (
|
||||
id integer primary key autoincrement not null ,
|
||||
"uuid" uuid not null unique ,
|
||||
package integer[] default '{}'
|
||||
);
|
||||
|
||||
create table if not exists comment (
|
||||
id integer primary key autoincrement not null ,
|
||||
"text" text ,
|
||||
package integer not null,
|
||||
created timestamp not null default current_timestamp ,
|
||||
updated timestamp default current_timestamp
|
||||
);
|
||||
|
||||
create table if not exists module (
|
||||
id integer primary key autoincrement not null ,
|
||||
name varchar not null ,
|
||||
version varchar not null ,
|
||||
last_version varchar not null
|
||||
);
|
||||
|
||||
create table if not exists issue (
|
||||
id serial not null unique primary key ,
|
||||
--package integer references package(id) ,
|
||||
modules integer[] default '[]',
|
||||
status integer default 0 ,
|
||||
"desc" varchar
|
||||
);
|
||||
|
||||
create table if not exists package (
|
||||
id integer primary key autoincrement not null ,
|
||||
name varchar not null ,
|
||||
url varchar ,
|
||||
modules integer[] default '[]',
|
||||
issues integer[] default '[]',
|
||||
comments integer[] default '[]'
|
||||
);
|
||||
|
||||
|
||||
32
internal/storage/postgres_tmp/queries.go
Normal file
32
internal/storage/postgres_tmp/queries.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package postgres
|
||||
|
||||
const (
|
||||
queryListPackage = `
|
||||
select
|
||||
id,
|
||||
name,
|
||||
url,
|
||||
comments
|
||||
--modules,
|
||||
--issues,
|
||||
from package;
|
||||
`
|
||||
queryAddComment = `
|
||||
with insert_comm as (
|
||||
insert into comment(text) values ($1) returning id
|
||||
)
|
||||
update package set comments = array_append(comments, (select * from insert_comm)) where id=$2;
|
||||
`
|
||||
queryAddPackage = `
|
||||
insert into package(name, url, modules) values ($1, $2, $3);
|
||||
`
|
||||
queryInsMsgGetIDs = `
|
||||
insert into module(name, version, last_version) values
|
||||
%s
|
||||
returning id;
|
||||
`
|
||||
queryGetModule = `
|
||||
select id, name, version, last_version from module
|
||||
where id in %s ;
|
||||
`
|
||||
)
|
||||
233
internal/storage/postgres_tmp/storage.go
Normal file
233
internal/storage/postgres_tmp/storage.go
Normal file
@@ -0,0 +1,233 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
mpgx "github.com/golang-migrate/migrate/v4/database/pgx"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"github.com/lib/pq"
|
||||
"go.unistack.org/micro/v4/logger"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/config"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
pathMigration = `migrations/postgres`
|
||||
)
|
||||
|
||||
type Postgres struct {
|
||||
db *sql.DB
|
||||
fs embed.FS
|
||||
}
|
||||
|
||||
func NewStorage() func(*sql.DB, embed.FS) interface{} {
|
||||
return func(db *sql.DB, fs embed.FS) interface{} {
|
||||
return &Postgres{db: db, fs: fs}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Postgres) MigrateUp() error {
|
||||
driver, err := mpgx.WithInstance(s.db, &mpgx.Config{
|
||||
MigrationsTable: mpgx.DefaultMigrationsTable,
|
||||
DatabaseName: config.ServiceName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source, err := iofs.New(s.fs, pathMigration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: pass own logger
|
||||
m, err := migrate.NewWithInstance("fs", source, config.ServiceName, driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Postgres) MigrateDown() error {
|
||||
driver, err := mpgx.WithInstance(s.db, &mpgx.Config{
|
||||
MigrationsTable: mpgx.DefaultMigrationsTable,
|
||||
DatabaseName: config.ServiceName,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source, err := iofs.New(s.fs, pathMigration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: pass own logger
|
||||
m, err := migrate.NewWithInstance("fs", source, config.ServiceName, driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.Down(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Postgres) PackagesUpdate(ctx context.Context, req *pb.PackagesUpdateReq) error {
|
||||
panic("need implement")
|
||||
}
|
||||
|
||||
func (s *Postgres) PackagesList(ctx context.Context, req *pb.PackagesListReq) (models.ListPackage, error) {
|
||||
rows, err := s.db.QueryContext(ctx, queryListPackage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err = rows.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
err = rows.Err()
|
||||
}()
|
||||
|
||||
result := make([]*models.Package, 0)
|
||||
for rows.Next() {
|
||||
tmp := &models.Package{}
|
||||
if err = rows.Scan(
|
||||
&tmp.ID,
|
||||
&tmp.Name,
|
||||
&tmp.URL,
|
||||
pq.Array(&tmp.Comments),
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *Postgres) CommentsCreate(ctx context.Context, req *pb.CommentsCreateReq) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
logger.Errorf(ctx, "AddComment: unable to rollback: %v", rollbackErr)
|
||||
}
|
||||
} else {
|
||||
err = tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
res, err := tx.ExecContext(ctx, queryAddComment, req.Text, req.PackageId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if aff, affErr := res.RowsAffected(); err != nil {
|
||||
err = affErr
|
||||
} else if aff == 0 {
|
||||
err = errors.New("rows affected is 0")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Postgres) PackagesCreate(ctx context.Context, req *pb.PackagesCreateReq) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
logger.Errorf(ctx, "AddPackage: unable to rollback: %v", rollbackErr)
|
||||
}
|
||||
} else {
|
||||
err = tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
res, err := tx.ExecContext(ctx, queryAddPackage, req.Name, req.Url, pq.Array(req.Modules))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if aff, affErr := res.RowsAffected(); err != nil {
|
||||
err = affErr
|
||||
} else if aff == 0 {
|
||||
err = errors.New("rows affected is 0")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Postgres) InsertButchModules(ctx context.Context, req []models.Module) ([]uint64, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
logger.Errorf(ctx, "AddPackage: unable to rollback: %v", rollbackErr)
|
||||
}
|
||||
} else {
|
||||
err = tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
query := generateQuery(req)
|
||||
|
||||
rows, err := tx.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err = rows.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
err = rows.Err()
|
||||
}()
|
||||
|
||||
result := make([]uint64, 0)
|
||||
for rows.Next() {
|
||||
tmp := uint64(0)
|
||||
if err = rows.Scan(&tmp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, tmp)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func generateQuery(rsp []models.Module) string {
|
||||
const pattern = `%c('%s', '%s', '%s')`
|
||||
build := strings.Builder{}
|
||||
comma := ' '
|
||||
for i := range rsp {
|
||||
str := fmt.Sprintf(pattern, comma, rsp[i].Name, rsp[i].Version, rsp[i].LastVersion)
|
||||
build.WriteString(str)
|
||||
comma = ','
|
||||
}
|
||||
|
||||
return fmt.Sprintf(queryInsMsgGetIDs, build.String())
|
||||
}
|
||||
38
internal/storage/postgres_tmp/storage_test.go
Normal file
38
internal/storage/postgres_tmp/storage_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
m := []models.Module{
|
||||
{
|
||||
ID: 1,
|
||||
Name: "test",
|
||||
Version: "1.2.3",
|
||||
Package: 2,
|
||||
LastVersion: "23.31",
|
||||
},
|
||||
{
|
||||
ID: 1,
|
||||
Name: "321test",
|
||||
Version: "1.3",
|
||||
Package: 4,
|
||||
LastVersion: "2111.31",
|
||||
},
|
||||
{
|
||||
ID: 1,
|
||||
Name: "testabcd",
|
||||
Version: "1.2.3",
|
||||
Package: 2,
|
||||
LastVersion: "153453.31",
|
||||
},
|
||||
}
|
||||
|
||||
str := generateQuery(m)
|
||||
|
||||
fmt.Println(str)
|
||||
}
|
||||
34
internal/storage/sqlite/queries.go
Normal file
34
internal/storage/sqlite/queries.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package sqlite
|
||||
|
||||
const (
|
||||
queryPackagesList = `select
|
||||
id,
|
||||
name,
|
||||
url,
|
||||
comments
|
||||
modules,
|
||||
issues,
|
||||
from package;
|
||||
`
|
||||
queryCommentsCreate = `
|
||||
insert into comment(text) values ($1) returning id;
|
||||
update package
|
||||
set comments = json_insert(comments, '$[#]', ( select last_insert_rowid() as id from comment ))
|
||||
where id = $2 ;
|
||||
`
|
||||
queryPackagesCreate = `
|
||||
insert into package(name, url, modules) values ($1, $2, $3);
|
||||
`
|
||||
queryInsMsgGetIDs = `
|
||||
insert into module(name, version, last_version) values
|
||||
%s
|
||||
returning id;
|
||||
`
|
||||
queryModulesList = `
|
||||
select id, name, version, last_version, created, updated from modules;
|
||||
`
|
||||
|
||||
queryCommentsList = `
|
||||
select id, text, created, updated from comments;
|
||||
`
|
||||
)
|
||||
321
internal/storage/sqlite/storage.go
Normal file
321
internal/storage/sqlite/storage.go
Normal file
@@ -0,0 +1,321 @@
|
||||
package sqlite
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"go.unistack.org/micro/v4/logger"
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
pathMigration = `migrations/sqlite`
|
||||
)
|
||||
|
||||
type Sqlite struct {
|
||||
db *sql.DB
|
||||
fs embed.FS
|
||||
}
|
||||
|
||||
func NewStorage() func(*sql.DB, embed.FS) interface{} {
|
||||
return func(db *sql.DB, fs embed.FS) interface{} {
|
||||
return &Sqlite{db: db, fs: fs}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Sqlite) MigrateUp() error {
|
||||
driver, err := sqlite.WithInstance(s.db, &sqlite.Config{
|
||||
MigrationsTable: sqlite.DefaultMigrationsTable,
|
||||
DatabaseName: "pkgdash",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source, err := iofs.New(s.fs, pathMigration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: pass own logger
|
||||
m, err := migrate.NewWithInstance("fs", source, "pkgdash", driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.Up(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sqlite) MigrateDown() error {
|
||||
driver, err := sqlite.WithInstance(s.db, &sqlite.Config{
|
||||
MigrationsTable: sqlite.DefaultMigrationsTable,
|
||||
DatabaseName: "pkgdash",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
source, err := iofs.New(s.fs, pathMigration)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO: pass own logger
|
||||
m, err := migrate.NewWithInstance("fs", source, "pkgdash", driver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = m.Down(); err != nil && !errors.Is(err, migrate.ErrNoChange) {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Sqlite) PackagesUpdate(ctx context.Context, req *pb.PackagesUpdateReq) error {
|
||||
panic("need implement")
|
||||
}
|
||||
|
||||
func (s *Sqlite) PackagesList(ctx context.Context, req *pb.PackagesListReq) ([]*models.Package, error) {
|
||||
var packages []*models.Package
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, queryPackagesList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for ; rows.Err() == nil; rows.Next() {
|
||||
pkg := &models.Package{}
|
||||
if err = rows.Scan(
|
||||
&pkg.ID,
|
||||
&pkg.Name,
|
||||
&pkg.URL,
|
||||
&pkg.Comments,
|
||||
); err != nil {
|
||||
_ = rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
packages = append(packages, pkg)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return packages, err
|
||||
}
|
||||
|
||||
func (s *Sqlite) CommentsCreate(ctx context.Context, req *pb.CommentsCreateReq) (id uint64, err error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
logger.Errorf(ctx, "AddComment: unable to rollback: %v", rollbackErr)
|
||||
}
|
||||
} else {
|
||||
err = tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
if err = tx.QueryRowContext(ctx, queryCommentsCreate, req.Text, req.PackageId).Scan(&id); err != nil {
|
||||
return id, err
|
||||
}
|
||||
|
||||
return id, err
|
||||
}
|
||||
|
||||
func (s *Sqlite) PackagesCreate(ctx context.Context, req *pb.PackagesCreateReq) error {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
logger.Errorf(ctx, "AddPackage: unable to rollback: %v", rollbackErr)
|
||||
}
|
||||
} else {
|
||||
err = tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
res, err := tx.ExecContext(ctx, queryPackagesCreate, req.Name, req.Url, pq.Array(req.Modules))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if aff, affErr := res.RowsAffected(); err != nil {
|
||||
err = affErr
|
||||
} else if aff == 0 {
|
||||
err = errors.New("rows affected is 0")
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Sqlite) InsertButchModules(ctx context.Context, req []models.Module) ([]uint64, error) {
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
if rollbackErr := tx.Rollback(); rollbackErr != nil {
|
||||
logger.Errorf(ctx, "AddPackage: unable to rollback: %v", rollbackErr)
|
||||
}
|
||||
} else {
|
||||
err = tx.Commit()
|
||||
}
|
||||
}()
|
||||
|
||||
query := generateQuery(req)
|
||||
|
||||
rows, err := tx.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err = rows.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
err = rows.Err()
|
||||
}()
|
||||
|
||||
result := make([]uint64, 0)
|
||||
for rows.Next() {
|
||||
tmp := uint64(0)
|
||||
if err = rows.Scan(&tmp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result = append(result, tmp)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *Sqlite) GetModule(ctx context.Context, req *pb.ModulesListReq) ([]*models.Module, error) {
|
||||
var err error
|
||||
var modules []*models.Module
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, queryModulesList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err = rows.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
err = rows.Err()
|
||||
}()
|
||||
|
||||
for ; rows.Err() == nil; rows.Next() {
|
||||
mod := &models.Module{}
|
||||
if err = rows.Scan(
|
||||
&mod.ID,
|
||||
&mod.Name,
|
||||
&mod.Version,
|
||||
&mod.LastVersion,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modules = append(modules, mod)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return modules, nil
|
||||
}
|
||||
|
||||
func (s *Sqlite) CommentsList(ctx context.Context, req *pb.CommentsListReq) ([]*models.Comment, error) {
|
||||
var comments []*models.Comment
|
||||
|
||||
rows, err := s.db.QueryContext(ctx, queryCommentsList, req.PackageId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err = rows.Close(); err != nil {
|
||||
return
|
||||
}
|
||||
err = rows.Err()
|
||||
}()
|
||||
for ; rows.Err() == nil; rows.Next() {
|
||||
com := &models.Comment{}
|
||||
if err = rows.Scan(
|
||||
&com.ID,
|
||||
&com.Text,
|
||||
&com.Created,
|
||||
&com.Updated,
|
||||
); err != nil {
|
||||
_ = rows.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
comments = append(comments, com)
|
||||
}
|
||||
|
||||
if err = rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return comments, nil
|
||||
}
|
||||
|
||||
func convertSliceUInt(arg ...uint64) []interface{} {
|
||||
result := make([]interface{}, 0, len(arg))
|
||||
for i := range arg {
|
||||
result = append(result, arg[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func generateQuery(rsp []models.Module) string {
|
||||
const pattern = `%c('%s', '%s', '%s')`
|
||||
build := strings.Builder{}
|
||||
comma := ' '
|
||||
for i := range rsp {
|
||||
str := fmt.Sprintf(pattern, comma, rsp[i].Name, rsp[i].Version, rsp[i].LastVersion)
|
||||
build.WriteString(str)
|
||||
comma = ','
|
||||
}
|
||||
|
||||
return fmt.Sprintf(queryInsMsgGetIDs, build.String())
|
||||
}
|
||||
|
||||
func generateArrayIneq(count int) string {
|
||||
return "(?" + strings.Repeat(",?", count-1) + ")"
|
||||
}
|
||||
69
internal/storage/storage.go
Normal file
69
internal/storage/storage.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"embed"
|
||||
"errors"
|
||||
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/models"
|
||||
// "git.unistack.org/unistack-org/pkgdash/internal/storage/postgres"
|
||||
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/storage/sqlite"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
//go:embed migrations
|
||||
var fs embed.FS
|
||||
|
||||
var storages = map[string]func(*sql.DB, embed.FS) interface{}{
|
||||
//"postgres": postgres.NewStorage(),
|
||||
"sqlite": sqlite.NewStorage(),
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
var storeIdent = contextKey("store")
|
||||
|
||||
type Migrate interface {
|
||||
MigrateUp() error
|
||||
MigrateDown() error
|
||||
}
|
||||
|
||||
type Storage interface {
|
||||
Migrate
|
||||
PackagesCreate(ctx context.Context, req *pb.PackagesCreateReq) error
|
||||
PackagesList(ctx context.Context, req *pb.PackagesListReq) ([]*models.Package, error)
|
||||
PackagesUpdate(ctx context.Context, req *pb.PackagesUpdateReq) error
|
||||
PackagesDelete(ctx context.Context, req *pb.PackagesDeleteReq) error
|
||||
CommentsCreate(ctx context.Context, req *pb.CommentsCreateReq) (*models.Comment, error)
|
||||
CommentsDelete(ctx context.Context, req *pb.CommentsDeleteReq) error
|
||||
CommentsList(ctx context.Context, req *pb.CommentsListReq) ([]*models.Comment, error)
|
||||
InsertButchModules(ctx context.Context, req []models.Module) ([]uint64, error)
|
||||
ModulesList(ctx context.Context, req *pb.ModulesListReq) ([]*models.Module, error)
|
||||
}
|
||||
|
||||
func NewStorage(name string, db *sql.DB) (Storage, error) {
|
||||
function, ok := storages[name]
|
||||
if !ok {
|
||||
return nil, errors.New("incorrect name store")
|
||||
}
|
||||
store := function(db, fs)
|
||||
database, ok := store.(Storage)
|
||||
if !ok {
|
||||
return nil, errors.New("dont implements interface Storage")
|
||||
}
|
||||
return database, nil
|
||||
}
|
||||
|
||||
func InContext(ctx context.Context, val Storage) context.Context {
|
||||
return context.WithValue(ctx, storeIdent, val)
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) (Storage, error) {
|
||||
if store, ok := ctx.Value(storeIdent).(Storage); !ok {
|
||||
return nil, errors.New("empty store")
|
||||
} else {
|
||||
return store, nil
|
||||
}
|
||||
}
|
||||
69
internal/storage/storage_test.go
Normal file
69
internal/storage/storage_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"git.unistack.org/unistack-org/pkgdash/internal/storage/sqlite"
|
||||
pb "git.unistack.org/unistack-org/pkgdash/proto"
|
||||
)
|
||||
|
||||
func TestGetModule(t *testing.T) {
|
||||
conn, err := sql.Open("sqlite3", "/Users/devstigneev_local/GolandProjects/unistack/pkgdash/identifier.sqlite")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if err = conn.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
st := sqlite.NewStorage()
|
||||
store := st(conn, fs)
|
||||
|
||||
s, ok := store.(Storage)
|
||||
if !ok {
|
||||
t.Fatal("dont implements interface Storage")
|
||||
}
|
||||
|
||||
req := &pb.GetModuleReq{
|
||||
Id: []uint64{1, 2, 3},
|
||||
}
|
||||
|
||||
module, err := s.GetModule(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fmt.Println(module)
|
||||
}
|
||||
|
||||
func TestGetComment(t *testing.T) {
|
||||
conn, err := sql.Open("sqlite3", "/Users/devstigneev_local/GolandProjects/unistack/pkgdash/identifier.sqlite")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer conn.Close()
|
||||
if err = conn.Ping(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
st := sqlite.NewStorage()
|
||||
store := st(conn, fs)
|
||||
|
||||
s, ok := store.(Storage)
|
||||
if !ok {
|
||||
t.Fatal("dont implements interface Storage")
|
||||
}
|
||||
|
||||
req := &pb.GetCommentsReq{
|
||||
Id: []uint64{1, 2, 3, 15},
|
||||
}
|
||||
comments, err := s.GetComment(context.Background(), req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(comments.Decode())
|
||||
}
|
||||
Reference in New Issue
Block a user