Skip to content

Commit

Permalink
mariadb: implement parts of ScheduleStorage
Browse files Browse the repository at this point in the history
  • Loading branch information
Wessie committed Mar 25, 2024
1 parent 0d430b0 commit f954fbb
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 19 deletions.
9 changes: 4 additions & 5 deletions radio.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func (s Status) Copy() Status {
}

// UserID is an identifier corresponding to an user
type UserID uint64
type UserID int32

// UserPermission is a permission for user authorization
type UserPermission string
Expand Down Expand Up @@ -1076,16 +1076,15 @@ func (day ScheduleDay) String() string {
type ScheduleEntry struct {
ID ScheduleID
// Weekday is the day this entry is for
//Weekday ScheduleDay
Weekday int
Weekday ScheduleDay
// Text is the actual body of the entry
Text string
// Owner is who "owns" this day for streaming rights
User *User
Owner *User
// UpdatedAt is when this was updated
UpdatedAt time.Time
// UpdatedBy is who updated this
// UpdatedBy User
UpdatedBy User
// Notification indicates if we should notify users of this entry
Notification bool
}
131 changes: 118 additions & 13 deletions storage/mariadb/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,139 @@ type ScheduleStorage struct {
handle handle
}

var latestScheduleQuery = `
SELECT
schedule.id AS id,
schedule.weekday AS weekday,
schedule.text AS text,
schedule.updated_at AS updated_at,
schedule.notification AS notification,
ub.id AS 'updatedby.id',
ub.user AS 'updatedby.username',
ub.pass AS 'updatedby.password',
IFNULL(ub.email, '') AS 'updatedby.email',
ub.ip AS 'updatedby.ip',
ub.updated_at AS 'updatedby.updated_at',
ub.deleted_at AS 'updatedby.deleted_at',
ub.created_at AS 'updatedby.created_at',
(SELECT group_concat(permission) FROM permissions WHERE user_id=ub.id) AS 'updatedby.userpermissions',
IFNULL(ub_djs.id, 0) AS 'updatedby.dj.id',
IFNULL(ub_djs.regex, '') AS 'updatedby.dj.regex',
IFNULL(ub_djs.djname, '') AS 'updatedby.dj.name',
IFNULL(ub_djs.djtext, '') AS 'updatedby.dj.text',
IFNULL(ub_djs.djimage, '') AS 'updatedby.dj.image',
IFNULL(ub_djs.visible, 0) AS 'updatedby.dj.visible',
IFNULL(ub_djs.priority, 0) AS 'updatedby.dj.priority',
IFNULL(ub_djs.role, '') AS 'updatedby.dj.role',
IFNULL(ub_djs.css, '') AS 'updatedby.dj.css',
IFNULL(ub_djs.djcolor, '') AS 'updatedby.dj.color',
IFNULL(ub_themes.id, 0) AS 'updatedby.dj.theme.id',
IFNULL(ub_themes.name, '') AS 'updatedby.dj.theme.name',
IFNULL(ub_themes.display_name, '') AS 'updatedby.dj.theme.displayname',
IFNULL(ub_themes.author, '') AS 'updatedby.dj.theme.author',
IFNULL(ow.id, 0) AS 'owner.id',
IFNULL(ow.user, '') AS 'owner.username',
IFNULL(ow.pass, '') AS 'owner.password',
IFNULL(ow.email, '') AS 'owner.email',
IFNULL(ow.ip, '') AS 'owner.ip',
IFNULL(ow.updated_at, TIMESTAMP('0000-00-00 00:00:00')) AS 'owner.updated_at',
IFNULL(ow.deleted_at, TIMESTAMP('0000-00-00 00:00:00')) AS 'owner.deleted_at',
IFNULL(ow.created_at, TIMESTAMP('0000-00-00 00:00:00')) AS 'owner.created_at',
(SELECT group_concat(permission) FROM permissions WHERE user_id=ow.id) AS 'owner.userpermissions',
IFNULL(ow_djs.id, 0) AS 'owner.dj.id',
IFNULL(ow_djs.regex, '') AS 'owner.dj.regex',
IFNULL(ow_djs.djname, '') AS 'owner.dj.name',
IFNULL(ow_djs.djtext, '') AS 'owner.dj.text',
IFNULL(ow_djs.djimage, '') AS 'owner.dj.image',
IFNULL(ow_djs.visible, 0) AS 'owner.dj.visible',
IFNULL(ow_djs.priority, 0) AS 'owner.dj.priority',
IFNULL(ow_djs.role, '') AS 'owner.dj.role',
IFNULL(ow_djs.css, '') AS 'owner.dj.css',
IFNULL(ow_djs.djcolor, '') AS 'owner.dj.color',
IFNULL(ow_themes.id, 0) AS 'owner.dj.theme.id',
IFNULL(ow_themes.name, 'default') AS 'owner.dj.theme.name',
IFNULL(ow_themes.display_name, 'default') AS 'owner.dj.theme.displayname',
IFNULL(ow_themes.author, 'unknown') AS 'owner.dj.theme.author'
FROM
schedule
RIGHT JOIN
(SELECT weekday, max(updated_at) AS updated_at FROM schedule GROUP BY weekday) AS s2 USING (weekday, updated_at)
JOIN
users AS ub ON schedule.updated_by = ub.id
LEFT JOIN
djs AS ub_djs ON ub.djid = ub_djs.id
LEFT JOIN
themes AS ub_themes ON ub_djs.theme_id = ub_themes.id
LEFT JOIN
users AS ow ON schedule.owner = ow.id
LEFT JOIN
djs AS ow_djs ON ow.djid = ow_djs.id
LEFT JOIN
themes AS ow_themes ON ow_djs.theme_id = ow_themes.id
ORDER BY
schedule.weekday;
`

func (ss ScheduleStorage) Latest() ([]radio.ScheduleEntry, error) {
const op errors.Op = "mariadb/ScheduleStorage.Latest"
handle, deferFn := ss.handle.span(op)
defer deferFn()

var query = `
SELECT
schedule.id AS id,
schedule.weekday AS weekday,
schedule.text AS text,
schedule.owner AS 'user.id',
schedule.updated_at AS updated_at,
schedule.notification AS notification
FROM
schedule
`

var schedule []radio.ScheduleEntry

err := sqlx.Select(handle, &schedule, query)
err := sqlx.Select(handle, &schedule, latestScheduleQuery)
if err != nil {
return nil, errors.E(op, err)
}

for i := range schedule {
if schedule[i].Owner == nil {
continue
}
if schedule[i].Owner.ID == 0 {
schedule[i].Owner = nil
}
}

return schedule, nil
}

func (ss ScheduleStorage) Update(entry radio.ScheduleEntry) error {
const op errors.Op = "mariadb/ScheduleStorage.Update"
handle, deferFn := ss.handle.span(op)
defer deferFn()

var query = `
INSERT INTO
schedule (
weekday,
text,
owner,
updated_by,
updated_at,
notification
) VALUES (
:weekday,
:text,
IF(:owner.id, :owner.id, NULL),
:updatedby.id,
CURRENT_TIMESTAMP(),
:notification
)
`

// entry.Owner can be nil but that breaks the named query above, so we insert
// a fake user with id 0 if it's nil
if entry.Owner == nil {
entry.Owner = &radio.User{}
}

_, err := sqlx.NamedExec(handle, query, entry)
if err != nil {
return errors.E(op, err)
}
return nil
}

Expand Down
77 changes: 77 additions & 0 deletions storage/test/schedule.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package storagetest

import (
"cmp"
"slices"
"time"

radio "github.com/R-a-dio/valkyrie"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func (suite *Suite) TestScheduleUpdate() {
t := suite.T()
ss := suite.Storage.Schedule(suite.ctx)

user := OneOff[radio.User](genUser())
user.ID = 0

user, err := suite.Storage.User(suite.ctx).UpdateUser(user)
require.NoError(t, err)

should := make(map[radio.ScheduleDay]radio.ScheduleEntry)

var entries []radio.ScheduleEntry
for i := radio.ScheduleDay(0); i <= radio.Sunday; i++ {
entries = append(entries, radio.ScheduleEntry{
Weekday: i,
Text: "This is " + i.String(),
UpdatedBy: user,
Notification: true,
})
}

updatedMonday := radio.ScheduleEntry{
Weekday: radio.Monday,
Text: "This is an updated Monday",
UpdatedBy: user,
Notification: false,
}

updatedFriday := radio.ScheduleEntry{
Weekday: radio.Friday,
Text: "And this is an updated Friday",
Owner: &user,
UpdatedBy: user,
Notification: true,
}
entries = append(entries, updatedMonday, updatedFriday)

for _, entry := range entries {
err := ss.Update(entry)
require.NoError(t, err)
time.Sleep(time.Second / 4)
should[entry.Weekday] = entry
}

latest, err := ss.Latest()
require.NoError(t, err)

// theres only 7 days, so we should only get 7 entries back
require.Equal(t, 7, len(latest), "latest should have 7 entries")
require.True(t, slices.IsSortedFunc(latest, func(a, b radio.ScheduleEntry) int {
return cmp.Compare(a.Weekday, b.Weekday)
}), "latest should be sorted by weekday")

for _, got := range latest {
s := should[got.Weekday]

assert.Equal(t, s.Text, got.Text)
assert.Equal(t, s.Notification, got.Notification)
assert.Equal(t, s.UpdatedBy.ID, got.UpdatedBy.ID)
if s.Owner != nil && assert.NotNil(t, got.Owner) {
assert.Equal(t, s.Owner.ID, got.Owner.ID)
}
}
}
6 changes: 5 additions & 1 deletion storage/test/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,15 @@ func LimitString(size int) func(string) bool {
}
}

func Positive(id radio.UserID) bool {
return id > 0
}

func genUser() gopter.Gen {
arbitraries := arbitrary.DefaultArbitraries()

return gen.Struct(reflect.TypeOf(radio.User{}), map[string]gopter.Gen{
"ID": arbitraries.GenForType(reflect.TypeOf(radio.UserID(0))),
"ID": arbitraries.GenForType(reflect.TypeOf(radio.UserID(0))).SuchThat(Positive),
"Username": gen.AlphaString().SuchThat(LimitString(50)),
"Password": gen.AlphaString().SuchThat(LimitString(120)),
"Email": gen.RegexMatch(`\w+@\w+\.\w{2,25}`).SuchThat(LimitString(255)),
Expand Down

0 comments on commit f954fbb

Please sign in to comment.