Skip to content

Commit

Permalink
feat(search): Adding basic saved search crud
Browse files Browse the repository at this point in the history
Signed-off-by: Vincent Boutour <[email protected]>
  • Loading branch information
ViBiOh committed Dec 29, 2022
1 parent 5ceec4e commit 3353472
Show file tree
Hide file tree
Showing 22 changed files with 373 additions and 67 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,8 @@ Usage of fibr:
[redis] Redis Username, if any {FIBR_REDIS_USERNAME}
-sanitizeOnStart
[crud] Sanitize name on start {FIBR_SANITIZE_ON_START}
-searchAmqpExclusiveRoutingKey string
[search] AMQP Routing Key for exclusive lock on default exchange {FIBR_SEARCH_AMQP_EXCLUSIVE_ROUTING_KEY} (default "fibr.semaphore.search")
-shareAmqpExchange string
[share] AMQP Exchange Name {FIBR_SHARE_AMQP_EXCHANGE} (default "fibr.shares")
-shareAmqpExclusiveRoutingKey string
Expand Down
3 changes: 3 additions & 0 deletions cmd/fibr/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
basicMemory "github.com/ViBiOh/auth/v2/pkg/store/memory"
"github.com/ViBiOh/fibr/pkg/crud"
"github.com/ViBiOh/fibr/pkg/exif"
"github.com/ViBiOh/fibr/pkg/search"
"github.com/ViBiOh/fibr/pkg/share"
"github.com/ViBiOh/fibr/pkg/storage"
"github.com/ViBiOh/fibr/pkg/thumbnail"
Expand Down Expand Up @@ -51,6 +52,7 @@ type configuration struct {
amqpShare amqphandler.Config
amqpWebhook amqphandler.Config
redis redis.Config
search search.Config
disableAuth *bool
}

Expand Down Expand Up @@ -82,6 +84,7 @@ func newConfig() (configuration, error) {
amqpShare: amqphandler.Flags(fs, "amqpShare", flags.NewOverride("Exchange", "fibr.shares"), flags.NewOverride("Queue", "fibr.share-"+generateIdentityName()), flags.NewOverride("RoutingKey", "share"), flags.NewOverride("Exclusive", true), flags.NewOverride("RetryInterval", time.Duration(0))),
amqpWebhook: amqphandler.Flags(fs, "amqpWebhook", flags.NewOverride("Exchange", "fibr.webhooks"), flags.NewOverride("Queue", "fibr.webhook-"+generateIdentityName()), flags.NewOverride("RoutingKey", "webhook"), flags.NewOverride("Exclusive", true), flags.NewOverride("RetryInterval", time.Duration(0))),
redis: redis.Flags(fs, "redis", flags.NewOverride("Address", "")),
search: search.Flags(fs, "search"),
disableAuth: flags.Bool(fs, "", "auth", "NoAuth", "Disable basic authentification", false, nil),
}, fs.Parse(os.Args[1:])
}
3 changes: 2 additions & 1 deletion cmd/fibr/fibr.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ func main() {
amqpWebhookApp, err := amqphandler.New(config.amqpWebhook, client.amqp, client.tracer.GetTracer("amqp_handler_webhook"), webhookApp.AMQPHandler)
logger.Fatal(err)

searchApp := search.New(filteredStorage, thumbnailApp, exifApp, client.tracer.GetTracer("search"))
searchApp, err := search.New(config.search, filteredStorage, thumbnailApp, exifApp, client.amqp, client.tracer.GetTracer("search"))
logger.Fatal(err)

crudApp, err := crud.New(config.crud, storageApp, filteredStorage, rendererApp, shareApp, webhookApp, thumbnailApp, exifApp, searchApp, eventBus.Push, client.amqp, client.tracer.GetTracer("crud"))
logger.Fatal(err)
Expand Down
21 changes: 21 additions & 0 deletions cmd/fibr/templates/files.html
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
{{ template "webhook-list" . }}
{{ end }}

{{ range .SavedSearches }}
{{ if $root.Request.CanEdit }}
{{ template "delete-saved-search-modal" . }}
{{ end }}
{{ end }}

{{ range .Files }}
{{ if $root.Request.CanEdit }}
{{ template "edit-modal" . }}
Expand Down Expand Up @@ -205,6 +211,21 @@
</div>

<ul id="files" class="no-margin no-padding">
{{ range .SavedSearches }}
<li class="file relative padding-half">
<a class="filelink center ellipsis" href="?{{ raw .Query }}" title="{{ .Name }}">
<img class="icon {{ if eq $root.Request.Display "grid" }}icon-large{{ end }}" src="{{ url "/svg/folder-search?fill=aliceblue" }}" alt="Saved Search">
<span class="filename ellipsis {{ if eq $root.Request.Display "list" }}padding-left{{ end }}">{{ .Name }}</span>

{{ if $root.Request.CanEdit }}
<a href="#delete-saved-search-modal-{{ .ID }}" class="button button-icon file-delete" title="Delete">
<img class="icon icon-square" src="{{ url "/svg/times?fill=crimson" }}" alt="Delete">
</a>
{{ end }}
</a>
</li>
{{ end }}

{{ range .Files }}
<li class="file relative {{ if not .HasThumbnail }}padding-half{{ end }}">
<a class="filelink center ellipsis" href="{{ .URL }}{{ if .IsDir }}?d={{ $root.Request.LayoutPath ($root.Request.AbsoluteURL .URL) }}{{ else }}?browser{{ end }}" title="{{ .Name }}">
Expand Down
19 changes: 0 additions & 19 deletions cmd/fibr/templates/saved-search-folder-modal.html

This file was deleted.

39 changes: 39 additions & 0 deletions cmd/fibr/templates/saved-search-modal.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{{ define "create-saved-search-modal" }}
<div id="create-saved-search" class="modal saved-search-modal">
<div class="modal-content">
<h2 class="header">Create new saved-search</h2>

<form method="post" action="#">
<input type="hidden" name="type" value="saved-search" />
<input type="hidden" name="method" value="PUT" />

<p class="padding no-margin">
<label for="name" class="block">Name</label>
<input id="name" class="full" type="text" name="name" />
</p>

{{ template "form_buttons" "Create" }}
</form>
</div>
</div>
{{ end }}

{{ define "delete-saved-search-modal" }}
<div id="delete-saved-search-modal-{{ .ID }}" class="modal delete-modal">
<div class="modal-content">
<h2 class="header">Confirmation</h2>

<form method="post" action="#">
<input type="hidden" name="type" value="saved-search" />
<input type="hidden" name="method" value="DELETE" />
<input type="hidden" name="name" value="{{ .Name }}" />

<p class="padding no-margin center">
Are you sure you want to delete <strong>{{ .Name }}</strong>?
</p>

{{ template "form_buttons" "Confirm" }}
</form>
</div>
</div>
{{ end }}
6 changes: 3 additions & 3 deletions cmd/fibr/templates/search.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
overflow: auto;
}

.saved-search-folder-modal:target {
.saved-search-modal:target {
display: flex;
z-index: 5;
}

.saved-search-folder-modal:target ~ .content {
.saved-search-modal:target ~ .content {
pointer-events: none;
}

Expand Down Expand Up @@ -61,7 +61,7 @@
{{ template "items-style" . }}

{{ if .Request.CanEdit }}
{{ template "saved-search-folder-modal" }}
{{ template "create-saved-search-modal" }}
{{ end }}

{{ if .HasMap }}
Expand Down
39 changes: 39 additions & 0 deletions pkg/crud/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/ViBiOh/fibr/pkg/provider"
"github.com/ViBiOh/httputils/v4/pkg/model"
"github.com/ViBiOh/httputils/v4/pkg/renderer"
"github.com/ViBiOh/httputils/v4/pkg/sha"
)

func (a App) Create(w http.ResponseWriter, r *http.Request, request provider.Request) {
Expand Down Expand Up @@ -38,3 +39,41 @@ func (a App) Create(w http.ResponseWriter, r *http.Request, request provider.Req

a.rendererApp.Redirect(w, r, fmt.Sprintf("%s/?d=%s", name, request.Display), renderer.NewSuccessMessage("Directory %s successfully created", path.Base(pathname)))
}

func (a App) CreateSavedSearch(w http.ResponseWriter, r *http.Request, request provider.Request) {
if !request.CanEdit {
a.error(w, r, request, model.WrapForbidden(ErrNotAuthorized))
return
}

name, err := checkFormName(r, "name")
if err != nil && !errors.Is(err, ErrEmptyName) {
a.error(w, r, request, err)
return
}

name, err = provider.SanitizeName(name, false)
if err != nil {
a.error(w, r, request, model.WrapInternal(err))
return
}

ctx := r.Context()

item, err := a.storageApp.Info(ctx, request.Filepath())
if err != nil {
a.error(w, r, request, model.WrapNotFound(err))
return
}

if err = a.searchApp.Update(ctx, item, provider.Search{
ID: sha.New(name),
Name: name,
Query: r.URL.RawQuery,
}); err != nil {
a.error(w, r, request, fmt.Errorf("update: %w", err))
return
}

a.rendererApp.Redirect(w, r, fmt.Sprintf("?%s", r.URL.RawQuery), renderer.NewSuccessMessage("Saved search %s successfully created", name))
}
30 changes: 29 additions & 1 deletion pkg/crud/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/ViBiOh/httputils/v4/pkg/tracer"
)

// Delete given path from filesystem
func (a App) Delete(w http.ResponseWriter, r *http.Request, request provider.Request) {
if !request.CanEdit {
a.error(w, r, request, model.WrapForbidden(ErrNotAuthorized))
Expand Down Expand Up @@ -52,3 +51,32 @@ func (a App) Delete(w http.ResponseWriter, r *http.Request, request provider.Req

a.rendererApp.Redirect(w, r, fmt.Sprintf("?d=%s", request.Display), renderer.NewSuccessMessage("%s successfully deleted", info.Name))
}

// Delete given path from filesystem
func (a App) DeleteSavedSearch(w http.ResponseWriter, r *http.Request, request provider.Request) {
if !request.CanEdit {
a.error(w, r, request, model.WrapForbidden(ErrNotAuthorized))
return
}

name, err := checkFormName(r, "name")
if err != nil && !errors.Is(err, ErrEmptyName) {
a.error(w, r, request, err)
return
}

ctx := r.Context()

item, err := a.storageApp.Info(ctx, request.Filepath())
if err != nil {
a.error(w, r, request, model.WrapNotFound(err))
return
}

if err = a.searchApp.Delete(ctx, item, name); err != nil {
a.error(w, r, request, err)
return
}

a.rendererApp.Redirect(w, r, fmt.Sprintf("?d=%s", request.Display), renderer.NewSuccessMessage("%s successfully deleted", name))
}
6 changes: 3 additions & 3 deletions pkg/crud/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (a App) getWithMessage(w http.ResponseWriter, r *http.Request, request prov

if err != nil && absto.IsNotExist(err) && provider.StreamExtensions[filepath.Ext(pathname)] {
if request.Share.File {
// URL with /<share_id>/segment.ts will be the pas `/path/of/shared/file/segment.ts`, so we need to remove two directories before appending segment
// URL with /<share_id>/segment.ts will be the path `/path/of/shared/file/segment.ts`, so we need to remove two directories before appending segment
pathname = provider.Dirname(path.Dir(path.Dir(pathname))) + path.Base(pathname)
}

Expand All @@ -44,10 +44,10 @@ func (a App) getWithMessage(w http.ResponseWriter, r *http.Request, request prov

if err != nil {
if absto.IsNotExist(err) {
return errorReturn(request, model.WrapNotFound(err))
err = model.WrapNotFound(err)
}

return errorReturn(request, model.WrapNotFound(err))
return errorReturn(request, err)
}

if item.IsDir && !strings.HasSuffix(r.URL.Path, "/") {
Expand Down
35 changes: 26 additions & 9 deletions pkg/crud/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ func (a App) list(ctx context.Context, request provider.Request, message rendere
}
}()

var savedSearches map[string]provider.Search
savedSearchDone := make(chan struct{})
go func() {
defer close(savedSearchDone)

var err error

savedSearches, err = a.searchApp.List(ctx, item)
if err != nil {
listLogger(item.Pathname).Error("list saved searches: %s", err)
return
}
}()

wg.Wait()

items := make([]provider.RenderItem, len(files))
Expand All @@ -79,16 +93,19 @@ func (a App) list(ctx context.Context, request provider.Request, message rendere
<-thumbnailDone
hasThumbnail, hasStory, cover := a.enrichThumbnail(ctx, directoryAggregate, items, thumbnails)

<-savedSearchDone

content := map[string]any{
"Paths": getPathParts(request),
"Files": items,
"Cover": cover,
"Request": request,
"Message": message,
"HasMap": len(directoryAggregate.Location),
"HasThumbnail": hasThumbnail,
"HasStory": hasStory,
"ChunkUpload": a.chunkUpload,
"Paths": getPathParts(request),
"Files": items,
"SavedSearches": savedSearches,
"Cover": cover,
"Request": request,
"Message": message,
"HasMap": len(directoryAggregate.Location),
"HasThumbnail": hasThumbnail,
"HasStory": hasStory,
"ChunkUpload": a.chunkUpload,
}

if request.CanShare {
Expand Down
11 changes: 10 additions & 1 deletion pkg/crud/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,11 +200,20 @@ func (a App) handlePost(w http.ResponseWriter, r *http.Request, request provider
switch putType := r.FormValue("type"); putType {
case "folder":
a.Create(w, r, request)
case "saved-search":
a.CreateSavedSearch(w, r, request)
default:
a.error(w, r, request, model.WrapInvalid(fmt.Errorf("unknown type `%s`", putType)))
}
case http.MethodDelete:
a.Delete(w, r, request)
switch putType := r.FormValue("type"); putType {
case "file":
a.Delete(w, r, request)
case "saved-search":
a.DeleteSavedSearch(w, r, request)
default:
a.error(w, r, request, model.WrapInvalid(fmt.Errorf("unknown type `%s`", putType)))
}
case http.MethodTrace:
a.regenerate(w, r, request)
default:
Expand Down
11 changes: 11 additions & 0 deletions pkg/provider/search.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package provider

type Search struct {
ID string `json:"id"`
Name string `json:"name"`
Query string `json:"query"`
}

func (s Search) IsZero() bool {
return len(s.Name) != 0
}
Loading

0 comments on commit 3353472

Please sign in to comment.