Skip to content

Commit

Permalink
Events search (untested)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-brennan2005 committed May 20, 2024
1 parent c9c2823 commit 9c6ed85
Show file tree
Hide file tree
Showing 12 changed files with 3,862 additions and 690 deletions.
1 change: 0 additions & 1 deletion backend/entities/clubs/base/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package base
import (
"net/http"

"github.com/GenerateNU/sac/backend/entities/models"
"github.com/GenerateNU/sac/backend/utilities"
"github.com/garrettladley/fiberpaginate"
"github.com/gofiber/fiber/v2"
Expand Down
1 change: 0 additions & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ require (
github.com/garrettladley/fiberpaginate v1.0.1
github.com/garrettladley/mattress v0.4.0
github.com/go-playground/validator/v10 v10.20.0
github.com/go-resty/resty/v2 v2.13.1
github.com/goccy/go-json v0.10.2
github.com/gofiber/fiber/v2 v2.52.4
github.com/gofiber/swagger v1.0.0
Expand Down
1 change: 1 addition & 0 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ func main() {

if *seedSearch {
search.SeedClubs(db)

Check failure on line 78 in backend/main.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `search.SeedClubs` is not checked (errcheck)
search.SeedEvents(db)

Check failure on line 79 in backend/main.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `search.SeedEvents` is not checked (errcheck)
}

app := server.Init(db, integrations, *config)
Expand Down
8 changes: 4 additions & 4 deletions backend/migrations/000001_init.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ CREATE TABLE IF NOT EXISTS clubs(
PRIMARY KEY(id)
);

CREATE UNIQUE INDEX IF NOT EXISTS uni_clubs_name ON clubs USING btree ("name");
CREATE INDEX IF NOT EXISTS idx_clubs_num_members ON clubs USING btree ("num_members");
CREATE INDEX IF NOT EXISTS idx_clubs_one_word_to_describe_us ON clubs USING btree ("one_word_to_describe_us");
--CREATE UNIQUE INDEX IF NOT EXISTS uni_clubs_name ON clubs USING btree ("name");
--CREATE INDEX IF NOT EXISTS idx_clubs_num_members ON clubs USING btree ("num_members");
--CREATE INDEX IF NOT EXISTS idx_clubs_one_word_to_describe_us ON clubs USING btree ("one_word_to_describe_us");

CREATE TABLE IF NOT EXISTS series(
id uuid NOT NULL DEFAULT uuid_generate_v4(),
Expand All @@ -60,7 +60,7 @@ CREATE TABLE IF NOT EXISTS events(
updated_at timestamp with time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
name varchar(255) NOT NULL,
preview varchar(255) NOT NULL,
description varchar(255) NOT NULL,
description text NOT NULL,
event_type varchar(255) NOT NULL,
location varchar(255),
link varchar(255),
Expand Down
29 changes: 29 additions & 0 deletions backend/search/base/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,32 @@ func (s *SearchController) SearchClubs(c *fiber.Ctx) error {

return c.Status(http.StatusOK).JSON(result)
}

// SearchEvents godoc
//
// @Summary Searches through events
// @Description Searches through events
// @ID search-club
// @Tags search
// @Accept json
// @Produce json
// @Param searchQuery query models.SearchQueryParams true "Search Body"
// @Success 200 {object} []models.EventsSearchResult
// @Failure 404 {object} error
// @Failure 500 {object} error
// @Router /search/events [get]
func (s *SearchController) SearchEvents(c *fiber.Ctx) error {
var searchQuery search.EventSearchQuery

if err := c.QueryParser(&searchQuery); err != nil {
return utilities.InvalidJSON()
}

result, err := s.searchService.SearchEvents(searchQuery)

if err != nil {
return err
}

return c.Status(http.StatusOK).JSON(result)
}
1 change: 1 addition & 0 deletions backend/search/base/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ func Search(params types.RouteParams) {
search := params.Router.Group("/search")

search.Get("/clubs", searchController.SearchClubs)
search.Get("/events", searchController.SearchEvents)
}
5 changes: 5 additions & 0 deletions backend/search/base/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

type SearchServiceInterface interface {
SearchClubs(query search.ClubSearchQuery) (*search.ClubSearchResult, error)
SearchEvents(query search.EventSearchQuery) (*search.EventSearchResult, error)
}

type SearchService struct {
Expand All @@ -21,3 +22,7 @@ func NewSearchService(serviceParams types.ServiceParams) SearchServiceInterface
func (s *SearchService) SearchClubs(query search.ClubSearchQuery) (*search.ClubSearchResult, error) {
return SearchClubs(s.DB, query)
}

func (s *SearchService) SearchEvents(query search.EventSearchQuery) (*search.EventSearchResult, error) {
return SearchEvents(s.DB, query)
}
22 changes: 22 additions & 0 deletions backend/search/base/transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,25 @@ func SearchClubs(db *gorm.DB, query search.ClubSearchQuery) (*search.ClubSearchR
Results: clubs,
}, nil
}

func SearchEvents(db *gorm.DB, query search.EventSearchQuery) (*search.EventSearchResult, error) {
result, err := doSearchGetRequest[search.SearchEndpointRequest, search.SearchEndpointResponse]("/events/_search", query.ToSearchEndpointRequest())
if err != nil {
return nil, nil
}

ids := make([]string, len(result.Hits.Hits))
for i, result := range result.Hits.Hits {
ids[i] = result.Id
}

var events []models.Event

if err = db.Model(&models.Event{}).Preload("Tag").Where("id IN ?", ids).Find(&events).Error; err != nil {
return nil, nil
}

return &search.EventSearchResult{
Results: events,
}, nil
}
57 changes: 57 additions & 0 deletions backend/search/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,60 @@ func SeedClubs(db *gorm.DB) error {

return nil
}

// FIXME: see fixme comment for SeedClubs()
func SeedEvents(db *gorm.DB) error {
stdout := os.Stdout

stdout.WriteString("Deleting existing event index...\n")
req, err := http.NewRequest("DELETE", fmt.Sprintf("%s/events", constants.SEARCH_URL), nil)
if err != nil {
return err
}
http.DefaultClient.Do(req)

Check failure on line 84 in backend/search/seed.go

View workflow job for this annotation

GitHub Actions / Lint

Error return value of `http.DefaultClient.Do` is not checked (errcheck)

var events []models.Event

if err := db.Preload("Tag").Preload("Clubs").Not("is_draft = ?", true).Not("is_archived = ?", true).Find(&events).Error; err != nil {
return err
}

var requestBodyBuilder strings.Builder

for _, event := range events {
indexData := BulkRequestCreate{}
indexData.Create.Index = "events"
indexData.Create.Id = event.ID.String()
indexJson, err := json.Marshal(indexData)
if err != nil {
return err
}

eventData := EventSearchDocumentFromEvent(&event)
eventJson, err := json.Marshal(eventData)
if err != nil {
return err
}

requestBodyBuilder.WriteString(fmt.Sprintf("%s\n%s\n", indexJson, eventJson))
}

req, err = http.NewRequest("POST", fmt.Sprintf("%s/_bulk", constants.SEARCH_URL), bytes.NewBufferString(requestBodyBuilder.String()))
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")

resp, err := http.DefaultClient.Do(req)
if err != nil {
stdout.WriteString(fmt.Sprintf("Error making _bulk request for event seeding: %s\n", err.Error()))
}

if resp.StatusCode != 200 {
stdout.WriteString(fmt.Sprintf("Error making _bulk request for event seeding: response returned %d code\n", resp.StatusCode))
}

stdout.WriteString("Seeding events finished")

return nil
}
138 changes: 127 additions & 11 deletions backend/search/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package search

import (
"strings"
"time"

"github.com/GenerateNU/sac/backend/entities/models"
)
Expand All @@ -14,11 +15,11 @@ type Json map[string]interface{}
// specified; depending on what we are looking to search we'll need to add different clauses.
type SearchEndpointRequest Json

// Used in SearchEndpointRequest
type TagFilterInSearchEndpointRequest struct {
Term struct {
Tag string `json:"tag"`
} `json:"term"`
// Used for SearchEndpointRequests.
type BooleanQuery struct {
Must []Json `json:"must"`
Should []Json `json:"should"`
Filter []Json `json:"filter"`
}

// Used for responses from OpenSearch GET /<index>/_search requests.
Expand Down Expand Up @@ -66,12 +67,6 @@ type ClubSearchQuery struct {

// To Query DSL JSON
func (q *ClubSearchQuery) ToSearchEndpointRequest() SearchEndpointRequest {
type BooleanQuery struct {
Must []Json `json:"must"`
Should []Json `json:"should"`
Filter []Json `json:"filter"`
}

query := BooleanQuery{}

if q.Search != "" {
Expand Down Expand Up @@ -122,6 +117,127 @@ type ClubSearchResult struct {
Results []models.Club `json:"results"`
}

// How Events are represented in the OpenSearch index.
type EventSearchDocument struct {
Name string `json:"name"`
Preview string `json:"preview"`
Description string `json:"description"`
EventType models.EventType `json:"event_type"`
StartTime time.Time `json:"start_time"`
EndTime time.Time `json:"end_time"`
Clubs []string `json:"clubs"`
Tags []string `json:"tags"`
}

func EventSearchDocumentFromEvent(c *models.Event) EventSearchDocument {
tagIds := make([]string, len(c.Tag))
for i, tag := range c.Tag {
tagIds[i] = tag.ID.String()
}

clubIds := make([]string, len(c.Clubs))
for i, club := range c.Clubs {
clubIds[i] = club.ID.String()
}

return EventSearchDocument{
Name: c.Name,
Preview: c.Preview,
Description: c.Description,
EventType: c.EventType,
StartTime: c.StartTime,
EndTime: c.EndTime,
Tags: tagIds,
Clubs: clubIds,
}
}

// Query parameters for an API GET /api/v1/search/events request.
type EventSearchQuery struct {
Search string `query:"search"`
StartTime time.Time `query:"start_time"`
EndTime time.Time `query:"end_time"`
EventType []models.EventType `query:"event_type"`
Clubs []string `query:"clubs"`
Tags []string `query:"tags"`
}

// To Query DSL JSON
func (q *EventSearchQuery) ToSearchEndpointRequest() SearchEndpointRequest {
query := BooleanQuery{}

if q.Search != "" {
query.Must = append(query.Must, Json{
"query_string": Json{
"query": q.Search,
},
})
}

if !q.StartTime.IsZero() {
query.Filter = append(query.Filter, Json{
"range": Json{
"start_time": Json{
"gte": q.StartTime,
},
},
})
}

if !q.EndTime.IsZero() {
query.Filter = append(query.Filter, Json{
"range": Json{
"end_time": Json{
"lte": q.EndTime,
},
},
})
}

if len(q.EventType) != 0 {
query.Filter = append(query.Filter, Json{
"terms": Json{
"event_type": q.EventType,
},
})
}

if len(q.Clubs) != 0 {
query.Filter = append(query.Filter, Json{
"match": Json{
"tags": Json{
"query": strings.Join(q.Clubs, " "),
"operator": "or",
"minimum_should_match": 1,
},
},
})
}

if len(q.Tags) != 0 {
query.Filter = append(query.Filter, Json{
"match": Json{
"tags": Json{
"query": strings.Join(q.Tags, " "),
"operator": "or",
"minimum_should_match": 1,
},
},
})
}

return SearchEndpointRequest{
"query": Json{
"bool": query,
},
}
}

// The (successful) response of an API GET /api/v1/search/events request.
type EventSearchResult struct {
Results []models.Event `json:"results"`
}

// Used for making OpenSearch /_bulk requests.
type BulkRequestCreate struct {
Create struct {
Expand Down
Loading

0 comments on commit 9c6ed85

Please sign in to comment.