From 1ec3e8b06472994b43797d1061760c7152961b86 Mon Sep 17 00:00:00 2001 From: Henry Date: Tue, 23 Feb 2021 20:23:50 +0100 Subject: [PATCH] list, set and get pinned notices --- admindb/interface.go | 11 +- admindb/mockdb/fixed_pages.go | 183 ++- admindb/mockdb/pages.go | 39 +- admindb/sqlite/migrations/3-notices.sql | 46 +- admindb/sqlite/models/boil_table_names.go | 18 +- admindb/sqlite/models/notices.go | 251 ++-- admindb/sqlite/models/pinned_notices.go | 943 --------------- admindb/sqlite/models/pins.go | 1060 +++++++++++++++++ admindb/sqlite/notices.go | 129 +- admindb/sqlite/notices_test.go | 90 ++ admindb/types.go | 38 + cmd/server/main.go | 1 + web/handlers/http.go | 10 +- web/handlers/notices.go | 23 +- web/i18n/defaults/active.en.toml | 16 +- web/router/complete.go | 11 +- web/templates/admin/notice-edit.tmpl | 4 +- web/templates/notice/list.tmpl | 18 + .../{notice.tmpl => notice/show.tmpl} | 0 19 files changed, 1775 insertions(+), 1116 deletions(-) delete mode 100644 admindb/sqlite/models/pinned_notices.go create mode 100644 admindb/sqlite/models/pins.go create mode 100644 web/templates/notice/list.tmpl rename web/templates/{notice.tmpl => notice/show.tmpl} (100%) diff --git a/admindb/interface.go b/admindb/interface.go index c92e7ab..f574624 100644 --- a/admindb/interface.go +++ b/admindb/interface.go @@ -48,14 +48,19 @@ type AllowListService interface { type AliasService interface{} // PinnedNoticesService allows an admin to assign Notices to specific placeholder pages. -// TODO: better name then _fixed_ // like updates, privacy policy, code of conduct -// TODO: enum these type PinnedNoticesService interface { + // List returns a list of all the pinned notices with their corrosponding notices and languges + List(context.Context) (PinnedNotices, error) + // Set assigns a fixed page name to an page ID and a language to allow for multiple translated versions of the same page. - Set(name PinnedNoticeName, id int64, lang string) error + Set(ctx context.Context, name PinnedNoticeName, id int64) error + + // Get returns a single notice for a name and a language + Get(ctx context.Context, name PinnedNoticeName, language string) (*Notice, error) } +// NoticesService is the low level store to manage single notices type NoticesService interface { // GetByID returns the page for that ID or an error GetByID(context.Context, int64) (Notice, error) diff --git a/admindb/mockdb/fixed_pages.go b/admindb/mockdb/fixed_pages.go index db70afd..0d479ad 100644 --- a/admindb/mockdb/fixed_pages.go +++ b/admindb/mockdb/fixed_pages.go @@ -2,18 +2,47 @@ package mockdb import ( + "context" "sync" "github.com/ssb-ngi-pointer/go-ssb-room/admindb" ) type FakePinnedNoticesService struct { - SetStub func(admindb.PinnedNoticeName, int64, string) error + GetStub func(context.Context, admindb.PinnedNoticeName, string) (*admindb.Notice, error) + getMutex sync.RWMutex + getArgsForCall []struct { + arg1 context.Context + arg2 admindb.PinnedNoticeName + arg3 string + } + getReturns struct { + result1 *admindb.Notice + result2 error + } + getReturnsOnCall map[int]struct { + result1 *admindb.Notice + result2 error + } + ListStub func(context.Context) (admindb.PinnedNotices, error) + listMutex sync.RWMutex + listArgsForCall []struct { + arg1 context.Context + } + listReturns struct { + result1 admindb.PinnedNotices + result2 error + } + listReturnsOnCall map[int]struct { + result1 admindb.PinnedNotices + result2 error + } + SetStub func(context.Context, admindb.PinnedNoticeName, int64) error setMutex sync.RWMutex setArgsForCall []struct { - arg1 admindb.PinnedNoticeName - arg2 int64 - arg3 string + arg1 context.Context + arg2 admindb.PinnedNoticeName + arg3 int64 } setReturns struct { result1 error @@ -25,13 +54,143 @@ type FakePinnedNoticesService struct { invocationsMutex sync.RWMutex } -func (fake *FakePinnedNoticesService) Set(arg1 admindb.PinnedNoticeName, arg2 int64, arg3 string) error { +func (fake *FakePinnedNoticesService) Get(arg1 context.Context, arg2 admindb.PinnedNoticeName, arg3 string) (*admindb.Notice, error) { + fake.getMutex.Lock() + ret, specificReturn := fake.getReturnsOnCall[len(fake.getArgsForCall)] + fake.getArgsForCall = append(fake.getArgsForCall, struct { + arg1 context.Context + arg2 admindb.PinnedNoticeName + arg3 string + }{arg1, arg2, arg3}) + stub := fake.GetStub + fakeReturns := fake.getReturns + fake.recordInvocation("Get", []interface{}{arg1, arg2, arg3}) + fake.getMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakePinnedNoticesService) GetCallCount() int { + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + return len(fake.getArgsForCall) +} + +func (fake *FakePinnedNoticesService) GetCalls(stub func(context.Context, admindb.PinnedNoticeName, string) (*admindb.Notice, error)) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = stub +} + +func (fake *FakePinnedNoticesService) GetArgsForCall(i int) (context.Context, admindb.PinnedNoticeName, string) { + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + argsForCall := fake.getArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakePinnedNoticesService) GetReturns(result1 *admindb.Notice, result2 error) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = nil + fake.getReturns = struct { + result1 *admindb.Notice + result2 error + }{result1, result2} +} + +func (fake *FakePinnedNoticesService) GetReturnsOnCall(i int, result1 *admindb.Notice, result2 error) { + fake.getMutex.Lock() + defer fake.getMutex.Unlock() + fake.GetStub = nil + if fake.getReturnsOnCall == nil { + fake.getReturnsOnCall = make(map[int]struct { + result1 *admindb.Notice + result2 error + }) + } + fake.getReturnsOnCall[i] = struct { + result1 *admindb.Notice + result2 error + }{result1, result2} +} + +func (fake *FakePinnedNoticesService) List(arg1 context.Context) (admindb.PinnedNotices, error) { + fake.listMutex.Lock() + ret, specificReturn := fake.listReturnsOnCall[len(fake.listArgsForCall)] + fake.listArgsForCall = append(fake.listArgsForCall, struct { + arg1 context.Context + }{arg1}) + stub := fake.ListStub + fakeReturns := fake.listReturns + fake.recordInvocation("List", []interface{}{arg1}) + fake.listMutex.Unlock() + if stub != nil { + return stub(arg1) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakePinnedNoticesService) ListCallCount() int { + fake.listMutex.RLock() + defer fake.listMutex.RUnlock() + return len(fake.listArgsForCall) +} + +func (fake *FakePinnedNoticesService) ListCalls(stub func(context.Context) (admindb.PinnedNotices, error)) { + fake.listMutex.Lock() + defer fake.listMutex.Unlock() + fake.ListStub = stub +} + +func (fake *FakePinnedNoticesService) ListArgsForCall(i int) context.Context { + fake.listMutex.RLock() + defer fake.listMutex.RUnlock() + argsForCall := fake.listArgsForCall[i] + return argsForCall.arg1 +} + +func (fake *FakePinnedNoticesService) ListReturns(result1 admindb.PinnedNotices, result2 error) { + fake.listMutex.Lock() + defer fake.listMutex.Unlock() + fake.ListStub = nil + fake.listReturns = struct { + result1 admindb.PinnedNotices + result2 error + }{result1, result2} +} + +func (fake *FakePinnedNoticesService) ListReturnsOnCall(i int, result1 admindb.PinnedNotices, result2 error) { + fake.listMutex.Lock() + defer fake.listMutex.Unlock() + fake.ListStub = nil + if fake.listReturnsOnCall == nil { + fake.listReturnsOnCall = make(map[int]struct { + result1 admindb.PinnedNotices + result2 error + }) + } + fake.listReturnsOnCall[i] = struct { + result1 admindb.PinnedNotices + result2 error + }{result1, result2} +} + +func (fake *FakePinnedNoticesService) Set(arg1 context.Context, arg2 admindb.PinnedNoticeName, arg3 int64) error { fake.setMutex.Lock() ret, specificReturn := fake.setReturnsOnCall[len(fake.setArgsForCall)] fake.setArgsForCall = append(fake.setArgsForCall, struct { - arg1 admindb.PinnedNoticeName - arg2 int64 - arg3 string + arg1 context.Context + arg2 admindb.PinnedNoticeName + arg3 int64 }{arg1, arg2, arg3}) stub := fake.SetStub fakeReturns := fake.setReturns @@ -52,13 +211,13 @@ func (fake *FakePinnedNoticesService) SetCallCount() int { return len(fake.setArgsForCall) } -func (fake *FakePinnedNoticesService) SetCalls(stub func(admindb.PinnedNoticeName, int64, string) error) { +func (fake *FakePinnedNoticesService) SetCalls(stub func(context.Context, admindb.PinnedNoticeName, int64) error) { fake.setMutex.Lock() defer fake.setMutex.Unlock() fake.SetStub = stub } -func (fake *FakePinnedNoticesService) SetArgsForCall(i int) (admindb.PinnedNoticeName, int64, string) { +func (fake *FakePinnedNoticesService) SetArgsForCall(i int) (context.Context, admindb.PinnedNoticeName, int64) { fake.setMutex.RLock() defer fake.setMutex.RUnlock() argsForCall := fake.setArgsForCall[i] @@ -91,6 +250,10 @@ func (fake *FakePinnedNoticesService) SetReturnsOnCall(i int, result1 error) { func (fake *FakePinnedNoticesService) Invocations() map[string][][]interface{} { fake.invocationsMutex.RLock() defer fake.invocationsMutex.RUnlock() + fake.getMutex.RLock() + defer fake.getMutex.RUnlock() + fake.listMutex.RLock() + defer fake.listMutex.RUnlock() fake.setMutex.RLock() defer fake.setMutex.RUnlock() copiedInvocations := map[string][][]interface{}{} diff --git a/admindb/mockdb/pages.go b/admindb/mockdb/pages.go index 1bbd897..556a49e 100644 --- a/admindb/mockdb/pages.go +++ b/admindb/mockdb/pages.go @@ -35,19 +35,17 @@ type FakeNoticesService struct { removeIDReturnsOnCall map[int]struct { result1 error } - SaveStub func(context.Context, admindb.Notice) (int64, error) + SaveStub func(context.Context, *admindb.Notice) error saveMutex sync.RWMutex saveArgsForCall []struct { arg1 context.Context - arg2 admindb.Notice + arg2 *admindb.Notice } saveReturns struct { - result1 int64 - result2 error + result1 error } saveReturnsOnCall map[int]struct { - result1 int64 - result2 error + result1 error } invocations map[string][][]interface{} invocationsMutex sync.RWMutex @@ -180,12 +178,12 @@ func (fake *FakeNoticesService) RemoveIDReturnsOnCall(i int, result1 error) { }{result1} } -func (fake *FakeNoticesService) Save(arg1 context.Context, arg2 admindb.Notice) (int64, error) { +func (fake *FakeNoticesService) Save(arg1 context.Context, arg2 *admindb.Notice) error { fake.saveMutex.Lock() ret, specificReturn := fake.saveReturnsOnCall[len(fake.saveArgsForCall)] fake.saveArgsForCall = append(fake.saveArgsForCall, struct { arg1 context.Context - arg2 admindb.Notice + arg2 *admindb.Notice }{arg1, arg2}) stub := fake.SaveStub fakeReturns := fake.saveReturns @@ -195,9 +193,9 @@ func (fake *FakeNoticesService) Save(arg1 context.Context, arg2 admindb.Notice) return stub(arg1, arg2) } if specificReturn { - return ret.result1, ret.result2 + return ret.result1 } - return fakeReturns.result1, fakeReturns.result2 + return fakeReturns.result1 } func (fake *FakeNoticesService) SaveCallCount() int { @@ -206,43 +204,40 @@ func (fake *FakeNoticesService) SaveCallCount() int { return len(fake.saveArgsForCall) } -func (fake *FakeNoticesService) SaveCalls(stub func(context.Context, admindb.Notice) (int64, error)) { +func (fake *FakeNoticesService) SaveCalls(stub func(context.Context, *admindb.Notice) error) { fake.saveMutex.Lock() defer fake.saveMutex.Unlock() fake.SaveStub = stub } -func (fake *FakeNoticesService) SaveArgsForCall(i int) (context.Context, admindb.Notice) { +func (fake *FakeNoticesService) SaveArgsForCall(i int) (context.Context, *admindb.Notice) { fake.saveMutex.RLock() defer fake.saveMutex.RUnlock() argsForCall := fake.saveArgsForCall[i] return argsForCall.arg1, argsForCall.arg2 } -func (fake *FakeNoticesService) SaveReturns(result1 int64, result2 error) { +func (fake *FakeNoticesService) SaveReturns(result1 error) { fake.saveMutex.Lock() defer fake.saveMutex.Unlock() fake.SaveStub = nil fake.saveReturns = struct { - result1 int64 - result2 error - }{result1, result2} + result1 error + }{result1} } -func (fake *FakeNoticesService) SaveReturnsOnCall(i int, result1 int64, result2 error) { +func (fake *FakeNoticesService) SaveReturnsOnCall(i int, result1 error) { fake.saveMutex.Lock() defer fake.saveMutex.Unlock() fake.SaveStub = nil if fake.saveReturnsOnCall == nil { fake.saveReturnsOnCall = make(map[int]struct { - result1 int64 - result2 error + result1 error }) } fake.saveReturnsOnCall[i] = struct { - result1 int64 - result2 error - }{result1, result2} + result1 error + }{result1} } func (fake *FakeNoticesService) Invocations() map[string][][]interface{} { diff --git a/admindb/sqlite/migrations/3-notices.sql b/admindb/sqlite/migrations/3-notices.sql index f977b96..87e628b 100644 --- a/admindb/sqlite/migrations/3-notices.sql +++ b/admindb/sqlite/migrations/3-notices.sql @@ -1,4 +1,10 @@ -- +migrate Up +CREATE TABLE pins ( + id integer NOT NULL PRIMARY KEY, + name text NOT NULL UNIQUE +); + + CREATE TABLE notices ( id integer PRIMARY KEY AUTOINCREMENT NOT NULL, title text NOT NULL, @@ -6,17 +12,24 @@ CREATE TABLE notices ( language text NOT NULL ); -CREATE TABLE pinned_notices ( - name text NOT NULL, - notice_id integer NOT NULL UNIQUE, - language text NOT NULL, - - PRIMARY KEY (name, language), +-- n:m relation table +CREATE TABLE pin_notices ( + notice_id integer NOT NULL, + pin_id integer NOT NULL, - -- make sure the notices exist - FOREIGN KEY ( notice_id ) REFERENCES notices( "id" ) + PRIMARY KEY (notice_id, pin_id), + + FOREIGN KEY ( notice_id ) REFERENCES notices( "id" ), + FOREIGN KEY ( pin_id ) REFERENCES pins( "id" ) ); +-- TODO: find a better way to insert the defaults +INSERT INTO pins (name) VALUES +('NoticeDescription'), +('NoticeNews'), +('NoticeCodeOfConduct'), +('NoticePrivacyPolicy'); + INSERT INTO notices (title, content, language) VALUES ('Description', 'Basic description of this Room.', 'en-GB'), ('News', 'Some recent updates...', 'en-GB'), @@ -31,14 +44,15 @@ INSERT INTO notices (title, content, language) VALUES ('Datenschutzrichtlinien', 'Bitte aktualisieren', 'de-DE'), ('Beschreibung', 'Allgemeine beschreibung des Raumes.', 'de-DE'); -INSERT INTO pinned_notices (name, notice_id, language) VALUES -('NoticeDescription', 1, 'en-GB'), -('NoticeNews', 2, 'en-GB'), -('NoticeCodeOfConduct', 3, 'en-GB'), -('NoticePrivacyPolicy', 4, 'en-GB'), -('NoticePrivacyPolicy', 5, 'de-DE'), -('NoticeDescription', 6, 'de-DE'); +INSERT INTO pin_notices (notice_id, pin_id) VALUES +(1, 1), +(2, 2), +(3, 3), +(4, 4), +(5, 4), +(6, 1); -- +migrate Down DROP TABLE notices; -DROP TABLE pinned_notices; \ No newline at end of file +DROP TABLE pins; +DROP TABLE pin_notices; \ No newline at end of file diff --git a/admindb/sqlite/models/boil_table_names.go b/admindb/sqlite/models/boil_table_names.go index 838cb84..9e18c0c 100644 --- a/admindb/sqlite/models/boil_table_names.go +++ b/admindb/sqlite/models/boil_table_names.go @@ -4,13 +4,15 @@ package models var TableNames = struct { - AllowList string - AuthFallback string - Notices string - PinnedNotices string + AllowList string + AuthFallback string + Notices string + PinNotices string + Pins string }{ - AllowList: "allow_list", - AuthFallback: "auth_fallback", - Notices: "notices", - PinnedNotices: "pinned_notices", + AllowList: "allow_list", + AuthFallback: "auth_fallback", + Notices: "notices", + PinNotices: "pin_notices", + Pins: "pins", } diff --git a/admindb/sqlite/models/notices.go b/admindb/sqlite/models/notices.go index 0eb26ba..df37174 100644 --- a/admindb/sqlite/models/notices.go +++ b/admindb/sqlite/models/notices.go @@ -59,14 +59,14 @@ var NoticeWhere = struct { // NoticeRels is where relationship names are stored. var NoticeRels = struct { - PinnedNotice string + Pins string }{ - PinnedNotice: "PinnedNotice", + Pins: "Pins", } // noticeR is where relationships are stored. type noticeR struct { - PinnedNotice *PinnedNotice `boil:"PinnedNotice" json:"PinnedNotice" toml:"PinnedNotice" yaml:"PinnedNotice"` + Pins PinSlice `boil:"Pins" json:"Pins" toml:"Pins" yaml:"Pins"` } // NewStruct creates a new relationship struct @@ -359,23 +359,31 @@ func (q noticeQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (boo return count > 0, nil } -// PinnedNotice pointed to by the foreign key. -func (o *Notice) PinnedNotice(mods ...qm.QueryMod) pinnedNoticeQuery { - queryMods := []qm.QueryMod{ - qm.Where("\"notice_id\" = ?", o.ID), +// Pins retrieves all the pin's Pins with an executor. +func (o *Notice) Pins(mods ...qm.QueryMod) pinQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) } - queryMods = append(queryMods, mods...) + queryMods = append(queryMods, + qm.InnerJoin("\"pin_notices\" on \"pins\".\"id\" = \"pin_notices\".\"pin_id\""), + qm.Where("\"pin_notices\".\"notice_id\"=?", o.ID), + ) - query := PinnedNotices(queryMods...) - queries.SetFrom(query.Query, "\"pinned_notices\"") + query := Pins(queryMods...) + queries.SetFrom(query.Query, "\"pins\"") + + if len(queries.GetSelect(query.Query)) == 0 { + queries.SetSelect(query.Query, []string{"\"pins\".*"}) + } return query } -// LoadPinnedNotice allows an eager lookup of values, cached into the -// loaded structs of the objects. This is for a 1-1 relationship. -func (noticeL) LoadPinnedNotice(ctx context.Context, e boil.ContextExecutor, singular bool, maybeNotice interface{}, mods queries.Applicator) error { +// LoadPins allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (noticeL) LoadPins(ctx context.Context, e boil.ContextExecutor, singular bool, maybeNotice interface{}, mods queries.Applicator) error { var slice []*Notice var object *Notice @@ -413,8 +421,10 @@ func (noticeL) LoadPinnedNotice(ctx context.Context, e boil.ContextExecutor, sin } query := NewQuery( - qm.From(`pinned_notices`), - qm.WhereIn(`pinned_notices.notice_id in ?`, args...), + qm.Select("\"pins\".id, \"pins\".name, \"a\".\"notice_id\""), + qm.From("\"pins\""), + qm.InnerJoin("\"pin_notices\" as \"a\" on \"pins\".\"id\" = \"a\".\"pin_id\""), + qm.WhereIn("\"a\".\"notice_id\" in ?", args...), ) if mods != nil { mods.Apply(query) @@ -422,50 +432,62 @@ func (noticeL) LoadPinnedNotice(ctx context.Context, e boil.ContextExecutor, sin results, err := query.QueryContext(ctx, e) if err != nil { - return errors.Wrap(err, "failed to eager load PinnedNotice") + return errors.Wrap(err, "failed to eager load pins") } - var resultSlice []*PinnedNotice - if err = queries.Bind(results, &resultSlice); err != nil { - return errors.Wrap(err, "failed to bind eager loaded slice PinnedNotice") + var resultSlice []*Pin + + var localJoinCols []int64 + for results.Next() { + one := new(Pin) + var localJoinCol int64 + + err = results.Scan(&one.ID, &one.Name, &localJoinCol) + if err != nil { + return errors.Wrap(err, "failed to scan eager loaded results for pins") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "failed to plebian-bind eager loaded slice pins") + } + + resultSlice = append(resultSlice, one) + localJoinCols = append(localJoinCols, localJoinCol) } if err = results.Close(); err != nil { - return errors.Wrap(err, "failed to close results of eager load for pinned_notices") + return errors.Wrap(err, "failed to close results in eager load on pins") } if err = results.Err(); err != nil { - return errors.Wrap(err, "error occurred during iteration of eager loaded relations for pinned_notices") + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for pins") } - if len(noticeAfterSelectHooks) != 0 { + if len(pinAfterSelectHooks) != 0 { for _, obj := range resultSlice { if err := obj.doAfterSelectHooks(ctx, e); err != nil { return err } } } - - if len(resultSlice) == 0 { + if singular { + object.R.Pins = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = &pinR{} + } + foreign.R.Notices = append(foreign.R.Notices, object) + } return nil } - if singular { - foreign := resultSlice[0] - object.R.PinnedNotice = foreign - if foreign.R == nil { - foreign.R = &pinnedNoticeR{} - } - foreign.R.Notice = object - } - - for _, local := range slice { - for _, foreign := range resultSlice { - if local.ID == foreign.NoticeID { - local.R.PinnedNotice = foreign + for i, foreign := range resultSlice { + localJoinCol := localJoinCols[i] + for _, local := range slice { + if local.ID == localJoinCol { + local.R.Pins = append(local.R.Pins, foreign) if foreign.R == nil { - foreign.R = &pinnedNoticeR{} + foreign.R = &pinR{} } - foreign.R.Notice = local + foreign.R.Notices = append(foreign.R.Notices, local) break } } @@ -474,57 +496,146 @@ func (noticeL) LoadPinnedNotice(ctx context.Context, e boil.ContextExecutor, sin return nil } -// SetPinnedNotice of the notice to the related item. -// Sets o.R.PinnedNotice to related. -// Adds o to related.R.Notice. -func (o *Notice) SetPinnedNotice(ctx context.Context, exec boil.ContextExecutor, insert bool, related *PinnedNotice) error { +// AddPins adds the given related objects to the existing relationships +// of the notice, optionally inserting them as new records. +// Appends related to o.R.Pins. +// Sets related.R.Notices appropriately. +func (o *Notice) AddPins(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Pin) error { var err error - - if insert { - related.NoticeID = o.ID - - if err = related.Insert(ctx, exec, boil.Infer()); err != nil { - return errors.Wrap(err, "failed to insert into foreign table") + for _, rel := range related { + if insert { + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } } - } else { - updateQuery := fmt.Sprintf( - "UPDATE \"pinned_notices\" SET %s WHERE %s", - strmangle.SetParamNames("\"", "\"", 0, []string{"notice_id"}), - strmangle.WhereClause("\"", "\"", 0, pinnedNoticePrimaryKeyColumns), - ) - values := []interface{}{o.ID, related.Name, related.Language} + } + + for _, rel := range related { + query := "insert into \"pin_notices\" (\"notice_id\", \"pin_id\") values (?, ?)" + values := []interface{}{o.ID, rel.ID} if boil.IsDebug(ctx) { writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, query) fmt.Fprintln(writer, values) } - if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { - return errors.Wrap(err, "failed to update foreign table") + _, err = exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to insert into join table") } - - related.NoticeID = o.ID - } - if o.R == nil { o.R = ¬iceR{ - PinnedNotice: related, + Pins: related, } } else { - o.R.PinnedNotice = related + o.R.Pins = append(o.R.Pins, related...) } - if related.R == nil { - related.R = &pinnedNoticeR{ - Notice: o, + for _, rel := range related { + if rel.R == nil { + rel.R = &pinR{ + Notices: NoticeSlice{o}, + } + } else { + rel.R.Notices = append(rel.R.Notices, o) } - } else { - related.R.Notice = o } return nil } +// SetPins removes all previously related items of the +// notice replacing them completely with the passed +// in related items, optionally inserting them as new records. +// Sets o.R.Notices's Pins accordingly. +// Replaces o.R.Pins with related. +// Sets related.R.Notices's Pins accordingly. +func (o *Notice) SetPins(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Pin) error { + query := "delete from \"pin_notices\" where \"notice_id\" = ?" + values := []interface{}{o.ID} + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err := exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + + removePinsFromNoticesSlice(o, related) + if o.R != nil { + o.R.Pins = nil + } + return o.AddPins(ctx, exec, insert, related...) +} + +// RemovePins relationships from objects passed in. +// Removes related items from R.Pins (uses pointer comparison, removal does not keep order) +// Sets related.R.Notices. +func (o *Notice) RemovePins(ctx context.Context, exec boil.ContextExecutor, related ...*Pin) error { + var err error + query := fmt.Sprintf( + "delete from \"pin_notices\" where \"notice_id\" = ? and \"pin_id\" in (%s)", + strmangle.Placeholders(dialect.UseIndexPlaceholders, len(related), 2, 1), + ) + values := []interface{}{o.ID} + for _, rel := range related { + values = append(values, rel.ID) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err = exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + removePinsFromNoticesSlice(o, related) + if o.R == nil { + return nil + } + + for _, rel := range related { + for i, ri := range o.R.Pins { + if rel != ri { + continue + } + + ln := len(o.R.Pins) + if ln > 1 && i < ln-1 { + o.R.Pins[i] = o.R.Pins[ln-1] + } + o.R.Pins = o.R.Pins[:ln-1] + break + } + } + + return nil +} + +func removePinsFromNoticesSlice(o *Notice, related []*Pin) { + for _, rel := range related { + if rel.R == nil { + continue + } + for i, ri := range rel.R.Notices { + if o.ID != ri.ID { + continue + } + + ln := len(rel.R.Notices) + if ln > 1 && i < ln-1 { + rel.R.Notices[i] = rel.R.Notices[ln-1] + } + rel.R.Notices = rel.R.Notices[:ln-1] + break + } + } +} + // Notices retrieves all the records using an executor. func Notices(mods ...qm.QueryMod) noticeQuery { mods = append(mods, qm.From("\"notices\"")) diff --git a/admindb/sqlite/models/pinned_notices.go b/admindb/sqlite/models/pinned_notices.go deleted file mode 100644 index 38e46d0..0000000 --- a/admindb/sqlite/models/pinned_notices.go +++ /dev/null @@ -1,943 +0,0 @@ -// Code generated by SQLBoiler 4.4.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. -// This file is meant to be re-generated in place and/or deleted at any time. - -package models - -import ( - "context" - "database/sql" - "fmt" - "reflect" - "strings" - "sync" - "time" - - "github.com/friendsofgo/errors" - "github.com/volatiletech/sqlboiler/v4/boil" - "github.com/volatiletech/sqlboiler/v4/queries" - "github.com/volatiletech/sqlboiler/v4/queries/qm" - "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" - "github.com/volatiletech/strmangle" -) - -// PinnedNotice is an object representing the database table. -type PinnedNotice struct { - Name string `boil:"name" json:"name" toml:"name" yaml:"name"` - NoticeID int64 `boil:"notice_id" json:"notice_id" toml:"notice_id" yaml:"notice_id"` - Language string `boil:"language" json:"language" toml:"language" yaml:"language"` - - R *pinnedNoticeR `boil:"-" json:"-" toml:"-" yaml:"-"` - L pinnedNoticeL `boil:"-" json:"-" toml:"-" yaml:"-"` -} - -var PinnedNoticeColumns = struct { - Name string - NoticeID string - Language string -}{ - Name: "name", - NoticeID: "notice_id", - Language: "language", -} - -// Generated where - -var PinnedNoticeWhere = struct { - Name whereHelperstring - NoticeID whereHelperint64 - Language whereHelperstring -}{ - Name: whereHelperstring{field: "\"pinned_notices\".\"name\""}, - NoticeID: whereHelperint64{field: "\"pinned_notices\".\"notice_id\""}, - Language: whereHelperstring{field: "\"pinned_notices\".\"language\""}, -} - -// PinnedNoticeRels is where relationship names are stored. -var PinnedNoticeRels = struct { - Notice string -}{ - Notice: "Notice", -} - -// pinnedNoticeR is where relationships are stored. -type pinnedNoticeR struct { - Notice *Notice `boil:"Notice" json:"Notice" toml:"Notice" yaml:"Notice"` -} - -// NewStruct creates a new relationship struct -func (*pinnedNoticeR) NewStruct() *pinnedNoticeR { - return &pinnedNoticeR{} -} - -// pinnedNoticeL is where Load methods for each relationship are stored. -type pinnedNoticeL struct{} - -var ( - pinnedNoticeAllColumns = []string{"name", "notice_id", "language"} - pinnedNoticeColumnsWithoutDefault = []string{"name", "notice_id", "language"} - pinnedNoticeColumnsWithDefault = []string{} - pinnedNoticePrimaryKeyColumns = []string{"name", "language"} -) - -type ( - // PinnedNoticeSlice is an alias for a slice of pointers to PinnedNotice. - // This should generally be used opposed to []PinnedNotice. - PinnedNoticeSlice []*PinnedNotice - // PinnedNoticeHook is the signature for custom PinnedNotice hook methods - PinnedNoticeHook func(context.Context, boil.ContextExecutor, *PinnedNotice) error - - pinnedNoticeQuery struct { - *queries.Query - } -) - -// Cache for insert, update and upsert -var ( - pinnedNoticeType = reflect.TypeOf(&PinnedNotice{}) - pinnedNoticeMapping = queries.MakeStructMapping(pinnedNoticeType) - pinnedNoticePrimaryKeyMapping, _ = queries.BindMapping(pinnedNoticeType, pinnedNoticeMapping, pinnedNoticePrimaryKeyColumns) - pinnedNoticeInsertCacheMut sync.RWMutex - pinnedNoticeInsertCache = make(map[string]insertCache) - pinnedNoticeUpdateCacheMut sync.RWMutex - pinnedNoticeUpdateCache = make(map[string]updateCache) - pinnedNoticeUpsertCacheMut sync.RWMutex - pinnedNoticeUpsertCache = make(map[string]insertCache) -) - -var ( - // Force time package dependency for automated UpdatedAt/CreatedAt. - _ = time.Second - // Force qmhelper dependency for where clause generation (which doesn't - // always happen) - _ = qmhelper.Where -) - -var pinnedNoticeBeforeInsertHooks []PinnedNoticeHook -var pinnedNoticeBeforeUpdateHooks []PinnedNoticeHook -var pinnedNoticeBeforeDeleteHooks []PinnedNoticeHook -var pinnedNoticeBeforeUpsertHooks []PinnedNoticeHook - -var pinnedNoticeAfterInsertHooks []PinnedNoticeHook -var pinnedNoticeAfterSelectHooks []PinnedNoticeHook -var pinnedNoticeAfterUpdateHooks []PinnedNoticeHook -var pinnedNoticeAfterDeleteHooks []PinnedNoticeHook -var pinnedNoticeAfterUpsertHooks []PinnedNoticeHook - -// doBeforeInsertHooks executes all "before insert" hooks. -func (o *PinnedNotice) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeBeforeInsertHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// doBeforeUpdateHooks executes all "before Update" hooks. -func (o *PinnedNotice) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeBeforeUpdateHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// doBeforeDeleteHooks executes all "before Delete" hooks. -func (o *PinnedNotice) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeBeforeDeleteHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// doBeforeUpsertHooks executes all "before Upsert" hooks. -func (o *PinnedNotice) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeBeforeUpsertHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// doAfterInsertHooks executes all "after Insert" hooks. -func (o *PinnedNotice) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeAfterInsertHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// doAfterSelectHooks executes all "after Select" hooks. -func (o *PinnedNotice) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeAfterSelectHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// doAfterUpdateHooks executes all "after Update" hooks. -func (o *PinnedNotice) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeAfterUpdateHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// doAfterDeleteHooks executes all "after Delete" hooks. -func (o *PinnedNotice) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeAfterDeleteHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// doAfterUpsertHooks executes all "after Upsert" hooks. -func (o *PinnedNotice) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { - if boil.HooksAreSkipped(ctx) { - return nil - } - - for _, hook := range pinnedNoticeAfterUpsertHooks { - if err := hook(ctx, exec, o); err != nil { - return err - } - } - - return nil -} - -// AddPinnedNoticeHook registers your hook function for all future operations. -func AddPinnedNoticeHook(hookPoint boil.HookPoint, pinnedNoticeHook PinnedNoticeHook) { - switch hookPoint { - case boil.BeforeInsertHook: - pinnedNoticeBeforeInsertHooks = append(pinnedNoticeBeforeInsertHooks, pinnedNoticeHook) - case boil.BeforeUpdateHook: - pinnedNoticeBeforeUpdateHooks = append(pinnedNoticeBeforeUpdateHooks, pinnedNoticeHook) - case boil.BeforeDeleteHook: - pinnedNoticeBeforeDeleteHooks = append(pinnedNoticeBeforeDeleteHooks, pinnedNoticeHook) - case boil.BeforeUpsertHook: - pinnedNoticeBeforeUpsertHooks = append(pinnedNoticeBeforeUpsertHooks, pinnedNoticeHook) - case boil.AfterInsertHook: - pinnedNoticeAfterInsertHooks = append(pinnedNoticeAfterInsertHooks, pinnedNoticeHook) - case boil.AfterSelectHook: - pinnedNoticeAfterSelectHooks = append(pinnedNoticeAfterSelectHooks, pinnedNoticeHook) - case boil.AfterUpdateHook: - pinnedNoticeAfterUpdateHooks = append(pinnedNoticeAfterUpdateHooks, pinnedNoticeHook) - case boil.AfterDeleteHook: - pinnedNoticeAfterDeleteHooks = append(pinnedNoticeAfterDeleteHooks, pinnedNoticeHook) - case boil.AfterUpsertHook: - pinnedNoticeAfterUpsertHooks = append(pinnedNoticeAfterUpsertHooks, pinnedNoticeHook) - } -} - -// One returns a single pinnedNotice record from the query. -func (q pinnedNoticeQuery) One(ctx context.Context, exec boil.ContextExecutor) (*PinnedNotice, error) { - o := &PinnedNotice{} - - queries.SetLimit(q.Query, 1) - - err := q.Bind(ctx, exec, o) - if err != nil { - if errors.Cause(err) == sql.ErrNoRows { - return nil, sql.ErrNoRows - } - return nil, errors.Wrap(err, "models: failed to execute a one query for pinned_notices") - } - - if err := o.doAfterSelectHooks(ctx, exec); err != nil { - return o, err - } - - return o, nil -} - -// All returns all PinnedNotice records from the query. -func (q pinnedNoticeQuery) All(ctx context.Context, exec boil.ContextExecutor) (PinnedNoticeSlice, error) { - var o []*PinnedNotice - - err := q.Bind(ctx, exec, &o) - if err != nil { - return nil, errors.Wrap(err, "models: failed to assign all query results to PinnedNotice slice") - } - - if len(pinnedNoticeAfterSelectHooks) != 0 { - for _, obj := range o { - if err := obj.doAfterSelectHooks(ctx, exec); err != nil { - return o, err - } - } - } - - return o, nil -} - -// Count returns the count of all PinnedNotice records in the query. -func (q pinnedNoticeQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { - var count int64 - - queries.SetSelect(q.Query, nil) - queries.SetCount(q.Query) - - err := q.Query.QueryRowContext(ctx, exec).Scan(&count) - if err != nil { - return 0, errors.Wrap(err, "models: failed to count pinned_notices rows") - } - - return count, nil -} - -// Exists checks if the row exists in the table. -func (q pinnedNoticeQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { - var count int64 - - queries.SetSelect(q.Query, nil) - queries.SetCount(q.Query) - queries.SetLimit(q.Query, 1) - - err := q.Query.QueryRowContext(ctx, exec).Scan(&count) - if err != nil { - return false, errors.Wrap(err, "models: failed to check if pinned_notices exists") - } - - return count > 0, nil -} - -// Notice pointed to by the foreign key. -func (o *PinnedNotice) Notice(mods ...qm.QueryMod) noticeQuery { - queryMods := []qm.QueryMod{ - qm.Where("\"id\" = ?", o.NoticeID), - } - - queryMods = append(queryMods, mods...) - - query := Notices(queryMods...) - queries.SetFrom(query.Query, "\"notices\"") - - return query -} - -// LoadNotice allows an eager lookup of values, cached into the -// loaded structs of the objects. This is for an N-1 relationship. -func (pinnedNoticeL) LoadNotice(ctx context.Context, e boil.ContextExecutor, singular bool, maybePinnedNotice interface{}, mods queries.Applicator) error { - var slice []*PinnedNotice - var object *PinnedNotice - - if singular { - object = maybePinnedNotice.(*PinnedNotice) - } else { - slice = *maybePinnedNotice.(*[]*PinnedNotice) - } - - args := make([]interface{}, 0, 1) - if singular { - if object.R == nil { - object.R = &pinnedNoticeR{} - } - args = append(args, object.NoticeID) - - } else { - Outer: - for _, obj := range slice { - if obj.R == nil { - obj.R = &pinnedNoticeR{} - } - - for _, a := range args { - if a == obj.NoticeID { - continue Outer - } - } - - args = append(args, obj.NoticeID) - - } - } - - if len(args) == 0 { - return nil - } - - query := NewQuery( - qm.From(`notices`), - qm.WhereIn(`notices.id in ?`, args...), - ) - if mods != nil { - mods.Apply(query) - } - - results, err := query.QueryContext(ctx, e) - if err != nil { - return errors.Wrap(err, "failed to eager load Notice") - } - - var resultSlice []*Notice - if err = queries.Bind(results, &resultSlice); err != nil { - return errors.Wrap(err, "failed to bind eager loaded slice Notice") - } - - if err = results.Close(); err != nil { - return errors.Wrap(err, "failed to close results of eager load for notices") - } - if err = results.Err(); err != nil { - return errors.Wrap(err, "error occurred during iteration of eager loaded relations for notices") - } - - if len(pinnedNoticeAfterSelectHooks) != 0 { - for _, obj := range resultSlice { - if err := obj.doAfterSelectHooks(ctx, e); err != nil { - return err - } - } - } - - if len(resultSlice) == 0 { - return nil - } - - if singular { - foreign := resultSlice[0] - object.R.Notice = foreign - if foreign.R == nil { - foreign.R = ¬iceR{} - } - foreign.R.PinnedNotice = object - return nil - } - - for _, local := range slice { - for _, foreign := range resultSlice { - if local.NoticeID == foreign.ID { - local.R.Notice = foreign - if foreign.R == nil { - foreign.R = ¬iceR{} - } - foreign.R.PinnedNotice = local - break - } - } - } - - return nil -} - -// SetNotice of the pinnedNotice to the related item. -// Sets o.R.Notice to related. -// Adds o to related.R.PinnedNotice. -func (o *PinnedNotice) SetNotice(ctx context.Context, exec boil.ContextExecutor, insert bool, related *Notice) error { - var err error - if insert { - if err = related.Insert(ctx, exec, boil.Infer()); err != nil { - return errors.Wrap(err, "failed to insert into foreign table") - } - } - - updateQuery := fmt.Sprintf( - "UPDATE \"pinned_notices\" SET %s WHERE %s", - strmangle.SetParamNames("\"", "\"", 0, []string{"notice_id"}), - strmangle.WhereClause("\"", "\"", 0, pinnedNoticePrimaryKeyColumns), - ) - values := []interface{}{related.ID, o.Name, o.Language} - - if boil.IsDebug(ctx) { - writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, updateQuery) - fmt.Fprintln(writer, values) - } - if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { - return errors.Wrap(err, "failed to update local table") - } - - o.NoticeID = related.ID - if o.R == nil { - o.R = &pinnedNoticeR{ - Notice: related, - } - } else { - o.R.Notice = related - } - - if related.R == nil { - related.R = ¬iceR{ - PinnedNotice: o, - } - } else { - related.R.PinnedNotice = o - } - - return nil -} - -// PinnedNotices retrieves all the records using an executor. -func PinnedNotices(mods ...qm.QueryMod) pinnedNoticeQuery { - mods = append(mods, qm.From("\"pinned_notices\"")) - return pinnedNoticeQuery{NewQuery(mods...)} -} - -// FindPinnedNotice retrieves a single record by ID with an executor. -// If selectCols is empty Find will return all columns. -func FindPinnedNotice(ctx context.Context, exec boil.ContextExecutor, name string, language string, selectCols ...string) (*PinnedNotice, error) { - pinnedNoticeObj := &PinnedNotice{} - - sel := "*" - if len(selectCols) > 0 { - sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") - } - query := fmt.Sprintf( - "select %s from \"pinned_notices\" where \"name\"=? AND \"language\"=?", sel, - ) - - q := queries.Raw(query, name, language) - - err := q.Bind(ctx, exec, pinnedNoticeObj) - if err != nil { - if errors.Cause(err) == sql.ErrNoRows { - return nil, sql.ErrNoRows - } - return nil, errors.Wrap(err, "models: unable to select from pinned_notices") - } - - return pinnedNoticeObj, nil -} - -// Insert a single record using an executor. -// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. -func (o *PinnedNotice) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { - if o == nil { - return errors.New("models: no pinned_notices provided for insertion") - } - - var err error - - if err := o.doBeforeInsertHooks(ctx, exec); err != nil { - return err - } - - nzDefaults := queries.NonZeroDefaultSet(pinnedNoticeColumnsWithDefault, o) - - key := makeCacheKey(columns, nzDefaults) - pinnedNoticeInsertCacheMut.RLock() - cache, cached := pinnedNoticeInsertCache[key] - pinnedNoticeInsertCacheMut.RUnlock() - - if !cached { - wl, returnColumns := columns.InsertColumnSet( - pinnedNoticeAllColumns, - pinnedNoticeColumnsWithDefault, - pinnedNoticeColumnsWithoutDefault, - nzDefaults, - ) - - cache.valueMapping, err = queries.BindMapping(pinnedNoticeType, pinnedNoticeMapping, wl) - if err != nil { - return err - } - cache.retMapping, err = queries.BindMapping(pinnedNoticeType, pinnedNoticeMapping, returnColumns) - if err != nil { - return err - } - if len(wl) != 0 { - cache.query = fmt.Sprintf("INSERT INTO \"pinned_notices\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) - } else { - cache.query = "INSERT INTO \"pinned_notices\" %sDEFAULT VALUES%s" - } - - var queryOutput, queryReturning string - - if len(cache.retMapping) != 0 { - cache.retQuery = fmt.Sprintf("SELECT \"%s\" FROM \"pinned_notices\" WHERE %s", strings.Join(returnColumns, "\",\""), strmangle.WhereClause("\"", "\"", 0, pinnedNoticePrimaryKeyColumns)) - } - - cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) - } - - value := reflect.Indirect(reflect.ValueOf(o)) - vals := queries.ValuesFromMapping(value, cache.valueMapping) - - if boil.IsDebug(ctx) { - writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, cache.query) - fmt.Fprintln(writer, vals) - } - _, err = exec.ExecContext(ctx, cache.query, vals...) - - if err != nil { - return errors.Wrap(err, "models: unable to insert into pinned_notices") - } - - var identifierCols []interface{} - - if len(cache.retMapping) == 0 { - goto CacheNoHooks - } - - identifierCols = []interface{}{ - o.Name, - o.Language, - } - - if boil.IsDebug(ctx) { - writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, cache.retQuery) - fmt.Fprintln(writer, identifierCols...) - } - err = exec.QueryRowContext(ctx, cache.retQuery, identifierCols...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) - if err != nil { - return errors.Wrap(err, "models: unable to populate default values for pinned_notices") - } - -CacheNoHooks: - if !cached { - pinnedNoticeInsertCacheMut.Lock() - pinnedNoticeInsertCache[key] = cache - pinnedNoticeInsertCacheMut.Unlock() - } - - return o.doAfterInsertHooks(ctx, exec) -} - -// Update uses an executor to update the PinnedNotice. -// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. -// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. -func (o *PinnedNotice) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { - var err error - if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { - return 0, err - } - key := makeCacheKey(columns, nil) - pinnedNoticeUpdateCacheMut.RLock() - cache, cached := pinnedNoticeUpdateCache[key] - pinnedNoticeUpdateCacheMut.RUnlock() - - if !cached { - wl := columns.UpdateColumnSet( - pinnedNoticeAllColumns, - pinnedNoticePrimaryKeyColumns, - ) - - if !columns.IsWhitelist() { - wl = strmangle.SetComplement(wl, []string{"created_at"}) - } - if len(wl) == 0 { - return 0, errors.New("models: unable to update pinned_notices, could not build whitelist") - } - - cache.query = fmt.Sprintf("UPDATE \"pinned_notices\" SET %s WHERE %s", - strmangle.SetParamNames("\"", "\"", 0, wl), - strmangle.WhereClause("\"", "\"", 0, pinnedNoticePrimaryKeyColumns), - ) - cache.valueMapping, err = queries.BindMapping(pinnedNoticeType, pinnedNoticeMapping, append(wl, pinnedNoticePrimaryKeyColumns...)) - if err != nil { - return 0, err - } - } - - values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) - - if boil.IsDebug(ctx) { - writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, cache.query) - fmt.Fprintln(writer, values) - } - var result sql.Result - result, err = exec.ExecContext(ctx, cache.query, values...) - if err != nil { - return 0, errors.Wrap(err, "models: unable to update pinned_notices row") - } - - rowsAff, err := result.RowsAffected() - if err != nil { - return 0, errors.Wrap(err, "models: failed to get rows affected by update for pinned_notices") - } - - if !cached { - pinnedNoticeUpdateCacheMut.Lock() - pinnedNoticeUpdateCache[key] = cache - pinnedNoticeUpdateCacheMut.Unlock() - } - - return rowsAff, o.doAfterUpdateHooks(ctx, exec) -} - -// UpdateAll updates all rows with the specified column values. -func (q pinnedNoticeQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { - queries.SetUpdate(q.Query, cols) - - result, err := q.Query.ExecContext(ctx, exec) - if err != nil { - return 0, errors.Wrap(err, "models: unable to update all for pinned_notices") - } - - rowsAff, err := result.RowsAffected() - if err != nil { - return 0, errors.Wrap(err, "models: unable to retrieve rows affected for pinned_notices") - } - - return rowsAff, nil -} - -// UpdateAll updates all rows with the specified column values, using an executor. -func (o PinnedNoticeSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { - ln := int64(len(o)) - if ln == 0 { - return 0, nil - } - - if len(cols) == 0 { - return 0, errors.New("models: update all requires at least one column argument") - } - - colNames := make([]string, len(cols)) - args := make([]interface{}, len(cols)) - - i := 0 - for name, value := range cols { - colNames[i] = name - args[i] = value - i++ - } - - // Append all of the primary key values for each column - for _, obj := range o { - pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), pinnedNoticePrimaryKeyMapping) - args = append(args, pkeyArgs...) - } - - sql := fmt.Sprintf("UPDATE \"pinned_notices\" SET %s WHERE %s", - strmangle.SetParamNames("\"", "\"", 0, colNames), - strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, pinnedNoticePrimaryKeyColumns, len(o))) - - if boil.IsDebug(ctx) { - writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, sql) - fmt.Fprintln(writer, args...) - } - result, err := exec.ExecContext(ctx, sql, args...) - if err != nil { - return 0, errors.Wrap(err, "models: unable to update all in pinnedNotice slice") - } - - rowsAff, err := result.RowsAffected() - if err != nil { - return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all pinnedNotice") - } - return rowsAff, nil -} - -// Delete deletes a single PinnedNotice record with an executor. -// Delete will match against the primary key column to find the record to delete. -func (o *PinnedNotice) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { - if o == nil { - return 0, errors.New("models: no PinnedNotice provided for delete") - } - - if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { - return 0, err - } - - args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), pinnedNoticePrimaryKeyMapping) - sql := "DELETE FROM \"pinned_notices\" WHERE \"name\"=? AND \"language\"=?" - - if boil.IsDebug(ctx) { - writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, sql) - fmt.Fprintln(writer, args...) - } - result, err := exec.ExecContext(ctx, sql, args...) - if err != nil { - return 0, errors.Wrap(err, "models: unable to delete from pinned_notices") - } - - rowsAff, err := result.RowsAffected() - if err != nil { - return 0, errors.Wrap(err, "models: failed to get rows affected by delete for pinned_notices") - } - - if err := o.doAfterDeleteHooks(ctx, exec); err != nil { - return 0, err - } - - return rowsAff, nil -} - -// DeleteAll deletes all matching rows. -func (q pinnedNoticeQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { - if q.Query == nil { - return 0, errors.New("models: no pinnedNoticeQuery provided for delete all") - } - - queries.SetDelete(q.Query) - - result, err := q.Query.ExecContext(ctx, exec) - if err != nil { - return 0, errors.Wrap(err, "models: unable to delete all from pinned_notices") - } - - rowsAff, err := result.RowsAffected() - if err != nil { - return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for pinned_notices") - } - - return rowsAff, nil -} - -// DeleteAll deletes all rows in the slice, using an executor. -func (o PinnedNoticeSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { - if len(o) == 0 { - return 0, nil - } - - if len(pinnedNoticeBeforeDeleteHooks) != 0 { - for _, obj := range o { - if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { - return 0, err - } - } - } - - var args []interface{} - for _, obj := range o { - pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), pinnedNoticePrimaryKeyMapping) - args = append(args, pkeyArgs...) - } - - sql := "DELETE FROM \"pinned_notices\" WHERE " + - strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, pinnedNoticePrimaryKeyColumns, len(o)) - - if boil.IsDebug(ctx) { - writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, sql) - fmt.Fprintln(writer, args) - } - result, err := exec.ExecContext(ctx, sql, args...) - if err != nil { - return 0, errors.Wrap(err, "models: unable to delete all from pinnedNotice slice") - } - - rowsAff, err := result.RowsAffected() - if err != nil { - return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for pinned_notices") - } - - if len(pinnedNoticeAfterDeleteHooks) != 0 { - for _, obj := range o { - if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { - return 0, err - } - } - } - - return rowsAff, nil -} - -// Reload refetches the object from the database -// using the primary keys with an executor. -func (o *PinnedNotice) Reload(ctx context.Context, exec boil.ContextExecutor) error { - ret, err := FindPinnedNotice(ctx, exec, o.Name, o.Language) - if err != nil { - return err - } - - *o = *ret - return nil -} - -// ReloadAll refetches every row with matching primary key column values -// and overwrites the original object slice with the newly updated slice. -func (o *PinnedNoticeSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { - if o == nil || len(*o) == 0 { - return nil - } - - slice := PinnedNoticeSlice{} - var args []interface{} - for _, obj := range *o { - pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), pinnedNoticePrimaryKeyMapping) - args = append(args, pkeyArgs...) - } - - sql := "SELECT \"pinned_notices\".* FROM \"pinned_notices\" WHERE " + - strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, pinnedNoticePrimaryKeyColumns, len(*o)) - - q := queries.Raw(sql, args...) - - err := q.Bind(ctx, exec, &slice) - if err != nil { - return errors.Wrap(err, "models: unable to reload all in PinnedNoticeSlice") - } - - *o = slice - - return nil -} - -// PinnedNoticeExists checks if the PinnedNotice row exists. -func PinnedNoticeExists(ctx context.Context, exec boil.ContextExecutor, name string, language string) (bool, error) { - var exists bool - sql := "select exists(select 1 from \"pinned_notices\" where \"name\"=? AND \"language\"=? limit 1)" - - if boil.IsDebug(ctx) { - writer := boil.DebugWriterFrom(ctx) - fmt.Fprintln(writer, sql) - fmt.Fprintln(writer, name, language) - } - row := exec.QueryRowContext(ctx, sql, name, language) - - err := row.Scan(&exists) - if err != nil { - return false, errors.Wrap(err, "models: unable to check if pinned_notices exists") - } - - return exists, nil -} diff --git a/admindb/sqlite/models/pins.go b/admindb/sqlite/models/pins.go new file mode 100644 index 0000000..7c29102 --- /dev/null +++ b/admindb/sqlite/models/pins.go @@ -0,0 +1,1060 @@ +// Code generated by SQLBoiler 4.4.0 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// Pin is an object representing the database table. +type Pin struct { + ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"` + Name string `boil:"name" json:"name" toml:"name" yaml:"name"` + + R *pinR `boil:"-" json:"-" toml:"-" yaml:"-"` + L pinL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var PinColumns = struct { + ID string + Name string +}{ + ID: "id", + Name: "name", +} + +// Generated where + +var PinWhere = struct { + ID whereHelperint64 + Name whereHelperstring +}{ + ID: whereHelperint64{field: "\"pins\".\"id\""}, + Name: whereHelperstring{field: "\"pins\".\"name\""}, +} + +// PinRels is where relationship names are stored. +var PinRels = struct { + Notices string +}{ + Notices: "Notices", +} + +// pinR is where relationships are stored. +type pinR struct { + Notices NoticeSlice `boil:"Notices" json:"Notices" toml:"Notices" yaml:"Notices"` +} + +// NewStruct creates a new relationship struct +func (*pinR) NewStruct() *pinR { + return &pinR{} +} + +// pinL is where Load methods for each relationship are stored. +type pinL struct{} + +var ( + pinAllColumns = []string{"id", "name"} + pinColumnsWithoutDefault = []string{"name"} + pinColumnsWithDefault = []string{"id"} + pinPrimaryKeyColumns = []string{"id"} +) + +type ( + // PinSlice is an alias for a slice of pointers to Pin. + // This should generally be used opposed to []Pin. + PinSlice []*Pin + // PinHook is the signature for custom Pin hook methods + PinHook func(context.Context, boil.ContextExecutor, *Pin) error + + pinQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + pinType = reflect.TypeOf(&Pin{}) + pinMapping = queries.MakeStructMapping(pinType) + pinPrimaryKeyMapping, _ = queries.BindMapping(pinType, pinMapping, pinPrimaryKeyColumns) + pinInsertCacheMut sync.RWMutex + pinInsertCache = make(map[string]insertCache) + pinUpdateCacheMut sync.RWMutex + pinUpdateCache = make(map[string]updateCache) + pinUpsertCacheMut sync.RWMutex + pinUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var pinBeforeInsertHooks []PinHook +var pinBeforeUpdateHooks []PinHook +var pinBeforeDeleteHooks []PinHook +var pinBeforeUpsertHooks []PinHook + +var pinAfterInsertHooks []PinHook +var pinAfterSelectHooks []PinHook +var pinAfterUpdateHooks []PinHook +var pinAfterDeleteHooks []PinHook +var pinAfterUpsertHooks []PinHook + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *Pin) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *Pin) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *Pin) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *Pin) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *Pin) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *Pin) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *Pin) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *Pin) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *Pin) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range pinAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddPinHook registers your hook function for all future operations. +func AddPinHook(hookPoint boil.HookPoint, pinHook PinHook) { + switch hookPoint { + case boil.BeforeInsertHook: + pinBeforeInsertHooks = append(pinBeforeInsertHooks, pinHook) + case boil.BeforeUpdateHook: + pinBeforeUpdateHooks = append(pinBeforeUpdateHooks, pinHook) + case boil.BeforeDeleteHook: + pinBeforeDeleteHooks = append(pinBeforeDeleteHooks, pinHook) + case boil.BeforeUpsertHook: + pinBeforeUpsertHooks = append(pinBeforeUpsertHooks, pinHook) + case boil.AfterInsertHook: + pinAfterInsertHooks = append(pinAfterInsertHooks, pinHook) + case boil.AfterSelectHook: + pinAfterSelectHooks = append(pinAfterSelectHooks, pinHook) + case boil.AfterUpdateHook: + pinAfterUpdateHooks = append(pinAfterUpdateHooks, pinHook) + case boil.AfterDeleteHook: + pinAfterDeleteHooks = append(pinAfterDeleteHooks, pinHook) + case boil.AfterUpsertHook: + pinAfterUpsertHooks = append(pinAfterUpsertHooks, pinHook) + } +} + +// One returns a single pin record from the query. +func (q pinQuery) One(ctx context.Context, exec boil.ContextExecutor) (*Pin, error) { + o := &Pin{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for pins") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all Pin records from the query. +func (q pinQuery) All(ctx context.Context, exec boil.ContextExecutor) (PinSlice, error) { + var o []*Pin + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to Pin slice") + } + + if len(pinAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all Pin records in the query. +func (q pinQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count pins rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q pinQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if pins exists") + } + + return count > 0, nil +} + +// Notices retrieves all the notice's Notices with an executor. +func (o *Pin) Notices(mods ...qm.QueryMod) noticeQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.InnerJoin("\"pin_notices\" on \"notices\".\"id\" = \"pin_notices\".\"notice_id\""), + qm.Where("\"pin_notices\".\"pin_id\"=?", o.ID), + ) + + query := Notices(queryMods...) + queries.SetFrom(query.Query, "\"notices\"") + + if len(queries.GetSelect(query.Query)) == 0 { + queries.SetSelect(query.Query, []string{"\"notices\".*"}) + } + + return query +} + +// LoadNotices allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (pinL) LoadNotices(ctx context.Context, e boil.ContextExecutor, singular bool, maybePin interface{}, mods queries.Applicator) error { + var slice []*Pin + var object *Pin + + if singular { + object = maybePin.(*Pin) + } else { + slice = *maybePin.(*[]*Pin) + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = &pinR{} + } + args = append(args, object.ID) + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = &pinR{} + } + + for _, a := range args { + if a == obj.ID { + continue Outer + } + } + + args = append(args, obj.ID) + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.Select("\"notices\".id, \"notices\".title, \"notices\".content, \"notices\".language, \"a\".\"pin_id\""), + qm.From("\"notices\""), + qm.InnerJoin("\"pin_notices\" as \"a\" on \"notices\".\"id\" = \"a\".\"notice_id\""), + qm.WhereIn("\"a\".\"pin_id\" in ?", args...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load notices") + } + + var resultSlice []*Notice + + var localJoinCols []int64 + for results.Next() { + one := new(Notice) + var localJoinCol int64 + + err = results.Scan(&one.ID, &one.Title, &one.Content, &one.Language, &localJoinCol) + if err != nil { + return errors.Wrap(err, "failed to scan eager loaded results for notices") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "failed to plebian-bind eager loaded slice notices") + } + + resultSlice = append(resultSlice, one) + localJoinCols = append(localJoinCols, localJoinCol) + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on notices") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for notices") + } + + if len(noticeAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.Notices = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = ¬iceR{} + } + foreign.R.Pins = append(foreign.R.Pins, object) + } + return nil + } + + for i, foreign := range resultSlice { + localJoinCol := localJoinCols[i] + for _, local := range slice { + if local.ID == localJoinCol { + local.R.Notices = append(local.R.Notices, foreign) + if foreign.R == nil { + foreign.R = ¬iceR{} + } + foreign.R.Pins = append(foreign.R.Pins, local) + break + } + } + } + + return nil +} + +// AddNotices adds the given related objects to the existing relationships +// of the pin, optionally inserting them as new records. +// Appends related to o.R.Notices. +// Sets related.R.Pins appropriately. +func (o *Pin) AddNotices(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Notice) error { + var err error + for _, rel := range related { + if insert { + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + } + + for _, rel := range related { + query := "insert into \"pin_notices\" (\"pin_id\", \"notice_id\") values (?, ?)" + values := []interface{}{o.ID, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err = exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to insert into join table") + } + } + if o.R == nil { + o.R = &pinR{ + Notices: related, + } + } else { + o.R.Notices = append(o.R.Notices, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = ¬iceR{ + Pins: PinSlice{o}, + } + } else { + rel.R.Pins = append(rel.R.Pins, o) + } + } + return nil +} + +// SetNotices removes all previously related items of the +// pin replacing them completely with the passed +// in related items, optionally inserting them as new records. +// Sets o.R.Pins's Notices accordingly. +// Replaces o.R.Notices with related. +// Sets related.R.Pins's Notices accordingly. +func (o *Pin) SetNotices(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Notice) error { + query := "delete from \"pin_notices\" where \"pin_id\" = ?" + values := []interface{}{o.ID} + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err := exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + + removeNoticesFromPinsSlice(o, related) + if o.R != nil { + o.R.Notices = nil + } + return o.AddNotices(ctx, exec, insert, related...) +} + +// RemoveNotices relationships from objects passed in. +// Removes related items from R.Notices (uses pointer comparison, removal does not keep order) +// Sets related.R.Pins. +func (o *Pin) RemoveNotices(ctx context.Context, exec boil.ContextExecutor, related ...*Notice) error { + var err error + query := fmt.Sprintf( + "delete from \"pin_notices\" where \"pin_id\" = ? and \"notice_id\" in (%s)", + strmangle.Placeholders(dialect.UseIndexPlaceholders, len(related), 2, 1), + ) + values := []interface{}{o.ID} + for _, rel := range related { + values = append(values, rel.ID) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err = exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + removeNoticesFromPinsSlice(o, related) + if o.R == nil { + return nil + } + + for _, rel := range related { + for i, ri := range o.R.Notices { + if rel != ri { + continue + } + + ln := len(o.R.Notices) + if ln > 1 && i < ln-1 { + o.R.Notices[i] = o.R.Notices[ln-1] + } + o.R.Notices = o.R.Notices[:ln-1] + break + } + } + + return nil +} + +func removeNoticesFromPinsSlice(o *Pin, related []*Notice) { + for _, rel := range related { + if rel.R == nil { + continue + } + for i, ri := range rel.R.Pins { + if o.ID != ri.ID { + continue + } + + ln := len(rel.R.Pins) + if ln > 1 && i < ln-1 { + rel.R.Pins[i] = rel.R.Pins[ln-1] + } + rel.R.Pins = rel.R.Pins[:ln-1] + break + } + } +} + +// Pins retrieves all the records using an executor. +func Pins(mods ...qm.QueryMod) pinQuery { + mods = append(mods, qm.From("\"pins\"")) + return pinQuery{NewQuery(mods...)} +} + +// FindPin retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindPin(ctx context.Context, exec boil.ContextExecutor, iD int64, selectCols ...string) (*Pin, error) { + pinObj := &Pin{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"pins\" where \"id\"=?", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, pinObj) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from pins") + } + + return pinObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *Pin) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no pins provided for insertion") + } + + var err error + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(pinColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + pinInsertCacheMut.RLock() + cache, cached := pinInsertCache[key] + pinInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + pinAllColumns, + pinColumnsWithDefault, + pinColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(pinType, pinMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(pinType, pinMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"pins\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"pins\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + cache.retQuery = fmt.Sprintf("SELECT \"%s\" FROM \"pins\" WHERE %s", strings.Join(returnColumns, "\",\""), strmangle.WhereClause("\"", "\"", 0, pinPrimaryKeyColumns)) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + result, err := exec.ExecContext(ctx, cache.query, vals...) + + if err != nil { + return errors.Wrap(err, "models: unable to insert into pins") + } + + var lastID int64 + var identifierCols []interface{} + + if len(cache.retMapping) == 0 { + goto CacheNoHooks + } + + lastID, err = result.LastInsertId() + if err != nil { + return ErrSyncFail + } + + o.ID = int64(lastID) + if lastID != 0 && len(cache.retMapping) == 1 && cache.retMapping[0] == pinMapping["id"] { + goto CacheNoHooks + } + + identifierCols = []interface{}{ + o.ID, + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.retQuery) + fmt.Fprintln(writer, identifierCols...) + } + err = exec.QueryRowContext(ctx, cache.retQuery, identifierCols...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + if err != nil { + return errors.Wrap(err, "models: unable to populate default values for pins") + } + +CacheNoHooks: + if !cached { + pinInsertCacheMut.Lock() + pinInsertCache[key] = cache + pinInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the Pin. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *Pin) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + pinUpdateCacheMut.RLock() + cache, cached := pinUpdateCache[key] + pinUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + pinAllColumns, + pinPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update pins, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"pins\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 0, wl), + strmangle.WhereClause("\"", "\"", 0, pinPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(pinType, pinMapping, append(wl, pinPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update pins row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for pins") + } + + if !cached { + pinUpdateCacheMut.Lock() + pinUpdateCache[key] = cache + pinUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q pinQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for pins") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for pins") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o PinSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), pinPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"pins\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 0, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, pinPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in pin slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all pin") + } + return rowsAff, nil +} + +// Delete deletes a single Pin record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *Pin) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no Pin provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), pinPrimaryKeyMapping) + sql := "DELETE FROM \"pins\" WHERE \"id\"=?" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from pins") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for pins") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q pinQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no pinQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from pins") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for pins") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o PinSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(pinBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), pinPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"pins\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, pinPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from pin slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for pins") + } + + if len(pinAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *Pin) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindPin(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *PinSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := PinSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), pinPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"pins\".* FROM \"pins\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, pinPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in PinSlice") + } + + *o = slice + + return nil +} + +// PinExists checks if the Pin row exists. +func PinExists(ctx context.Context, exec boil.ContextExecutor, iD int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"pins\" where \"id\"=? limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if pins exists") + } + + return exists, nil +} diff --git a/admindb/sqlite/notices.go b/admindb/sqlite/notices.go index 730f08b..4f02a7a 100644 --- a/admindb/sqlite/notices.go +++ b/admindb/sqlite/notices.go @@ -5,9 +5,10 @@ import ( "database/sql" "fmt" - "github.com/volatiletech/sqlboiler/v4/boil" - "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/ssb-ngi-pointer/go-ssb-room/admindb" "github.com/ssb-ngi-pointer/go-ssb-room/admindb/sqlite/models" ) @@ -19,12 +20,96 @@ type PinnedNotices struct { db *sql.DB } -func (pndb PinnedNotices) Set(name admindb.PinnedNoticeName, pageID int64, lang string) error { - if !name.Valid() { - return fmt.Errorf("fixed pages: invalid page name: %s", name) +// List returns a sortable map of all the pinned notices +func (pn PinnedNotices) List(ctx context.Context) (admindb.PinnedNotices, error) { + + // get all the pins and eager-load the related notices + lst, err := models.Pins(qm.Load("Notices")).All(ctx, pn.db) + if err != nil { + return nil, err } - return fmt.Errorf("TODO: set fixed page %s to %d:%s", name, pageID, lang) + // prepare a map for them + var pinnedMap = make(admindb.PinnedNotices, len(lst)) + + for _, entry := range lst { + + // skip a pin if it has no entries + noticeCount := len(entry.R.Notices) + if noticeCount == 0 { + continue + } + + name := admindb.PinnedNoticeName(entry.Name) + + // get the exisint map entry or create a new slice + notices, has := pinnedMap[name] + if !has { + notices = make([]admindb.Notice, 0, noticeCount) + } + + // add all the related notice to the slice + for _, n := range entry.R.Notices { + relatedNotice := admindb.Notice{ + ID: n.ID, + Title: n.Title, + Content: n.Content, + Language: n.Language, + } + notices = append(notices, relatedNotice) + } + + // update the map + pinnedMap[name] = notices + } + + return pinnedMap, nil +} + +func (pn PinnedNotices) Get(ctx context.Context, name admindb.PinnedNoticeName, lang string) (*admindb.Notice, error) { + p, err := models.Pins( + qm.Where("name = ?", name), + qm.Load("Notices", qm.Where("language = ?", lang)), + ).One(ctx, pn.db) + if err != nil { + return nil, err + } + + if n := len(p.R.Notices); n != 1 { + return nil, fmt.Errorf("pinnedNotice: expected 1 notice but got %d", n) + } + + modelNotice := p.R.Notices[0] + + return &admindb.Notice{ + ID: modelNotice.ID, + Title: modelNotice.Title, + Content: modelNotice.Content, + Language: modelNotice.Language, + }, nil +} + +func (pn PinnedNotices) Set(ctx context.Context, name admindb.PinnedNoticeName, noticeID int64) error { + if !name.Valid() { + return fmt.Errorf("pinned notice: invalid notice name: %s", name) + } + + n, err := models.FindNotice(ctx, pn.db, noticeID) + if err != nil { + return err + } + + p, err := models.Pins(qm.Where("name = ?", name)).One(ctx, pn.db) + if err != nil { + return err + } + + err = p.AddNotices(ctx, pn.db, false, n) + if err != nil { + return err + } + + return nil } // make sure to implement interfaces correctly @@ -34,28 +119,28 @@ type Notices struct { db *sql.DB } -func (ndb Notices) GetByID(ctx context.Context, id int64) (admindb.Notice, error) { - var n admindb.Notice +func (n Notices) GetByID(ctx context.Context, id int64) (admindb.Notice, error) { + var notice admindb.Notice - dbEntry, err := models.FindNotice(ctx, ndb.db, id) + dbEntry, err := models.FindNotice(ctx, n.db, id) if err != nil { if errors.Is(err, sql.ErrNoRows) { - return n, admindb.ErrNotFound + return notice, admindb.ErrNotFound } - return n, err + return notice, err } // convert models type to admindb type - n.ID = dbEntry.ID - n.Title = dbEntry.Title - n.Language = dbEntry.Language - n.Content = dbEntry.Content + notice.ID = dbEntry.ID + notice.Title = dbEntry.Title + notice.Language = dbEntry.Language + notice.Content = dbEntry.Content - return n, nil + return notice, nil } -func (ndb Notices) RemoveID(ctx context.Context, id int64) error { - dbEntry, err := models.FindNotice(ctx, ndb.db, id) +func (n Notices) RemoveID(ctx context.Context, id int64) error { + dbEntry, err := models.FindNotice(ctx, n.db, id) if err != nil { if errors.Is(err, sql.ErrNoRows) { return admindb.ErrNotFound @@ -63,7 +148,7 @@ func (ndb Notices) RemoveID(ctx context.Context, id int64) error { return err } - _, err = dbEntry.Delete(ctx, ndb.db) + _, err = dbEntry.Delete(ctx, n.db) if err != nil { return err } @@ -71,13 +156,13 @@ func (ndb Notices) RemoveID(ctx context.Context, id int64) error { return nil } -func (ndb Notices) Save(ctx context.Context, p *admindb.Notice) error { +func (n Notices) Save(ctx context.Context, p *admindb.Notice) error { if p.ID == 0 { var newEntry models.Notice newEntry.Title = p.Title newEntry.Content = p.Content newEntry.Language = p.Language - err := newEntry.Insert(ctx, ndb.db, boil.Whitelist("title", "content", "language")) + err := newEntry.Insert(ctx, n.db, boil.Whitelist("title", "content", "language")) if err != nil { return err } @@ -90,7 +175,7 @@ func (ndb Notices) Save(ctx context.Context, p *admindb.Notice) error { existing.Title = p.Title existing.Content = p.Content existing.Language = p.Language - _, err := existing.Update(ctx, ndb.db, boil.Whitelist("title", "content", "language")) + _, err := existing.Update(ctx, n.db, boil.Whitelist("title", "content", "language")) if err != nil { if errors.Is(err, sql.ErrNoRows) { return admindb.ErrNotFound diff --git a/admindb/sqlite/notices_test.go b/admindb/sqlite/notices_test.go index a93c357..6ef68a7 100644 --- a/admindb/sqlite/notices_test.go +++ b/admindb/sqlite/notices_test.go @@ -73,3 +73,93 @@ func TestNoticesCRUD(t *testing.T) { r.EqualError(err, admindb.ErrNotFound.Error()) }) } + +func TestPinnedNotices(t *testing.T) { + r := require.New(t) + ctx := context.Background() + + testRepo := filepath.Join("testrun", t.Name()) + os.RemoveAll(testRepo) + + tr := repo.New(testRepo) + + db, err := Open(tr) + r.NoError(err) + + t.Run("defaults", func(t *testing.T) { + allTheNotices, err := db.PinnedNotices.List(ctx) + r.NoError(err) + + type expectedNotices struct { + Name admindb.PinnedNoticeName + Count int + } + + cases := []expectedNotices{ + {admindb.NoticeDescription, 2}, + {admindb.NoticeNews, 1}, + {admindb.NoticePrivacyPolicy, 2}, + {admindb.NoticeCodeOfConduct, 1}, + } + + for i, tcase := range cases { + desc, has := allTheNotices[tcase.Name] + r.True(has, "case %d failed - notice %s not in map", i, tcase.Name) + r.Len(desc, tcase.Count, "case %d failed - wrong number of notices for %s", i, tcase.Name) + } + }) + + t.Run("validity", func(t *testing.T) { + var empty admindb.Notice + // no id + err = db.PinnedNotices.Set(ctx, admindb.NoticeNews, empty.ID) + r.Error(err) + + // not-null id + empty.ID = 999 + err = db.PinnedNotices.Set(ctx, admindb.NoticeNews, empty.ID) + r.Error(err) + + // invalid notice name + err = db.PinnedNotices.Set(ctx, "unknown", empty.ID) + r.Error(err) + }) + + t.Run("add new localization", func(t *testing.T) { + var notice admindb.Notice + notice.Title = "polĂ­tica de privacidad" + notice.Content = "solo una prueba" + notice.Language = "es" + + // save the new notice + err = db.Notices.Save(ctx, ¬ice) + r.NoError(err) + + // set it + err = db.PinnedNotices.Set(ctx, admindb.NoticePrivacyPolicy, notice.ID) + r.NoError(err) + + // retreive it + ret, err := db.PinnedNotices.Get(ctx, admindb.NoticePrivacyPolicy, notice.Language) + r.NoError(err) + r.Equal(notice, *ret, "notices are not the same") + + // see that it's in the list + allTheNotices, err := db.PinnedNotices.List(ctx) + r.NoError(err) + + notices, has := allTheNotices[admindb.NoticePrivacyPolicy] + r.True(has) + r.Len(notices, 3) + + has = false + for _, n := range notices { + if n.Title == notice.Title { + has = true + break + } + } + r.True(has, "did not find new notice in list()") + }) + +} diff --git a/admindb/types.go b/admindb/types.go index ef8230d..b3ecddf 100644 --- a/admindb/types.go +++ b/admindb/types.go @@ -6,6 +6,7 @@ import ( "database/sql/driver" "errors" "fmt" + "sort" refs "go.mindeco.de/ssb-refs" ) @@ -83,6 +84,8 @@ func (fpn PinnedNoticeName) Valid() bool { fpn == NoticeCodeOfConduct } +type PinnedNotices map[PinnedNoticeName][]Notice + // Notice holds the title and content of a page that is user generated type Notice struct { ID int64 @@ -90,3 +93,38 @@ type Notice struct { Content string Language string } + +type PinnedNotice struct { + Name PinnedNoticeName + Notices []Notice +} + +type SortedPinnedNotices []PinnedNotice + +// Sorted returns a sorted list of the map, by the key names +func (pn PinnedNotices) Sorted() SortedPinnedNotices { + + lst := make(SortedPinnedNotices, 0, len(pn)) + + for name, notices := range pn { + lst = append(lst, PinnedNotice{ + Name: name, + Notices: notices, + }) + } + + sort.Sort(lst) + return lst +} + +var _ sort.Interface = (SortedPinnedNotices)(nil) + +func (byName SortedPinnedNotices) Len() int { return len(byName) } + +func (byName SortedPinnedNotices) Less(i, j int) bool { + return byName[i].Name < byName[j].Name +} + +func (byName SortedPinnedNotices) Swap(i, j int) { + byName[i], byName[j] = byName[j], byName[i] +} diff --git a/cmd/server/main.go b/cmd/server/main.go index 255f1e4..bdb6a71 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -218,6 +218,7 @@ func runroomsrv() error { db.AuthFallback, db.AllowList, db.Notices, + db.PinnedNotices, ) if err != nil { return fmt.Errorf("failed to create HTTPdashboard handler: %w", err) diff --git a/web/handlers/http.go b/web/handlers/http.go index 5e55ab7..905e052 100644 --- a/web/handlers/http.go +++ b/web/handlers/http.go @@ -28,7 +28,8 @@ import ( var HTMLTempaltes = []string{ "landing/index.tmpl", "landing/about.tmpl", - "notice.tmpl", + "notice/list.tmpl", + "notice/show.tmpl", "error.tmpl", } @@ -41,6 +42,7 @@ func New( fs admindb.AuthFallbackService, al admindb.AllowListService, ns admindb.NoticesService, + ps admindb.PinnedNoticesService, ) (http.Handler, error) { m := router.CompleteApp() @@ -199,9 +201,11 @@ func New( m.Get(router.CompleteIndex).Handler(r.StaticHTML("landing/index.tmpl")) m.Get(router.CompleteAbout).Handler(r.StaticHTML("landing/about.tmpl")) - var nr noticeRenderer + var nr noticeHandler nr.notices = ns - m.Get(router.CompleteNotice).Handler(r.HTML("notice.tmpl", nr.render)) + nr.pinned = ps + m.Get(router.CompleteNoticeList).Handler(r.HTML("notice/list.tmpl", nr.list)) + m.Get(router.CompleteNoticeShow).Handler(r.HTML("notice/show.tmpl", nr.show)) m.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(web.Assets))) diff --git a/web/handlers/notices.go b/web/handlers/notices.go index 85f8264..5d6c711 100644 --- a/web/handlers/notices.go +++ b/web/handlers/notices.go @@ -11,30 +11,43 @@ import ( "github.com/ssb-ngi-pointer/go-ssb-room/web/errors" ) -type noticeRenderer struct { +type noticeHandler struct { + pinned admindb.PinnedNoticesService notices admindb.NoticesService } -type noticeData struct { +func (h noticeHandler) list(rw http.ResponseWriter, req *http.Request) (interface{}, error) { + + lst, err := h.pinned.List(req.Context()) + if err != nil { + return nil, err + } + + return struct { + Entries admindb.SortedPinnedNotices + }{lst.Sorted()}, nil +} + +type noticeShowData struct { ID int64 Title, Language string Content template.HTML } -func (pr noticeRenderer) render(rw http.ResponseWriter, req *http.Request) (interface{}, error) { +func (h noticeHandler) show(rw http.ResponseWriter, req *http.Request) (interface{}, error) { noticeID, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64) if err != nil { return nil, errors.ErrBadRequest{Where: "notice ID", Details: err} } - notice, err := pr.notices.GetByID(req.Context(), noticeID) + notice, err := h.notices.GetByID(req.Context(), noticeID) if err != nil { return nil, err } markdown := blackfriday.Run([]byte(notice.Content), blackfriday.WithNoExtensions()) - return noticeData{ + return noticeShowData{ ID: noticeID, Title: notice.Title, Content: template.HTML(markdown), diff --git a/web/i18n/defaults/active.en.toml b/web/i18n/defaults/active.en.toml index bdad47c..1e87460 100644 --- a/web/i18n/defaults/active.en.toml +++ b/web/i18n/defaults/active.en.toml @@ -1,6 +1,8 @@ -Confirm = "Confirm" -Save = "Save" -Preview = "Preview" +GenericConfirm = "Yes" +GenericGoBack = "Back" +GenericSave = "Save" +GenericPreview = "Preview" + PageNotFound = "The requested page was not found." LandingTitle = "ohai my room" @@ -15,9 +17,6 @@ AuthFallbackTitle = "The place of last resort" AuthSignIn = "Sign in" AuthSignOut = "Sign out" -GenericConfirm = "Yes" -GenericGoBack = "Back" - AdminDashboardWelcome = "Welcome to your dashboard" AdminDashboardTitle = "Room Admin Dashboard" @@ -30,13 +29,14 @@ AdminAllowListRemoveConfirmTitle = "Confirm member removal" NavAdminDashboard = "Dashboard" +NoticeEditTitle = "Edit Notice" +NoticeList = "List of notices" + [MemberCount] description = "Number of members" one = "1 member" other = "{{.Count}} members" -NoticeEditTitle = "Edit Notice" - [ListCount] description = "generic list" one = "There is one item on the List" diff --git a/web/router/complete.go b/web/router/complete.go index 1cc3168..39237b5 100644 --- a/web/router/complete.go +++ b/web/router/complete.go @@ -8,9 +8,11 @@ import ( // constant names for the named routes const ( - CompleteIndex = "complete:index" - CompleteAbout = "complete:about" - CompleteNotice = "complete:notice" + CompleteIndex = "complete:index" + CompleteAbout = "complete:about" + + CompleteNoticeShow = "complete:notice:show" + CompleteNoticeList = "complete:notice:list" ) // CompleteApp constructs a mux.Router containing the routes for batch Complete html frontend @@ -23,7 +25,8 @@ func CompleteApp() *mux.Router { m.Path("/").Methods("GET").Name(CompleteIndex) m.Path("/about").Methods("GET").Name(CompleteAbout) - m.Path("/notice").Methods("GET").Name(CompleteNotice) + m.Path("/notice/show").Methods("GET").Name(CompleteNoticeShow) + m.Path("/notice/list").Methods("GET").Name(CompleteNoticeList) return m } diff --git a/web/templates/admin/notice-edit.tmpl b/web/templates/admin/notice-edit.tmpl index a69d780..838e6c3 100644 --- a/web/templates/admin/notice-edit.tmpl +++ b/web/templates/admin/notice-edit.tmpl @@ -33,11 +33,11 @@ + >{{i18n "GenericSave"}}
-

{{i18n "Preview"}}

+

{{i18n "GenericPreview"}}

{{.ContentPreview}}
diff --git a/web/templates/notice/list.tmpl b/web/templates/notice/list.tmpl new file mode 100644 index 0000000..f1e37ee --- /dev/null +++ b/web/templates/notice/list.tmpl @@ -0,0 +1,18 @@ +{{ define "title" }}{{i18n "NoticeList"}}{{ end }} +{{ define "content" }} +
+

{{i18n "NoticeList"}}

+ + +
+{{end}} \ No newline at end of file diff --git a/web/templates/notice.tmpl b/web/templates/notice/show.tmpl similarity index 100% rename from web/templates/notice.tmpl rename to web/templates/notice/show.tmpl