eintopf.info@v0.13.16/service/event/store_sql.go (about)

     1  // Copyright (C) 2022 The Eintopf authors
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    15  
    16  package event
    17  
    18  import (
    19  	"context"
    20  	"database/sql"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/google/uuid"
    25  	"github.com/jmoiron/sqlx"
    26  
    27  	"eintopf.info/internal/crud"
    28  	"eintopf.info/service/dbmigration"
    29  )
    30  
    31  // NewSqlStore returns a new sql db event store.
    32  func NewSqlStore(db *sqlx.DB, migrationService dbmigration.Service) (*SqlStore, error) {
    33  	store := &SqlStore{db: db, migrationService: migrationService}
    34  	if err := store.runMigrations(context.Background()); err != nil {
    35  		return nil, err
    36  	}
    37  	return store, nil
    38  }
    39  
    40  type SqlStore struct {
    41  	db               *sqlx.DB
    42  	migrationService dbmigration.Service
    43  }
    44  
    45  func (s *SqlStore) runMigrations(ctx context.Context) error {
    46  	return s.migrationService.RunMigrations(ctx, []dbmigration.Migration{
    47  		dbmigration.NewMigration("createEventsTable", s.createEventsTable, nil),
    48  		dbmigration.NewMigration("createEventsOrganizerTable", s.createEventsOrganizerTable, nil),
    49  		dbmigration.NewMigration("createInvolvedTable", s.createInvolvedTable, nil),
    50  		dbmigration.NewMigration("createEventsTagsTable", s.createEventsTagsTable, nil),
    51  		dbmigration.NewMigration("createEventsOwnedByTable", s.createEventsOwnedByTable, nil),
    52  		dbmigration.NewMigration("moveOrganizerToNewTable", s.moveOrganizersToNewTable, nil),
    53  		dbmigration.NewMigration("removeEmptyTags", s.removeEmptyTags, nil),
    54  	})
    55  }
    56  
    57  func (s *SqlStore) createEventsTable(ctx context.Context) error {
    58  	_, err := s.db.ExecContext(ctx, `
    59          CREATE TABLE IF NOT EXISTS events (
    60              id varchar(32) NOT NULL PRIMARY KEY UNIQUE,
    61              published boolean DEFAULT FALSE,
    62              deactivated boolean DEFAULT FALSE,
    63              canceled boolean DEFAULT FALSE,
    64              parent varchar(64) DEFAULT "",
    65              parentListed boolean DEFAULT FALSE,
    66              name varchar(64) NOT NULL,
    67              location varchar(64),
    68              location2 varchar(64),
    69              description varchar(512),
    70              start timestamp,
    71              end timestamp NULL default NULL,
    72              image varchar(128)
    73          );
    74      `)
    75  	return err
    76  }
    77  
    78  func (s *SqlStore) recreateEventsTable(ctx context.Context) error {
    79  	events, total, err := s.Find(ctx, nil)
    80  	if err != nil {
    81  		return err
    82  	}
    83  	if len(events) != total {
    84  		return fmt.Errorf("len(events) != total")
    85  	}
    86  	if _, err := s.db.Exec(`DROP TABLE events`); err != nil {
    87  		return err
    88  	}
    89  	if err := s.createEventsTable(ctx); err != nil {
    90  		return err
    91  	}
    92  	for _, event := range events {
    93  		err := s.insertEvent(ctx, event)
    94  		if err != nil {
    95  			return err
    96  		}
    97  	}
    98  	return nil
    99  }
   100  
   101  func (s *SqlStore) createEventsOrganizerTable(ctx context.Context) error {
   102  	_, err := s.db.ExecContext(ctx, `
   103          CREATE TABLE IF NOT EXISTS events_organizer (
   104              ID INTEGER PRIMARY KEY AUTOINCREMENT,
   105              event_id varchar(32),
   106              organizer varchar(64)
   107          );
   108      `)
   109  	return err
   110  }
   111  
   112  func (s *SqlStore) createInvolvedTable(ctx context.Context) error {
   113  	_, err := s.db.ExecContext(ctx, `
   114          CREATE TABLE IF NOT EXISTS events_involved (
   115              ID INTEGER PRIMARY KEY AUTOINCREMENT,
   116              event_id varchar(32),
   117              name varchar(64),
   118              description varchar(512)
   119          );
   120      `)
   121  	return err
   122  }
   123  
   124  func (s *SqlStore) createEventsTagsTable(ctx context.Context) error {
   125  	_, err := s.db.ExecContext(ctx, `
   126          CREATE TABLE IF NOT EXISTS events_tags (
   127              ID INTEGER PRIMARY KEY AUTOINCREMENT,
   128              event_id varchar(32),
   129              tag varchar(64)
   130          );
   131      `)
   132  	return err
   133  }
   134  
   135  func (s *SqlStore) createEventsOwnedByTable(ctx context.Context) error {
   136  	_, err := s.db.ExecContext(ctx, `
   137          CREATE TABLE IF NOT EXISTS events_owned_by (
   138              ID INTEGER PRIMARY KEY AUTOINCREMENT,
   139              event_id varchar(32),
   140              user_id varchar(32)
   141          );
   142      `)
   143  	if err != nil {
   144  		return fmt.Errorf("create events_owned_by table: %s", err)
   145  	}
   146  	return nil
   147  }
   148  
   149  func (s *SqlStore) removeEmptyTags(ctx context.Context) error {
   150  	events, _, err := s.Find(context.Background(), &crud.FindParams[FindFilters]{})
   151  	if err != nil {
   152  		return fmt.Errorf("remove empty tags: find events: %s", err)
   153  	}
   154  	for _, e := range events {
   155  		needsUpdate := false
   156  		for i, tag := range e.Tags {
   157  			if tag == "" {
   158  				needsUpdate = true
   159  				e.Tags = append(e.Tags[:i], e.Tags[i+1:]...)
   160  			}
   161  		}
   162  		if needsUpdate {
   163  			_, err := s.Update(context.Background(), e)
   164  			if err != nil {
   165  				return fmt.Errorf("remove empty tags: update event(%s):%s", e.ID, err)
   166  			}
   167  		}
   168  	}
   169  	return nil
   170  }
   171  
   172  func (s *SqlStore) moveOrganizersToNewTable(ctx context.Context) error {
   173  	rows, err := s.db.QueryxContext(ctx, `
   174          SELECT id, organizer, organizer2
   175          FROM events
   176      `, nil)
   177  	if err != nil {
   178  		if err == sql.ErrNoRows {
   179  			return nil
   180  		}
   181  		// When the organizer or organizer2 column does not exist, just ignore the migration
   182  		if err.Error() == "no such column: organizer" || err.Error() == "no such column: organizer2" {
   183  			return nil
   184  		}
   185  		return fmt.Errorf("select organizer and organizer2: %s", err)
   186  	}
   187  	defer rows.Close()
   188  
   189  	type OldEvent struct {
   190  		ID         string `db:"id"`
   191  		Organizer  string `db:"organizer"`
   192  		Organizer2 string `db:"organizer2"`
   193  	}
   194  
   195  	oldEvents := []OldEvent{}
   196  	for rows.Next() {
   197  		oldEvent := OldEvent{}
   198  		err := rows.StructScan(&oldEvent)
   199  		if err != nil {
   200  			return err
   201  		}
   202  
   203  		oldEvents = append(oldEvents, oldEvent)
   204  	}
   205  	rows.Close()
   206  
   207  	for _, oldEvent := range oldEvents {
   208  		organizers := []string{}
   209  		if oldEvent.Organizer != "" {
   210  			organizers = append(organizers, oldEvent.Organizer)
   211  		}
   212  		if oldEvent.Organizer2 != "" {
   213  			organizers = append(organizers, oldEvent.Organizer2)
   214  		}
   215  		err = s.insertOrganizersForEvent(ctx, &Event{
   216  			ID:         oldEvent.ID,
   217  			Organizers: organizers,
   218  		})
   219  		if err != nil {
   220  			return fmt.Errorf("insert organizer and organizer2: %s", err)
   221  		}
   222  	}
   223  	if err := s.recreateEventsTable(ctx); err != nil {
   224  		return fmt.Errorf("recreateEventsTable: %s", err)
   225  	}
   226  	return nil
   227  }
   228  
   229  func (s *SqlStore) Create(ctx context.Context, newEvent *NewEvent) (*Event, error) {
   230  	event := EventFromNewEvent(newEvent, uuid.New().String())
   231  	err := s.insertEvent(ctx, event)
   232  	if err != nil {
   233  		return nil, err
   234  	}
   235  
   236  	err = s.insertOrganizersForEvent(ctx, event)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  	err = s.insertInvolvedForEvent(ctx, event)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  	err = s.insertTagsForEvent(ctx, event)
   245  	if err != nil {
   246  		return nil, err
   247  	}
   248  	err = s.insertOwnedByForEvent(ctx, event)
   249  	if err != nil {
   250  		return nil, err
   251  	}
   252  
   253  	return event, nil
   254  }
   255  
   256  func (s *SqlStore) insertEvent(ctx context.Context, event *Event) error {
   257  	_, err := s.db.NamedExecContext(ctx, `
   258          INSERT INTO events (
   259              id,
   260              published,
   261              deactivated,
   262              canceled,
   263              parent,
   264              parentListed,
   265              name,
   266              location,
   267              location2,
   268              description,
   269              start,
   270              end,
   271              image
   272          ) VALUES (
   273              :id,
   274              :published,
   275              :deactivated,
   276              :canceled,
   277              :parent,
   278              :parentListed,
   279              :name,
   280              :location,
   281              :location2,
   282              :description,
   283              :start,
   284              :end,
   285              :image
   286          )
   287      `, event)
   288  	return err
   289  }
   290  
   291  func (s *SqlStore) insertOrganizersForEvent(ctx context.Context, event *Event) error {
   292  	for _, organizer := range event.Organizers {
   293  		_, err := s.db.ExecContext(ctx, `
   294              INSERT INTO events_organizer (
   295                  event_id,
   296                  organizer
   297              ) VALUES (
   298                  $1,
   299                  $2
   300              )
   301          `, event.ID, organizer)
   302  		if err != nil {
   303  			return err
   304  		}
   305  	}
   306  	return nil
   307  }
   308  
   309  func (s *SqlStore) insertInvolvedForEvent(ctx context.Context, event *Event) error {
   310  	for _, involved := range event.Involved {
   311  		_, err := s.db.ExecContext(ctx, `
   312              INSERT INTO events_involved (
   313                  event_id,
   314                  name,
   315                  description
   316              ) VALUES (
   317                  $1,
   318                  $2,
   319                  $3
   320              )
   321          `, event.ID, involved.Name, involved.Description)
   322  		if err != nil {
   323  			return err
   324  		}
   325  	}
   326  	return nil
   327  }
   328  
   329  func (s *SqlStore) insertTagsForEvent(ctx context.Context, event *Event) error {
   330  	for _, tag := range event.Tags {
   331  		_, err := s.db.ExecContext(ctx, `
   332              INSERT INTO events_tags (
   333                  event_id,
   334                  tag
   335              ) VALUES (
   336                  $1,
   337                  $2
   338              )
   339          `, event.ID, tag)
   340  		if err != nil {
   341  			return err
   342  		}
   343  	}
   344  	return nil
   345  }
   346  
   347  func (s *SqlStore) insertOwnedByForEvent(ctx context.Context, event *Event) error {
   348  	for _, ownedBy := range event.OwnedBy {
   349  		_, err := s.db.ExecContext(ctx, `
   350              INSERT INTO events_owned_by (
   351                  event_id,
   352                  user_id
   353              ) VALUES (
   354                  $1,
   355                  $2
   356              )
   357          `, event.ID, ownedBy)
   358  		if err != nil {
   359  			return err
   360  		}
   361  	}
   362  	return nil
   363  }
   364  
   365  func (s *SqlStore) Update(ctx context.Context, event *Event) (*Event, error) {
   366  	_, err := s.db.NamedExecContext(ctx, `
   367          UPDATE
   368              events
   369          SET
   370              published=:published,
   371              deactivated=:deactivated,
   372              canceled=:canceled,
   373              parent=:parent,
   374              parentListed=:parentListed,
   375              name=:name,
   376              location=:location,
   377              location2=:location2,
   378              description=:description,
   379              start=:start,
   380              end=:end,
   381              image=:image
   382          WHERE
   383              id=:id
   384      `, event)
   385  	if err != nil {
   386  		return nil, err
   387  	}
   388  
   389  	err = s.deleteOrganizersForEvent(ctx, event.ID)
   390  	if err != nil {
   391  		return nil, err
   392  	}
   393  	err = s.insertOrganizersForEvent(ctx, event)
   394  	if err != nil {
   395  		return nil, err
   396  	}
   397  
   398  	err = s.deleteInvolvedForEvent(ctx, event.ID)
   399  	if err != nil {
   400  		return nil, err
   401  	}
   402  	err = s.insertInvolvedForEvent(ctx, event)
   403  	if err != nil {
   404  		return nil, err
   405  	}
   406  
   407  	err = s.deleteTagsForEvent(ctx, event.ID)
   408  	if err != nil {
   409  		return nil, err
   410  	}
   411  	err = s.insertTagsForEvent(ctx, event)
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  
   416  	err = s.deleteOwnedByForEvent(ctx, event.ID)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  	err = s.insertOwnedByForEvent(ctx, event)
   421  	if err != nil {
   422  		return nil, err
   423  	}
   424  
   425  	return event, err
   426  }
   427  
   428  func (s *SqlStore) Delete(ctx context.Context, id string) error {
   429  	_, err := s.db.ExecContext(ctx, "DELETE FROM events WHERE id = $1", id)
   430  	if err != nil {
   431  		return err
   432  	}
   433  
   434  	err = s.deleteOrganizersForEvent(ctx, id)
   435  	if err != nil {
   436  		return err
   437  	}
   438  	err = s.deleteInvolvedForEvent(ctx, id)
   439  	if err != nil {
   440  		return err
   441  	}
   442  	err = s.deleteTagsForEvent(ctx, id)
   443  	if err != nil {
   444  		return err
   445  	}
   446  	err = s.deleteOwnedByForEvent(ctx, id)
   447  	if err != nil {
   448  		return err
   449  	}
   450  
   451  	return nil
   452  }
   453  
   454  func (s *SqlStore) deleteOrganizersForEvent(ctx context.Context, id string) error {
   455  	_, err := s.db.ExecContext(ctx, "DELETE FROM events_organizer WHERE event_id = $1", id)
   456  	if err != nil {
   457  		return err
   458  	}
   459  	return nil
   460  }
   461  
   462  func (s *SqlStore) deleteInvolvedForEvent(ctx context.Context, id string) error {
   463  	_, err := s.db.ExecContext(ctx, "DELETE FROM events_Involved WHERE event_id = $1", id)
   464  	if err != nil {
   465  		return err
   466  	}
   467  	return nil
   468  }
   469  
   470  func (s *SqlStore) deleteTagsForEvent(ctx context.Context, id string) error {
   471  	_, err := s.db.ExecContext(ctx, "DELETE FROM events_tags WHERE event_id = $1", id)
   472  	if err != nil {
   473  		return err
   474  	}
   475  	return nil
   476  }
   477  
   478  func (s *SqlStore) deleteOwnedByForEvent(ctx context.Context, id string) error {
   479  	_, err := s.db.ExecContext(ctx, "DELETE FROM events_owned_by WHERE event_id = $1", id)
   480  	if err != nil {
   481  		return err
   482  	}
   483  	return nil
   484  }
   485  
   486  func (s *SqlStore) FindByID(ctx context.Context, id string) (*Event, error) {
   487  	event := &Event{}
   488  	err := s.db.GetContext(ctx, event, `
   489          SELECT
   490              id,
   491              published,
   492              deactivated,
   493              canceled,
   494              parent,
   495              parentListed,
   496              name,
   497              location,
   498              location2,
   499              description,
   500              start,
   501              end,
   502              image
   503          FROM events
   504          WHERE events.id = $1
   505      `, id)
   506  	if err == sql.ErrNoRows {
   507  		return nil, nil
   508  	}
   509  	if err != nil {
   510  		return nil, err
   511  	}
   512  
   513  	s.findOrganizersForEvent(ctx, event)
   514  	if err != nil {
   515  		return nil, err
   516  	}
   517  	s.findInvolvedForEvent(ctx, event)
   518  	if err != nil {
   519  		return nil, err
   520  	}
   521  	s.findTagsForEvent(ctx, event)
   522  	if err != nil {
   523  		return nil, err
   524  	}
   525  	s.findOwnedByForEvent(ctx, event)
   526  	if err != nil {
   527  		return nil, err
   528  	}
   529  
   530  	return event, nil
   531  }
   532  
   533  func (s *SqlStore) findOrganizersForEvent(ctx context.Context, event *Event) error {
   534  	organizers := []string{}
   535  	err := s.db.SelectContext(ctx, &organizers, `
   536          SELECT organizer
   537          FROM events_organizer
   538          WHERE event_id = $1
   539      `, event.ID)
   540  	if err == sql.ErrNoRows {
   541  		return nil
   542  	}
   543  	if err != nil {
   544  		return err
   545  	}
   546  	event.Organizers = organizers
   547  	return nil
   548  }
   549  
   550  func (s *SqlStore) findInvolvedForEvent(ctx context.Context, event *Event) error {
   551  	involved := []Involved{}
   552  	err := s.db.SelectContext(ctx, &involved, `
   553          SELECT name, description
   554          FROM events_involved
   555          WHERE event_id = $1
   556      `, event.ID)
   557  	if err == sql.ErrNoRows {
   558  		return nil
   559  	}
   560  	if err != nil {
   561  		return err
   562  	}
   563  	event.Involved = involved
   564  	return nil
   565  }
   566  
   567  func (s *SqlStore) findTagsForEvent(ctx context.Context, event *Event) error {
   568  	tags := []string{}
   569  	err := s.db.SelectContext(ctx, &tags, `
   570          SELECT tag
   571          FROM events_tags
   572          WHERE event_id = $1
   573      `, event.ID)
   574  	if err == sql.ErrNoRows {
   575  		return nil
   576  	}
   577  	if err != nil {
   578  		return err
   579  	}
   580  	event.Tags = tags
   581  	return nil
   582  }
   583  
   584  func (s *SqlStore) findOwnedByForEvent(ctx context.Context, event *Event) error {
   585  	ownedBy := []string{}
   586  	err := s.db.SelectContext(ctx, &ownedBy, `
   587          SELECT user_id
   588          FROM events_owned_by
   589          WHERE event_id = $1
   590      `, event.ID)
   591  	if err == sql.ErrNoRows {
   592  		return nil
   593  	}
   594  	if err != nil {
   595  		return err
   596  	}
   597  	event.OwnedBy = ownedBy
   598  	return nil
   599  }
   600  
   601  var sortableFields = map[string]string{
   602  	"id":           "id",
   603  	"deactivated":  "deactivated",
   604  	"published":    "published",
   605  	"canceled":     "canceled",
   606  	"parent":       "parent",
   607  	"parentListed": "parentListed",
   608  	"name":         "name",
   609  	"location":     "location",
   610  	"start":        "start",
   611  	"end":          "end",
   612  }
   613  
   614  func (s *SqlStore) Find(ctx context.Context, params *crud.FindParams[FindFilters]) ([]*Event, int, error) {
   615  	query := `
   616          SELECT
   617              events.id,
   618              events.published,
   619              events.deactivated,
   620              events.canceled,
   621              events.parent,
   622              events.parentListed,
   623              events.name,
   624              events.location,
   625              events.location2,
   626              events.description,
   627              events.start,
   628              events.end,
   629              events.image
   630          FROM
   631              events
   632      `
   633  	joinQuery := ""
   634  	whereStatements := []string{}
   635  	sqlParams := make(map[string]interface{})
   636  	if params != nil {
   637  		if params.Filters != nil {
   638  			if params.Filters.ID != nil {
   639  				whereStatements = append(whereStatements, "events.id=:id")
   640  				sqlParams["id"] = params.Filters.ID
   641  			}
   642  			if params.Filters.Deactivated != nil {
   643  				whereStatements = append(whereStatements, "events.deactivated=:deactivated")
   644  				sqlParams["deactivated"] = params.Filters.Deactivated
   645  			}
   646  			if params.Filters.Published != nil {
   647  				whereStatements = append(whereStatements, "events.published=:published")
   648  				sqlParams["published"] = params.Filters.Published
   649  			}
   650  			if params.Filters.Canceled != nil {
   651  				whereStatements = append(whereStatements, "events.canceled=:canceled")
   652  				sqlParams["canceled"] = params.Filters.Canceled
   653  			}
   654  			if params.Filters.Parent != nil {
   655  				whereStatements = append(whereStatements, "events.parent=:parent")
   656  				sqlParams["parent"] = params.Filters.Parent
   657  			}
   658  			if params.Filters.Name != nil {
   659  				whereStatements = append(whereStatements, "events.name=:name")
   660  				sqlParams["name"] = params.Filters.Name
   661  			}
   662  			if params.Filters.LikeName != nil {
   663  				whereStatements = append(whereStatements, "events.name LIKE :likeName")
   664  				sqlParams["likeName"] = fmt.Sprintf("%%%s%%", *params.Filters.LikeName)
   665  			}
   666  			if params.Filters.Organizers != nil {
   667  				joinQuery += " LEFT JOIN events_organizer ON events.id = events_organizer.event_id"
   668  				organizerStatemtes := make([]string, len(params.Filters.Organizers))
   669  				for i, organizer := range params.Filters.Organizers {
   670  					organizerRef := fmt.Sprintf("organizer%d", i)
   671  					organizerStatemtes[i] = fmt.Sprintf("events_organizer.organizer=:%s", organizerRef)
   672  					sqlParams[organizerRef] = organizer
   673  				}
   674  				whereStatements = append(whereStatements, fmt.Sprintf("(%s)", strings.Join(organizerStatemtes, " OR ")))
   675  			}
   676  			if params.Filters.Location != nil {
   677  				whereStatements = append(whereStatements, "events.location=:location")
   678  				sqlParams["Location"] = params.Filters.Location
   679  			}
   680  			if params.Filters.Location2 != nil {
   681  				whereStatements = append(whereStatements, "events.location2=:location2")
   682  				sqlParams["Location2"] = params.Filters.Location2
   683  			}
   684  			if params.Filters.Description != nil {
   685  				whereStatements = append(whereStatements, "events.description=:description")
   686  				sqlParams["Description"] = params.Filters.Description
   687  			}
   688  			if params.Filters.Start != nil {
   689  				whereStatements = append(whereStatements, "events.start=:start")
   690  				sqlParams["start"] = params.Filters.Start
   691  			}
   692  			if params.Filters.StartBefore != nil {
   693  				whereStatements = append(whereStatements, "events.start<:startBefore")
   694  				sqlParams["startBefore"] = params.Filters.StartBefore
   695  			}
   696  			if params.Filters.StartAfter != nil {
   697  				whereStatements = append(whereStatements, "events.start>:startAfter")
   698  				sqlParams["startAfter"] = params.Filters.StartAfter
   699  			}
   700  			if params.Filters.End != nil {
   701  				whereStatements = append(whereStatements, "events.end=:end")
   702  				sqlParams["end"] = params.Filters.End
   703  			}
   704  			if params.Filters.EndBefore != nil {
   705  				whereStatements = append(whereStatements, "events.end<:endBefore")
   706  				sqlParams["endBefore"] = params.Filters.EndBefore
   707  			}
   708  			if params.Filters.EndAfter != nil {
   709  				whereStatements = append(whereStatements, "events.end>:endAfter")
   710  				sqlParams["endAfter"] = params.Filters.EndAfter
   711  			}
   712  			if params.Filters.Tags != nil {
   713  				joinQuery += " JOIN events_tags ON events.id = events_tags.event_id"
   714  				tagStatemtes := make([]string, len(params.Filters.Tags))
   715  				for i, tag := range params.Filters.Tags {
   716  					tagRef := fmt.Sprintf("tag%d", i)
   717  					tagStatemtes[i] = fmt.Sprintf("events_tags.tag=:%s", tagRef)
   718  					sqlParams[tagRef] = tag
   719  				}
   720  				whereStatements = append(whereStatements, fmt.Sprintf("(%s)", strings.Join(tagStatemtes, " OR ")))
   721  			}
   722  			if params.Filters.OwnedBy != nil {
   723  				joinQuery += " LEFT JOIN events_owned_by ON events.id = events_owned_by.event_id"
   724  				ownedByStatements := []string{}
   725  				for i, ownedBy := range params.Filters.OwnedBy {
   726  					ownedByRef := fmt.Sprintf("ownedBy%d", i)
   727  					ownedByStatements = append(ownedByStatements, fmt.Sprintf("events_owned_by.user_id=:%s", ownedByRef))
   728  					sqlParams[ownedByRef] = ownedBy
   729  				}
   730  				if params.Filters.OwnedByOrOrganizers != nil {
   731  					joinQuery += " LEFT JOIN events_organizer ON events.id = events_organizer.event_id"
   732  					for i, ownedByGroup := range params.Filters.OwnedByOrOrganizers {
   733  						ownedByRef := fmt.Sprintf("ownedByOrganizer%d", i)
   734  						ownedByStatements = append(ownedByStatements, fmt.Sprintf("events_organizer.organizer=:%s", ownedByRef))
   735  						sqlParams[ownedByRef] = ownedByGroup
   736  					}
   737  				}
   738  				whereStatements = append(whereStatements, fmt.Sprintf("(%s)", strings.Join(ownedByStatements, " OR ")))
   739  			}
   740  
   741  			if joinQuery != "" {
   742  				query += " " + joinQuery
   743  			}
   744  			if len(whereStatements) > 0 {
   745  				query += " WHERE " + strings.Join(whereStatements, " AND ")
   746  			}
   747  			if joinQuery != "" {
   748  				query += " GROUP BY events.id"
   749  			}
   750  		}
   751  
   752  		if params.Sort != "" {
   753  			sort, ok := sortableFields[params.Sort]
   754  			if !ok {
   755  				return nil, 0, fmt.Errorf("find events: invalid sort field: %s", params.Sort)
   756  			}
   757  			order := "ASC"
   758  			if params.Order == "DESC" {
   759  				order = "DESC"
   760  			}
   761  			query += fmt.Sprintf(" ORDER BY events.%s %s", sort, order)
   762  		}
   763  		if params.Limit > 0 {
   764  			query += fmt.Sprintf(" LIMIT %d", params.Limit)
   765  		}
   766  		if params.Offset > 0 {
   767  			query += fmt.Sprintf(" OFFSET %d", params.Offset)
   768  		}
   769  	}
   770  	rows, err := s.db.NamedQueryContext(ctx, query, sqlParams)
   771  	if err != nil && err != sql.ErrNoRows {
   772  		return nil, 0, fmt.Errorf("find events: %s", err)
   773  	}
   774  	defer rows.Close()
   775  	events := make([]*Event, 0)
   776  	for rows.Next() {
   777  		event := Event{}
   778  		err := rows.StructScan(&event)
   779  		if err != nil {
   780  			return nil, 0, err
   781  		}
   782  
   783  		events = append(events, &event)
   784  	}
   785  
   786  	for i, event := range events {
   787  		err := s.findOrganizersForEvent(ctx, event)
   788  		if err != nil {
   789  			return nil, 0, err
   790  		}
   791  		err = s.findInvolvedForEvent(ctx, event)
   792  		if err != nil {
   793  			return nil, 0, err
   794  		}
   795  		err = s.findTagsForEvent(ctx, event)
   796  		if err != nil {
   797  			return nil, 0, err
   798  		}
   799  		err = s.findOwnedByForEvent(ctx, event)
   800  		if err != nil {
   801  			return nil, 0, err
   802  		}
   803  		events[i] = event
   804  	}
   805  
   806  	totalQuery := `
   807          SELECT COUNT(*) as total
   808          FROM (
   809              SELECT events.id
   810              FROM events
   811      `
   812  	if len(whereStatements) > 0 {
   813  		if joinQuery != "" {
   814  			totalQuery += " " + joinQuery
   815  		}
   816  		totalQuery += " WHERE " + strings.Join(whereStatements, " AND ")
   817  		if joinQuery != "" {
   818  			totalQuery += " GROUP BY events.id"
   819  		}
   820  	}
   821  	totalQuery += ")"
   822  
   823  	totalEvents := struct {
   824  		Total int `db:"total"`
   825  	}{}
   826  	rows2, err := s.db.NamedQueryContext(ctx, totalQuery, sqlParams)
   827  	if err != nil && err != sql.ErrNoRows {
   828  		return nil, 0, fmt.Errorf("find events: total: %s", err)
   829  	}
   830  	defer rows2.Close()
   831  	if rows2.Next() {
   832  		rows2.StructScan(&totalEvents)
   833  	}
   834  
   835  	return events, totalEvents.Total, nil
   836  }