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 }