Add pkg/search
This commit is contained in:
parent
be7709a5ed
commit
4774ec3343
|
@ -20,90 +20,46 @@ package main
|
|||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"io"
|
||||
|
||||
"github.com/twitchtv/twirp"
|
||||
"go.elara.ws/lure/internal/api"
|
||||
"go.elara.ws/lure/cmd/lure-api-server/internal/api"
|
||||
"go.elara.ws/lure/internal/log"
|
||||
"go.elara.ws/lure/pkg/config"
|
||||
"go.elara.ws/lure/pkg/db"
|
||||
"go.elara.ws/lure/pkg/search"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type lureWebAPI struct{}
|
||||
|
||||
func (l lureWebAPI) Search(ctx context.Context, req *api.SearchRequest) (*api.SearchResponse, error) {
|
||||
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
|
||||
args := []any{"%" + req.Query + "%", "%" + req.Query + "%", req.Query}
|
||||
|
||||
if req.FilterValue != nil && req.FilterType != api.FILTER_TYPE_NO_FILTER {
|
||||
switch req.FilterType {
|
||||
case api.FILTER_TYPE_IN_REPOSITORY:
|
||||
query += " AND repository = ?"
|
||||
case api.FILTER_TYPE_SUPPORTS_ARCH:
|
||||
query += " AND json_array_contains(architectures, ?)"
|
||||
}
|
||||
args = append(args, *req.FilterValue)
|
||||
}
|
||||
|
||||
if req.SortBy != api.SORT_BY_UNSORTED {
|
||||
switch req.SortBy {
|
||||
case api.SORT_BY_NAME:
|
||||
query += " ORDER BY name"
|
||||
case api.SORT_BY_REPOSITORY:
|
||||
query += " ORDER BY repository"
|
||||
case api.SORT_BY_VERSION:
|
||||
query += " ORDER BY version"
|
||||
}
|
||||
}
|
||||
|
||||
if req.Limit != 0 {
|
||||
query += " LIMIT " + strconv.FormatInt(req.Limit, 10)
|
||||
}
|
||||
|
||||
result, err := db.GetPkgs(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := &api.SearchResponse{}
|
||||
for result.Next() {
|
||||
pkg := &db.Package{}
|
||||
err = result.StructScan(pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.Packages = append(out.Packages, dbPkgToAPI(ctx, pkg))
|
||||
}
|
||||
|
||||
return out, err
|
||||
pkgs, err := search.Search(search.Options{
|
||||
Filter: search.Filter(req.FilterType),
|
||||
SortBy: search.SortBy(req.SortBy),
|
||||
Limit: req.Limit,
|
||||
Query: req.Query,
|
||||
})
|
||||
return &api.SearchResponse{Packages: searchPkgsToAPI(ctx, pkgs)}, err
|
||||
}
|
||||
|
||||
func (l lureWebAPI) GetPkg(ctx context.Context, req *api.GetPackageRequest) (*api.Package, error) {
|
||||
pkg, err := db.GetPkg("name = ? AND repository = ?", req.Name, req.Repository)
|
||||
pkg, err := search.GetPkg(req.Repository, req.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dbPkgToAPI(ctx, pkg), nil
|
||||
return searchPkgToAPI(ctx, pkg), nil
|
||||
}
|
||||
|
||||
func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptRequest) (*api.GetBuildScriptResponse, error) {
|
||||
if strings.ContainsAny(req.Name, "./") || strings.ContainsAny(req.Repository, "./") {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, "name and repository must not contain . or /")
|
||||
}
|
||||
|
||||
scriptPath := filepath.Join(config.GetPaths().RepoDir, req.Repository, req.Name, "lure.sh")
|
||||
_, err := os.Stat(scriptPath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil, twirp.NewError(twirp.NotFound, "requested package not found")
|
||||
r, err := search.GetScript(req.Repository, req.Name)
|
||||
if err == search.ErrScriptNotFound {
|
||||
return nil, twirp.NewError(twirp.NotFound, err.Error())
|
||||
} else if err == search.ErrInvalidArgument {
|
||||
return nil, twirp.NewError(twirp.InvalidArgument, err.Error())
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(scriptPath)
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -111,23 +67,31 @@ func (l lureWebAPI) GetBuildScript(ctx context.Context, req *api.GetBuildScriptR
|
|||
return &api.GetBuildScriptResponse{Script: string(data)}, nil
|
||||
}
|
||||
|
||||
func dbPkgToAPI(ctx context.Context, pkg *db.Package) *api.Package {
|
||||
func searchPkgsToAPI(ctx context.Context, pkgs []search.Package) []*api.Package {
|
||||
out := make([]*api.Package, len(pkgs))
|
||||
for i, pkg := range pkgs {
|
||||
out[i] = searchPkgToAPI(ctx, pkg)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func searchPkgToAPI(ctx context.Context, pkg search.Package) *api.Package {
|
||||
return &api.Package{
|
||||
Name: pkg.Name,
|
||||
Repository: pkg.Repository,
|
||||
Version: pkg.Version,
|
||||
Release: int64(pkg.Release),
|
||||
Epoch: ptr(int64(pkg.Epoch)),
|
||||
Description: performTranslation(ctx, pkg.Description.Val),
|
||||
Homepage: performTranslation(ctx, pkg.Homepage.Val),
|
||||
Maintainer: performTranslation(ctx, pkg.Maintainer.Val),
|
||||
Architectures: pkg.Architectures.Val,
|
||||
Licenses: pkg.Licenses.Val,
|
||||
Provides: pkg.Provides.Val,
|
||||
Conflicts: pkg.Conflicts.Val,
|
||||
Replaces: pkg.Replaces.Val,
|
||||
Depends: dbMapToAPI(pkg.Depends.Val),
|
||||
BuildDepends: dbMapToAPI(pkg.BuildDepends.Val),
|
||||
Description: performTranslation(ctx, pkg.Description),
|
||||
Homepage: performTranslation(ctx, pkg.Homepage),
|
||||
Maintainer: performTranslation(ctx, pkg.Maintainer),
|
||||
Architectures: pkg.Architectures,
|
||||
Licenses: pkg.Licenses,
|
||||
Provides: pkg.Provides,
|
||||
Conflicts: pkg.Conflicts,
|
||||
Replaces: pkg.Replaces,
|
||||
Depends: mapToAPI(pkg.Depends),
|
||||
BuildDepends: mapToAPI(pkg.BuildDepends),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -135,7 +99,7 @@ func ptr[T any](v T) *T {
|
|||
return &v
|
||||
}
|
||||
|
||||
func dbMapToAPI(m map[string][]string) map[string]*api.StringList {
|
||||
func mapToAPI(m map[string][]string) map[string]*api.StringList {
|
||||
out := make(map[string]*api.StringList, len(m))
|
||||
for override, list := range m {
|
||||
out[override] = &api.StringList{Entries: list}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.elara.ws/lure/pkg/db"
|
||||
"go.elara.ws/lure/pkg/search"
|
||||
)
|
||||
|
||||
//go:embed badge-logo.txt
|
||||
|
@ -21,7 +21,7 @@ func handleBadge() http.HandlerFunc {
|
|||
repo := chi.URLParam(req, "repo")
|
||||
name := chi.URLParam(req, "pkg")
|
||||
|
||||
pkg, err := db.GetPkg("name = ? AND repository = ?", name, repo)
|
||||
pkg, err := search.GetPkg(repo, name)
|
||||
if err != nil {
|
||||
http.Error(res, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
|
@ -31,7 +31,7 @@ func handleBadge() http.HandlerFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func genVersion(pkg *db.Package) string {
|
||||
func genVersion(pkg search.Package) string {
|
||||
sb := strings.Builder{}
|
||||
if pkg.Epoch != 0 {
|
||||
sb.WriteString(strconv.Itoa(int(pkg.Epoch)))
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/go-chi/chi/v5"
|
||||
"github.com/twitchtv/twirp"
|
||||
"go.elara.ws/logger"
|
||||
"go.elara.ws/lure/internal/api"
|
||||
"go.elara.ws/lure/cmd/lure-api-server/internal/api"
|
||||
"go.elara.ws/lure/internal/log"
|
||||
"go.elara.ws/lure/pkg/config"
|
||||
"go.elara.ws/lure/pkg/repos"
|
||||
|
|
|
@ -0,0 +1,164 @@
|
|||
package search
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"go.elara.ws/lure/pkg/config"
|
||||
"go.elara.ws/lure/pkg/db"
|
||||
)
|
||||
|
||||
// Filter represents search filters.
|
||||
type Filter int
|
||||
|
||||
// Filters
|
||||
const (
|
||||
FilterNone Filter = iota
|
||||
FilterInRepo
|
||||
FilterSupportsArch
|
||||
)
|
||||
|
||||
// SoryBy represents a value that packages can be sorted by.
|
||||
type SortBy int
|
||||
|
||||
// Sort values
|
||||
const (
|
||||
SortByNone = iota
|
||||
SortByName
|
||||
SortByRepo
|
||||
SortByVersion
|
||||
)
|
||||
|
||||
// Package represents a package from LURE's database
|
||||
type Package struct {
|
||||
Name string
|
||||
Version string
|
||||
Release int
|
||||
Epoch uint
|
||||
Description map[string]string
|
||||
Homepage map[string]string
|
||||
Maintainer map[string]string
|
||||
Architectures []string
|
||||
Licenses []string
|
||||
Provides []string
|
||||
Conflicts []string
|
||||
Replaces []string
|
||||
Depends map[string][]string
|
||||
BuildDepends map[string][]string
|
||||
OptDepends map[string][]string
|
||||
Repository string
|
||||
}
|
||||
|
||||
func convertPkg(p db.Package) Package {
|
||||
return Package{
|
||||
Name: p.Name,
|
||||
Version: p.Version,
|
||||
Release: p.Release,
|
||||
Epoch: p.Epoch,
|
||||
Description: p.Description.Val,
|
||||
Homepage: p.Homepage.Val,
|
||||
Maintainer: p.Maintainer.Val,
|
||||
Architectures: p.Architectures.Val,
|
||||
Licenses: p.Licenses.Val,
|
||||
Provides: p.Provides.Val,
|
||||
Conflicts: p.Conflicts.Val,
|
||||
Replaces: p.Replaces.Val,
|
||||
Depends: p.Depends.Val,
|
||||
OptDepends: p.OptDepends.Val,
|
||||
Repository: p.Repository,
|
||||
}
|
||||
}
|
||||
|
||||
// Options contains the options for a search.
|
||||
type Options struct {
|
||||
Filter Filter
|
||||
SortBy SortBy
|
||||
Limit int64
|
||||
Query string
|
||||
}
|
||||
|
||||
// Search searches for packages in the database based on the given options.
|
||||
func Search(opts Options) ([]Package, error) {
|
||||
query := "(name LIKE ? OR description LIKE ? OR json_array_contains(provides, ?))"
|
||||
args := []any{"%" + opts.Query + "%", "%" + opts.Query + "%", opts.Query}
|
||||
|
||||
if opts.Filter != FilterNone {
|
||||
switch opts.Filter {
|
||||
case FilterInRepo:
|
||||
query += " AND repository = ?"
|
||||
case FilterSupportsArch:
|
||||
query += " AND json_array_contains(architectures, ?)"
|
||||
}
|
||||
args = append(args, opts.Filter)
|
||||
}
|
||||
|
||||
if opts.SortBy != SortByNone {
|
||||
switch opts.SortBy {
|
||||
case SortByName:
|
||||
query += " ORDER BY name"
|
||||
case SortByRepo:
|
||||
query += " ORDER BY repository"
|
||||
case SortByVersion:
|
||||
query += " ORDER BY version"
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Limit != 0 {
|
||||
query += " LIMIT " + strconv.FormatInt(opts.Limit, 10)
|
||||
}
|
||||
|
||||
result, err := db.GetPkgs(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var out []Package
|
||||
for result.Next() {
|
||||
pkg := db.Package{}
|
||||
err = result.StructScan(&pkg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, convertPkg(pkg))
|
||||
}
|
||||
|
||||
return out, err
|
||||
}
|
||||
|
||||
// GetPkg gets a single package from the database and returns it.
|
||||
func GetPkg(repo, name string) (Package, error) {
|
||||
pkg, err := db.GetPkg("name = ? AND repository = ?", name, repo)
|
||||
return convertPkg(*pkg), err
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrInvalidArgument is an error returned by GetScript when one of its arguments
|
||||
// contain invalid characters
|
||||
ErrInvalidArgument = errors.New("name and repository must not contain . or /")
|
||||
|
||||
// ErrScriptNotFound is returned by GetScript if it can't find the script requested
|
||||
// by the user.
|
||||
ErrScriptNotFound = errors.New("requested script not found")
|
||||
)
|
||||
|
||||
// GetScript returns a reader containing the build script for a given package.
|
||||
func GetScript(repo, name string) (io.ReadCloser, error) {
|
||||
if strings.Contains(name, "./") || strings.ContainsAny(repo, "./") {
|
||||
return nil, ErrInvalidArgument
|
||||
}
|
||||
|
||||
scriptPath := filepath.Join(config.GetPaths().RepoDir, repo, name, "lure.sh")
|
||||
fl, err := os.Open(scriptPath)
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
return nil, ErrScriptNotFound
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return fl, nil
|
||||
}
|
Loading…
Reference in New Issue