aboutsummaryrefslogtreecommitdiffstats
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/conference/model.go (renamed from pkg/schedule/model.go)2
-rw-r--r--pkg/conference/parse.go (renamed from pkg/schedule/parse.go)2
-rw-r--r--pkg/conference/service.go218
-rw-r--r--pkg/database/migrations/0003_multi_conference.sql13
-rw-r--r--pkg/database/migrations/0004_user_admins.sql2
-rw-r--r--pkg/database/query/conferences.sql21
-rw-r--r--pkg/database/query/favourites.sql10
-rw-r--r--pkg/database/sqlc/conferences.sql.go119
-rw-r--r--pkg/database/sqlc/favourites.sql.go76
-rw-r--r--pkg/database/sqlc/models.go18
-rw-r--r--pkg/database/sqlc/users.sql.go36
-rw-r--r--pkg/favourites/service.go63
-rw-r--r--pkg/ical/service.go17
-rw-r--r--pkg/schedule/service.go116
-rw-r--r--pkg/session/memory.go3
-rw-r--r--pkg/session/service.go3
16 files changed, 543 insertions, 176 deletions
diff --git a/pkg/schedule/model.go b/pkg/conference/model.go
index fcf39a5..343271f 100644
--- a/pkg/schedule/model.go
+++ b/pkg/conference/model.go
@@ -1,4 +1,4 @@
-package schedule
+package conference
import "time"
diff --git a/pkg/schedule/parse.go b/pkg/conference/parse.go
index b0ed8df..b7f9dba 100644
--- a/pkg/schedule/parse.go
+++ b/pkg/conference/parse.go
@@ -1,4 +1,4 @@
-package schedule
+package conference
import (
"encoding/xml"
diff --git a/pkg/conference/service.go b/pkg/conference/service.go
new file mode 100644
index 0000000..59c2c8c
--- /dev/null
+++ b/pkg/conference/service.go
@@ -0,0 +1,218 @@
+package conference
+
+import (
+ "bufio"
+ "context"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "net/http"
+ "sync"
+ "time"
+
+ "github.com/LMBishop/confplanner/pkg/database/sqlc"
+ "github.com/jackc/pgx/v5/pgtype"
+ "github.com/jackc/pgx/v5/pgxpool"
+)
+
+type Service interface {
+ CreateConference(url string) (*sqlc.Conference, error)
+ DeleteConference(id int32) error
+ GetConferences() ([]sqlc.Conference, error)
+ GetSchedule(id int32) (*Schedule, time.Time, error)
+ GetEventByID(conferenceID, eventID int32) (*Event, error)
+}
+
+type loadedConference struct {
+ pentabarfUrl string
+ schedule *Schedule
+ eventsById map[int32]Event
+ lastUpdated time.Time
+ lock sync.RWMutex
+}
+
+var (
+ ErrConferenceNotFound = errors.New("conference not found")
+ ErrScheduleFetch = errors.New("could not fetch schedule")
+)
+
+type service struct {
+ conferences map[int32]*loadedConference
+ lock sync.RWMutex
+ pool *pgxpool.Pool
+}
+
+// TODO: Create a service implementation that persists to DB
+// and isn't in memory
+func NewService(pool *pgxpool.Pool) (Service, error) {
+ service := &service{
+ pool: pool,
+ conferences: make(map[int32]*loadedConference),
+ }
+
+ queries := sqlc.New(pool)
+ conferences, err := queries.GetConferences(context.Background())
+ if err != nil {
+ return nil, err
+ }
+
+ for _, conference := range conferences {
+ c := &loadedConference{
+ pentabarfUrl: conference.Url,
+ lastUpdated: time.Unix(0, 0),
+ }
+ service.conferences[conference.ID] = c
+ }
+
+ return service, nil
+}
+
+func (s *service) CreateConference(url string) (*sqlc.Conference, error) {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ c := &loadedConference{
+ pentabarfUrl: url,
+ lastUpdated: time.Unix(0, 0),
+ }
+ err := c.updateSchedule()
+ if err != nil {
+ return nil, errors.Join(ErrScheduleFetch, err)
+ }
+
+ queries := sqlc.New(s.pool)
+
+ conference, err := queries.CreateConference(context.Background(), sqlc.CreateConferenceParams{
+ Url: url,
+ Title: pgtype.Text{String: c.schedule.Conference.Title, Valid: true},
+ Venue: pgtype.Text{String: c.schedule.Conference.Venue, Valid: true},
+ City: pgtype.Text{String: c.schedule.Conference.City, Valid: true},
+ })
+ if err != nil {
+ return nil, fmt.Errorf("could not create conference: %w", err)
+ }
+
+ s.conferences[conference.ID] = c
+
+ return &conference, nil
+}
+
+func (s *service) DeleteConference(id int32) error {
+ s.lock.Lock()
+ defer s.lock.Unlock()
+
+ queries := sqlc.New(s.pool)
+ err := queries.DeleteConference(context.Background(), id)
+ if err != nil {
+ return fmt.Errorf("could not delete conference: %w", err)
+ }
+
+ delete(s.conferences, id)
+ return nil
+}
+
+func (s *service) GetConferences() ([]sqlc.Conference, error) {
+ s.lock.RLock()
+ defer s.lock.RUnlock()
+
+ queries := sqlc.New(s.pool)
+ return queries.GetConferences(context.Background())
+}
+
+func (s *service) GetSchedule(id int32) (*Schedule, time.Time, error) {
+ s.lock.RLock()
+ defer s.lock.RUnlock()
+
+ c, ok := s.conferences[id]
+ if !ok {
+ return nil, time.Time{}, ErrConferenceNotFound
+ }
+
+ if err := c.updateSchedule(); err != nil {
+ return nil, time.Time{}, err
+ }
+
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+
+ queries := sqlc.New(s.pool)
+ if _, err := queries.UpdateConferenceDetails(context.Background(), sqlc.UpdateConferenceDetailsParams{
+ ID: id,
+ Title: pgtype.Text{String: c.schedule.Conference.Title, Valid: true},
+ Venue: pgtype.Text{String: c.schedule.Conference.Venue, Valid: true},
+ City: pgtype.Text{String: c.schedule.Conference.City, Valid: true},
+ }); err != nil {
+ return nil, time.Time{}, fmt.Errorf("failed to update cached conference details: %w", err)
+ }
+
+ return c.schedule, c.lastUpdated, nil
+}
+
+func (s *service) GetEventByID(conferenceID, eventID int32) (*Event, error) {
+ s.lock.RLock()
+ defer s.lock.RUnlock()
+
+ c, ok := s.conferences[conferenceID]
+ if !ok {
+ return nil, ErrConferenceNotFound
+ }
+
+ c.lock.RLock()
+ defer c.lock.RUnlock()
+
+ event := c.eventsById[eventID]
+
+ return &event, nil
+}
+
+func (c *loadedConference) hasScheduleExpired() bool {
+ expire := c.lastUpdated.Add(15 * time.Minute)
+ return time.Now().After(expire)
+}
+
+func (c *loadedConference) updateSchedule() error {
+ if !c.hasScheduleExpired() {
+ return nil
+ }
+
+ if !c.lock.TryLock() {
+ // don't block if another goroutine is already fetching
+ return nil
+ }
+ defer c.lock.Unlock()
+
+ res, err := http.Get(c.pentabarfUrl)
+ if err != nil {
+ return err
+ }
+
+ reader := bufio.NewReader(res.Body)
+
+ var schedule schedule
+
+ decoder := xml.NewDecoder(reader)
+ if err := decoder.Decode(&schedule); err != nil {
+ return fmt.Errorf("failed to decode XML: %w", err)
+ }
+
+ var newSchedule Schedule
+ err = newSchedule.Scan(schedule)
+ if err != nil {
+ return fmt.Errorf("failed to scan schedule: %w", err)
+ }
+
+ c.schedule = &newSchedule
+ c.lastUpdated = time.Now()
+
+ c.eventsById = make(map[int32]Event)
+
+ for _, day := range newSchedule.Days {
+ for _, room := range day.Rooms {
+ for _, event := range room.Events {
+ c.eventsById[event.ID] = event
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/pkg/database/migrations/0003_multi_conference.sql b/pkg/database/migrations/0003_multi_conference.sql
new file mode 100644
index 0000000..31a1f58
--- /dev/null
+++ b/pkg/database/migrations/0003_multi_conference.sql
@@ -0,0 +1,13 @@
+-- +goose Up
+CREATE TABLE conferences (
+ id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
+ url text NOT NULL,
+ title text,
+ venue text,
+ city text
+);
+
+TRUNCATE TABLE favourites CONTINUE IDENTITY;
+ALTER TABLE favourites ADD conference_id int NOT NULL REFERENCES conferences(id) ON DELETE CASCADE;
+ALTER TABLE favourites DROP CONSTRAINT favourites_user_id_event_guid_event_id_key;
+ALTER TABLE favourites ADD UNIQUE(user_id, event_guid, event_id, conference_id);
diff --git a/pkg/database/migrations/0004_user_admins.sql b/pkg/database/migrations/0004_user_admins.sql
new file mode 100644
index 0000000..5c4efb3
--- /dev/null
+++ b/pkg/database/migrations/0004_user_admins.sql
@@ -0,0 +1,2 @@
+-- +goose Up
+ALTER TABLE users ADD admin boolean NOT NULL DEFAULT false;
diff --git a/pkg/database/query/conferences.sql b/pkg/database/query/conferences.sql
new file mode 100644
index 0000000..7acac3f
--- /dev/null
+++ b/pkg/database/query/conferences.sql
@@ -0,0 +1,21 @@
+-- name: CreateConference :one
+INSERT INTO conferences (
+ url, title, venue, city
+) VALUES (
+ $1, $2, $3, $4
+)
+RETURNING *;
+
+-- name: UpdateConferenceDetails :one
+UPDATE conferences SET (
+ title, venue, city
+) = ($2, $3, $4)
+WHERE id = $1
+RETURNING *;
+
+-- name: GetConferences :many
+SELECT * FROM conferences;
+
+-- name: DeleteConference :exec
+DELETE FROM conferences
+WHERE id = $1;
diff --git a/pkg/database/query/favourites.sql b/pkg/database/query/favourites.sql
index 94e914c..a903ac7 100644
--- a/pkg/database/query/favourites.sql
+++ b/pkg/database/query/favourites.sql
@@ -1,12 +1,16 @@
+-- name: GetFavouritesForUserConference :many
+SELECT * FROM favourites
+WHERE user_id = $1 AND conference_id = $2;
+
-- name: GetFavouritesForUser :many
SELECT * FROM favourites
WHERE user_id = $1;
-- name: CreateFavourite :one
INSERT INTO favourites (
- user_id, event_guid, event_id
+ user_id, event_guid, event_id, conference_id
) VALUES (
- $1, $2, $3
+ $1, $2, $3, $4
)
RETURNING *;
@@ -16,4 +20,4 @@ WHERE id = $1;
-- name: DeleteFavouriteByEventDetails :execrows
DELETE FROM favourites
-WHERE (event_guid = $1 OR event_id = $2) AND user_id = $3;
+WHERE (event_guid = $1 OR event_id = $2) AND user_id = $3 AND conference_id = $4;
diff --git a/pkg/database/sqlc/conferences.sql.go b/pkg/database/sqlc/conferences.sql.go
new file mode 100644
index 0000000..1345185
--- /dev/null
+++ b/pkg/database/sqlc/conferences.sql.go
@@ -0,0 +1,119 @@
+// Code generated by sqlc. DO NOT EDIT.
+// versions:
+// sqlc v1.29.0
+// source: conferences.sql
+
+package sqlc
+
+import (
+ "context"
+
+ "github.com/jackc/pgx/v5/pgtype"
+)
+
+const createConference = `-- name: CreateConference :one
+INSERT INTO conferences (
+ url, title, venue, city
+) VALUES (
+ $1, $2, $3, $4
+)
+RETURNING id, url, title, venue, city
+`
+
+type CreateConferenceParams struct {
+ Url string `json:"url"`
+ Title pgtype.Text `json:"title"`
+ Venue pgtype.Text `json:"venue"`
+ City pgtype.Text `json:"city"`
+}
+
+func (q *Queries) CreateConference(ctx context.Context, arg CreateConferenceParams) (Conference, error) {
+ row := q.db.QueryRow(ctx, createConference,
+ arg.Url,
+ arg.Title,
+ arg.Venue,
+ arg.City,
+ )
+ var i Conference
+ err := row.Scan(
+ &i.ID,
+ &i.Url,
+ &i.Title,
+ &i.Venue,
+ &i.City,
+ )
+ return i, err
+}
+
+const deleteConference = `-- name: DeleteConference :exec
+DELETE FROM conferences
+WHERE id = $1
+`
+
+func (q *Queries) DeleteConference(ctx context.Context, id int32) error {
+ _, err := q.db.Exec(ctx, deleteConference, id)
+ return err
+}
+
+const getConferences = `-- name: GetConferences :many
+SELECT id, url, title, venue, city FROM conferences
+`
+
+func (q *Queries) GetConferences(ctx context.Context) ([]Conference, error) {
+ rows, err := q.db.Query(ctx, getConferences)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Conference
+ for rows.Next() {
+ var i Conference
+ if err := rows.Scan(
+ &i.ID,
+ &i.Url,
+ &i.Title,
+ &i.Venue,
+ &i.City,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const updateConferenceDetails = `-- name: UpdateConferenceDetails :one
+UPDATE conferences SET (
+ title, venue, city
+) = ($2, $3, $4)
+WHERE id = $1
+RETURNING id, url, title, venue, city
+`
+
+type UpdateConferenceDetailsParams struct {
+ ID int32 `json:"id"`
+ Title pgtype.Text `json:"title"`
+ Venue pgtype.Text `json:"venue"`
+ City pgtype.Text `json:"city"`
+}
+
+func (q *Queries) UpdateConferenceDetails(ctx context.Context, arg UpdateConferenceDetailsParams) (Conference, error) {
+ row := q.db.QueryRow(ctx, updateConferenceDetails,
+ arg.ID,
+ arg.Title,
+ arg.Venue,
+ arg.City,
+ )
+ var i Conference
+ err := row.Scan(
+ &i.ID,
+ &i.Url,
+ &i.Title,
+ &i.Venue,
+ &i.City,
+ )
+ return i, err
+}
diff --git a/pkg/database/sqlc/favourites.sql.go b/pkg/database/sqlc/favourites.sql.go
index b13261f..d28470d 100644
--- a/pkg/database/sqlc/favourites.sql.go
+++ b/pkg/database/sqlc/favourites.sql.go
@@ -13,27 +13,34 @@ import (
const createFavourite = `-- name: CreateFavourite :one
INSERT INTO favourites (
- user_id, event_guid, event_id
+ user_id, event_guid, event_id, conference_id
) VALUES (
- $1, $2, $3
+ $1, $2, $3, $4
)
-RETURNING id, user_id, event_guid, event_id
+RETURNING id, user_id, event_guid, event_id, conference_id
`
type CreateFavouriteParams struct {
- UserID int32 `json:"user_id"`
- EventGuid pgtype.UUID `json:"event_guid"`
- EventID pgtype.Int4 `json:"event_id"`
+ UserID int32 `json:"user_id"`
+ EventGuid pgtype.UUID `json:"event_guid"`
+ EventID pgtype.Int4 `json:"event_id"`
+ ConferenceID int32 `json:"conference_id"`
}
func (q *Queries) CreateFavourite(ctx context.Context, arg CreateFavouriteParams) (Favourite, error) {
- row := q.db.QueryRow(ctx, createFavourite, arg.UserID, arg.EventGuid, arg.EventID)
+ row := q.db.QueryRow(ctx, createFavourite,
+ arg.UserID,
+ arg.EventGuid,
+ arg.EventID,
+ arg.ConferenceID,
+ )
var i Favourite
err := row.Scan(
&i.ID,
&i.UserID,
&i.EventGuid,
&i.EventID,
+ &i.ConferenceID,
)
return i, err
}
@@ -50,17 +57,23 @@ func (q *Queries) DeleteFavourite(ctx context.Context, id int32) error {
const deleteFavouriteByEventDetails = `-- name: DeleteFavouriteByEventDetails :execrows
DELETE FROM favourites
-WHERE (event_guid = $1 OR event_id = $2) AND user_id = $3
+WHERE (event_guid = $1 OR event_id = $2) AND user_id = $3 AND conference_id = $4
`
type DeleteFavouriteByEventDetailsParams struct {
- EventGuid pgtype.UUID `json:"event_guid"`
- EventID pgtype.Int4 `json:"event_id"`
- UserID int32 `json:"user_id"`
+ EventGuid pgtype.UUID `json:"event_guid"`
+ EventID pgtype.Int4 `json:"event_id"`
+ UserID int32 `json:"user_id"`
+ ConferenceID int32 `json:"conference_id"`
}
func (q *Queries) DeleteFavouriteByEventDetails(ctx context.Context, arg DeleteFavouriteByEventDetailsParams) (int64, error) {
- result, err := q.db.Exec(ctx, deleteFavouriteByEventDetails, arg.EventGuid, arg.EventID, arg.UserID)
+ result, err := q.db.Exec(ctx, deleteFavouriteByEventDetails,
+ arg.EventGuid,
+ arg.EventID,
+ arg.UserID,
+ arg.ConferenceID,
+ )
if err != nil {
return 0, err
}
@@ -68,7 +81,7 @@ func (q *Queries) DeleteFavouriteByEventDetails(ctx context.Context, arg DeleteF
}
const getFavouritesForUser = `-- name: GetFavouritesForUser :many
-SELECT id, user_id, event_guid, event_id FROM favourites
+SELECT id, user_id, event_guid, event_id, conference_id FROM favourites
WHERE user_id = $1
`
@@ -86,6 +99,43 @@ func (q *Queries) GetFavouritesForUser(ctx context.Context, userID int32) ([]Fav
&i.UserID,
&i.EventGuid,
&i.EventID,
+ &i.ConferenceID,
+ ); err != nil {
+ return nil, err
+ }
+ items = append(items, i)
+ }
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ return items, nil
+}
+
+const getFavouritesForUserConference = `-- name: GetFavouritesForUserConference :many
+SELECT id, user_id, event_guid, event_id, conference_id FROM favourites
+WHERE user_id = $1 AND conference_id = $2
+`
+
+type GetFavouritesForUserConferenceParams struct {
+ UserID int32 `json:"user_id"`
+ ConferenceID int32 `json:"conference_id"`
+}
+
+func (q *Queries) GetFavouritesForUserConference(ctx context.Context, arg GetFavouritesForUserConferenceParams) ([]Favourite, error) {
+ rows, err := q.db.Query(ctx, getFavouritesForUserConference, arg.UserID, arg.ConferenceID)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ var items []Favourite
+ for rows.Next() {
+ var i Favourite
+ if err := rows.Scan(
+ &i.ID,
+ &i.UserID,
+ &i.EventGuid,
+ &i.EventID,
+ &i.ConferenceID,
); err != nil {
return nil, err
}
diff --git a/pkg/database/sqlc/models.go b/pkg/database/sqlc/models.go
index 57fd082..b7dfc19 100644
--- a/pkg/database/sqlc/models.go
+++ b/pkg/database/sqlc/models.go
@@ -15,15 +15,25 @@ type Calendar struct {
Key string `json:"key"`
}
+type Conference struct {
+ ID int32 `json:"id"`
+ Url string `json:"url"`
+ Title pgtype.Text `json:"title"`
+ Venue pgtype.Text `json:"venue"`
+ City pgtype.Text `json:"city"`
+}
+
type Favourite struct {
- ID int32 `json:"id"`
- UserID int32 `json:"user_id"`
- EventGuid pgtype.UUID `json:"event_guid"`
- EventID pgtype.Int4 `json:"event_id"`
+ ID int32 `json:"id"`
+ UserID int32 `json:"user_id"`
+ EventGuid pgtype.UUID `json:"event_guid"`
+ EventID pgtype.Int4 `json:"event_id"`
+ ConferenceID int32 `json:"conference_id"`
}
type User struct {
ID int32 `json:"id"`
Username string `json:"username"`
Password pgtype.Text `json:"password"`
+ Admin bool `json:"admin"`
}
diff --git a/pkg/database/sqlc/users.sql.go b/pkg/database/sqlc/users.sql.go
index cf0aeb9..45ae019 100644
--- a/pkg/database/sqlc/users.sql.go
+++ b/pkg/database/sqlc/users.sql.go
@@ -17,7 +17,7 @@ INSERT INTO users (
) VALUES (
$1, $2
)
-RETURNING id, username, password
+RETURNING id, username, password, admin
`
type CreateUserParams struct {
@@ -28,7 +28,12 @@ type CreateUserParams struct {
func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error) {
row := q.db.QueryRow(ctx, createUser, arg.Username, arg.Password)
var i User
- err := row.Scan(&i.ID, &i.Username, &i.Password)
+ err := row.Scan(
+ &i.ID,
+ &i.Username,
+ &i.Password,
+ &i.Admin,
+ )
return i, err
}
@@ -43,31 +48,41 @@ func (q *Queries) DeleteUser(ctx context.Context, id int32) error {
}
const getUserByID = `-- name: GetUserByID :one
-SELECT id, username, password FROM users
+SELECT id, username, password, admin FROM users
WHERE id = $1 LIMIT 1
`
func (q *Queries) GetUserByID(ctx context.Context, id int32) (User, error) {
row := q.db.QueryRow(ctx, getUserByID, id)
var i User
- err := row.Scan(&i.ID, &i.Username, &i.Password)
+ err := row.Scan(
+ &i.ID,
+ &i.Username,
+ &i.Password,
+ &i.Admin,
+ )
return i, err
}
const getUserByName = `-- name: GetUserByName :one
-SELECT id, username, password FROM users
+SELECT id, username, password, admin FROM users
WHERE username = $1 LIMIT 1
`
func (q *Queries) GetUserByName(ctx context.Context, username string) (User, error) {
row := q.db.QueryRow(ctx, getUserByName, username)
var i User
- err := row.Scan(&i.ID, &i.Username, &i.Password)
+ err := row.Scan(
+ &i.ID,
+ &i.Username,
+ &i.Password,
+ &i.Admin,
+ )
return i, err
}
const listUsers = `-- name: ListUsers :many
-SELECT id, username, password FROM users
+SELECT id, username, password, admin FROM users
ORDER BY username
`
@@ -80,7 +95,12 @@ func (q *Queries) ListUsers(ctx context.Context) ([]User, error) {
var items []User
for rows.Next() {
var i User
- if err := rows.Scan(&i.ID, &i.Username, &i.Password); err != nil {
+ if err := rows.Scan(
+ &i.ID,
+ &i.Username,
+ &i.Password,
+ &i.Admin,
+ ); err != nil {
return nil, err
}
items = append(items, i)
diff --git a/pkg/favourites/service.go b/pkg/favourites/service.go
index cba249e..f73f400 100644
--- a/pkg/favourites/service.go
+++ b/pkg/favourites/service.go
@@ -12,9 +12,10 @@ import (
)
type Service interface {
- GetFavouritesForUser(id int32) (*[]sqlc.Favourite, error)
- CreateFavouriteForUser(id int32, eventGuid pgtype.UUID, eventId *int32) (*sqlc.Favourite, error)
- DeleteFavouriteForUserByEventDetails(id int32, eventGuid pgtype.UUID, eventId *int32) error
+ GetAllFavouritesForUser(id int32) (*[]sqlc.Favourite, error)
+ GetFavouritesForUserConference(id int32, conference int32) (*[]sqlc.Favourite, error)
+ CreateFavouriteForUser(id int32, eventGUID pgtype.UUID, eventID *int32, conferenceID int32) (*sqlc.Favourite, error)
+ DeleteFavouriteForUserByEventDetails(id int32, eventGUID pgtype.UUID, eventID *int32, conferenceID int32) error
}
var (
@@ -32,21 +33,22 @@ func NewService(pool *pgxpool.Pool) Service {
}
}
-func (s *service) CreateFavouriteForUser(id int32, eventGuid pgtype.UUID, eventId *int32) (*sqlc.Favourite, error) {
+func (s *service) CreateFavouriteForUser(userID int32, eventGUID pgtype.UUID, eventID *int32, conferenceID int32) (*sqlc.Favourite, error) {
queries := sqlc.New(s.pool)
- var pgEventId pgtype.Int4
- if eventId != nil {
- pgEventId = pgtype.Int4{
- Int32: *eventId,
+ var pgEventID pgtype.Int4
+ if eventID != nil {
+ pgEventID = pgtype.Int4{
+ Int32: *eventID,
Valid: true,
}
}
favourite, err := queries.CreateFavourite(context.Background(), sqlc.CreateFavouriteParams{
- UserID: id,
- EventGuid: eventGuid,
- EventID: pgEventId,
+ UserID: userID,
+ EventGuid: eventGUID,
+ EventID: pgEventID,
+ ConferenceID: conferenceID,
})
if err != nil {
return nil, fmt.Errorf("could not create favourite: %w", err)
@@ -55,10 +57,10 @@ func (s *service) CreateFavouriteForUser(id int32, eventGuid pgtype.UUID, eventI
return &favourite, nil
}
-func (s *service) GetFavouritesForUser(id int32) (*[]sqlc.Favourite, error) {
+func (s *service) GetAllFavouritesForUser(userID int32) (*[]sqlc.Favourite, error) {
queries := sqlc.New(s.pool)
- favourites, err := queries.GetFavouritesForUser(context.Background(), id)
+ favourites, err := queries.GetFavouritesForUser(context.Background(), userID)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
empty := make([]sqlc.Favourite, 0)
@@ -70,20 +72,39 @@ func (s *service) GetFavouritesForUser(id int32) (*[]sqlc.Favourite, error) {
return &favourites, nil
}
-func (s *service) DeleteFavouriteForUserByEventDetails(id int32, eventGuid pgtype.UUID, eventId *int32) error {
+func (s *service) GetFavouritesForUserConference(userID int32, conferenceID int32) (*[]sqlc.Favourite, error) {
queries := sqlc.New(s.pool)
- var pgEventId pgtype.Int4
- if eventId != nil {
- pgEventId = pgtype.Int4{
- Int32: *eventId,
+ favourites, err := queries.GetFavouritesForUserConference(context.Background(), sqlc.GetFavouritesForUserConferenceParams{
+ UserID: userID,
+ ConferenceID: conferenceID,
+ })
+ if err != nil {
+ if errors.Is(err, pgx.ErrNoRows) {
+ empty := make([]sqlc.Favourite, 0)
+ return &empty, nil
+ }
+ return nil, fmt.Errorf("could not fetch user: %w", err)
+ }
+
+ return &favourites, nil
+}
+
+func (s *service) DeleteFavouriteForUserByEventDetails(id int32, eventGUID pgtype.UUID, eventID *int32, conferenceID int32) error {
+ queries := sqlc.New(s.pool)
+
+ var pgEventID pgtype.Int4
+ if eventID != nil {
+ pgEventID = pgtype.Int4{
+ Int32: *eventID,
Valid: true,
}
}
rowsAffected, err := queries.DeleteFavouriteByEventDetails(context.Background(), sqlc.DeleteFavouriteByEventDetailsParams{
- EventGuid: eventGuid,
- EventID: pgEventId,
- UserID: id,
+ EventGuid: eventGUID,
+ EventID: pgEventID,
+ UserID: id,
+ ConferenceID: conferenceID,
})
if err != nil {
return fmt.Errorf("could not delete favourite: %w", err)
diff --git a/pkg/ical/service.go b/pkg/ical/service.go
index d93c846..bc82248 100644
--- a/pkg/ical/service.go
+++ b/pkg/ical/service.go
@@ -6,9 +6,9 @@ import (
"strings"
"time"
+ "github.com/LMBishop/confplanner/pkg/conference"
"github.com/LMBishop/confplanner/pkg/database/sqlc"
"github.com/LMBishop/confplanner/pkg/favourites"
- "github.com/LMBishop/confplanner/pkg/schedule"
"github.com/microcosm-cc/bluemonday"
)
@@ -23,28 +23,31 @@ var (
type service struct {
favouritesService favourites.Service
- scheduleService schedule.Service
+ conferenceService conference.Service
}
func NewService(
favouritesService favourites.Service,
- scheduleService schedule.Service,
+ conferenceService conference.Service,
) Service {
return &service{
favouritesService: favouritesService,
- scheduleService: scheduleService,
+ conferenceService: conferenceService,
}
}
func (s *service) GenerateIcalForCalendar(calendar sqlc.Calendar) (string, error) {
- favourites, err := s.favouritesService.GetFavouritesForUser(calendar.UserID)
+ favourites, err := s.favouritesService.GetAllFavouritesForUser(calendar.UserID)
if err != nil {
return "", err
}
- events := make([]schedule.Event, 0)
+ events := make([]conference.Event, 0)
for _, favourite := range *favourites {
- event := s.scheduleService.GetEventByID(favourite.EventID.Int32)
+ event, err := s.conferenceService.GetEventByID(favourite.ConferenceID, favourite.EventID.Int32)
+ if err != nil {
+ continue
+ }
events = append(events, *event)
}
diff --git a/pkg/schedule/service.go b/pkg/schedule/service.go
deleted file mode 100644
index a73a469..0000000
--- a/pkg/schedule/service.go
+++ /dev/null
@@ -1,116 +0,0 @@
-package schedule
-
-import (
- "bufio"
- "encoding/xml"
- "fmt"
- "net/http"
- "sync"
- "time"
-)
-
-type Service interface {
- GetSchedule() (*Schedule, time.Time, error)
- GetEventByID(id int32) *Event
-}
-
-type service struct {
- pentabarfUrl string
-
- schedule *Schedule
- eventsById map[int32]Event
- lastUpdated time.Time
- accessLock sync.RWMutex
- updateLock sync.Mutex
-}
-
-// TODO: Create a service implementation that persists to DB
-// and isn't in memory
-func NewService(pentabarfUrl string) (Service, error) {
- service := &service{
- pentabarfUrl: pentabarfUrl,
- lastUpdated: time.Unix(0, 0),
- }
-
- err := service.updateSchedule()
- if err != nil {
- return nil, fmt.Errorf("could not read schedule from '%s' (is it a valid pentabarf XML file?): %w", pentabarfUrl, err)
- }
- return service, nil
-}
-
-func (s *service) GetSchedule() (*Schedule, time.Time, error) {
- err := s.updateSchedule()
- if err != nil {
- return nil, time.Time{}, err
- }
-
- s.accessLock.RLock()
- defer s.accessLock.RUnlock()
-
- return s.schedule, s.lastUpdated, nil
-}
-
-func (s *service) GetEventByID(id int32) *Event {
- s.accessLock.RLock()
- defer s.accessLock.RUnlock()
-
- event := s.eventsById[id]
-
- return &event
-}
-
-func (s *service) hasScheduleExpired() bool {
- expire := s.lastUpdated.Add(15 * time.Minute)
- return time.Now().After(expire)
-}
-
-func (s *service) updateSchedule() error {
- if !s.hasScheduleExpired() {
- return nil
- }
-
- if !s.updateLock.TryLock() {
- // don't block if another goroutine is already fetching
- return nil
- }
- defer s.updateLock.Unlock()
-
- res, err := http.Get(s.pentabarfUrl)
- if err != nil {
- return err
- }
-
- reader := bufio.NewReader(res.Body)
-
- var schedule schedule
-
- decoder := xml.NewDecoder(reader)
- if err := decoder.Decode(&schedule); err != nil {
- return fmt.Errorf("failed to decode XML: %w", err)
- }
-
- var newSchedule Schedule
- err = newSchedule.Scan(schedule)
- if err != nil {
- return fmt.Errorf("failed to scan schedule: %w", err)
- }
-
- s.accessLock.Lock()
- defer s.accessLock.Unlock()
-
- s.schedule = &newSchedule
- s.lastUpdated = time.Now()
-
- s.eventsById = make(map[int32]Event)
-
- for _, day := range newSchedule.Days {
- for _, room := range day.Rooms {
- for _, event := range room.Events {
- s.eventsById[event.ID] = event
- }
- }
- }
-
- return nil
-}
diff --git a/pkg/session/memory.go b/pkg/session/memory.go
index f02b792..8fa6076 100644
--- a/pkg/session/memory.go
+++ b/pkg/session/memory.go
@@ -40,7 +40,7 @@ func (s *memoryStore) GetBySID(sid uint) *UserSession {
return s.sessionsBySID[sid]
}
-func (s *memoryStore) Create(uid int32, username string, ip string, ua string) (*UserSession, error) {
+func (s *memoryStore) Create(uid int32, username string, ip string, ua string, admin bool) (*UserSession, error) {
token := generateSessionToken()
s.lock.Lock()
@@ -65,6 +65,7 @@ func (s *memoryStore) Create(uid int32, username string, ip string, ua string) (
IP: ip,
UserAgent: ua,
LoginTime: time.Now(),
+ Admin: admin,
}
s.sessionsByToken[token] = session
s.sessionsBySID[sessionId] = session
diff --git a/pkg/session/service.go b/pkg/session/service.go
index 83009bc..a693fe8 100644
--- a/pkg/session/service.go
+++ b/pkg/session/service.go
@@ -5,7 +5,7 @@ import "time"
type Service interface {
GetByToken(token string) *UserSession
GetBySID(sid uint) *UserSession
- Create(uid int32, username string, ip string, ua string) (*UserSession, error)
+ Create(uid int32, username string, ip string, ua string, admin bool) (*UserSession, error)
Destroy(sid uint) error
}
@@ -17,4 +17,5 @@ type UserSession struct {
IP string
LoginTime time.Time
UserAgent string
+ Admin bool
}