diff --git a/admindb/interface.go b/admindb/interface.go index 806291e..c92e7ab 100644 --- a/admindb/interface.go +++ b/admindb/interface.go @@ -47,6 +47,26 @@ type AllowListService interface { // AliasService manages alias handle registration and lookup 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 { + // 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 +} + +type NoticesService interface { + // GetByID returns the page for that ID or an error + GetByID(context.Context, int64) (Notice, error) + + // Save updates the passed page or creates it if it's ID is zero + Save(context.Context, *Notice) error + + // RemoveID removes the page for that ID. + RemoveID(context.Context, int64) error +} + // for tests we use generated mocks from these interfaces created with https://github.com/maxbrunsfeld/counterfeiter //go:generate counterfeiter -o mockdb/auth.go . AuthWithSSBService @@ -56,3 +76,7 @@ type AliasService interface{} //go:generate counterfeiter -o mockdb/allow.go . AllowListService //go:generate counterfeiter -o mockdb/alias.go . AliasService + +//go:generate counterfeiter -o mockdb/fixed_pages.go . PinnedNoticesService + +//go:generate counterfeiter -o mockdb/pages.go . NoticesService diff --git a/admindb/mockdb/fixed_pages.go b/admindb/mockdb/fixed_pages.go new file mode 100644 index 0000000..db70afd --- /dev/null +++ b/admindb/mockdb/fixed_pages.go @@ -0,0 +1,115 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package mockdb + +import ( + "sync" + + "github.com/ssb-ngi-pointer/go-ssb-room/admindb" +) + +type FakePinnedNoticesService struct { + SetStub func(admindb.PinnedNoticeName, int64, string) error + setMutex sync.RWMutex + setArgsForCall []struct { + arg1 admindb.PinnedNoticeName + arg2 int64 + arg3 string + } + setReturns struct { + result1 error + } + setReturnsOnCall map[int]struct { + result1 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakePinnedNoticesService) Set(arg1 admindb.PinnedNoticeName, arg2 int64, arg3 string) 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, arg2, arg3}) + stub := fake.SetStub + fakeReturns := fake.setReturns + fake.recordInvocation("Set", []interface{}{arg1, arg2, arg3}) + fake.setMutex.Unlock() + if stub != nil { + return stub(arg1, arg2, arg3) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakePinnedNoticesService) SetCallCount() int { + fake.setMutex.RLock() + defer fake.setMutex.RUnlock() + return len(fake.setArgsForCall) +} + +func (fake *FakePinnedNoticesService) SetCalls(stub func(admindb.PinnedNoticeName, int64, string) error) { + fake.setMutex.Lock() + defer fake.setMutex.Unlock() + fake.SetStub = stub +} + +func (fake *FakePinnedNoticesService) SetArgsForCall(i int) (admindb.PinnedNoticeName, int64, string) { + fake.setMutex.RLock() + defer fake.setMutex.RUnlock() + argsForCall := fake.setArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3 +} + +func (fake *FakePinnedNoticesService) SetReturns(result1 error) { + fake.setMutex.Lock() + defer fake.setMutex.Unlock() + fake.SetStub = nil + fake.setReturns = struct { + result1 error + }{result1} +} + +func (fake *FakePinnedNoticesService) SetReturnsOnCall(i int, result1 error) { + fake.setMutex.Lock() + defer fake.setMutex.Unlock() + fake.SetStub = nil + if fake.setReturnsOnCall == nil { + fake.setReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.setReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakePinnedNoticesService) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.setMutex.RLock() + defer fake.setMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakePinnedNoticesService) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ admindb.PinnedNoticesService = new(FakePinnedNoticesService) diff --git a/admindb/mockdb/pages.go b/admindb/mockdb/pages.go new file mode 100644 index 0000000..1bbd897 --- /dev/null +++ b/admindb/mockdb/pages.go @@ -0,0 +1,276 @@ +// Code generated by counterfeiter. DO NOT EDIT. +package mockdb + +import ( + "context" + "sync" + + "github.com/ssb-ngi-pointer/go-ssb-room/admindb" +) + +type FakeNoticesService struct { + GetByIDStub func(context.Context, int64) (admindb.Notice, error) + getByIDMutex sync.RWMutex + getByIDArgsForCall []struct { + arg1 context.Context + arg2 int64 + } + getByIDReturns struct { + result1 admindb.Notice + result2 error + } + getByIDReturnsOnCall map[int]struct { + result1 admindb.Notice + result2 error + } + RemoveIDStub func(context.Context, int64) error + removeIDMutex sync.RWMutex + removeIDArgsForCall []struct { + arg1 context.Context + arg2 int64 + } + removeIDReturns struct { + result1 error + } + removeIDReturnsOnCall map[int]struct { + result1 error + } + SaveStub func(context.Context, admindb.Notice) (int64, error) + saveMutex sync.RWMutex + saveArgsForCall []struct { + arg1 context.Context + arg2 admindb.Notice + } + saveReturns struct { + result1 int64 + result2 error + } + saveReturnsOnCall map[int]struct { + result1 int64 + result2 error + } + invocations map[string][][]interface{} + invocationsMutex sync.RWMutex +} + +func (fake *FakeNoticesService) GetByID(arg1 context.Context, arg2 int64) (admindb.Notice, error) { + fake.getByIDMutex.Lock() + ret, specificReturn := fake.getByIDReturnsOnCall[len(fake.getByIDArgsForCall)] + fake.getByIDArgsForCall = append(fake.getByIDArgsForCall, struct { + arg1 context.Context + arg2 int64 + }{arg1, arg2}) + stub := fake.GetByIDStub + fakeReturns := fake.getByIDReturns + fake.recordInvocation("GetByID", []interface{}{arg1, arg2}) + fake.getByIDMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeNoticesService) GetByIDCallCount() int { + fake.getByIDMutex.RLock() + defer fake.getByIDMutex.RUnlock() + return len(fake.getByIDArgsForCall) +} + +func (fake *FakeNoticesService) GetByIDCalls(stub func(context.Context, int64) (admindb.Notice, error)) { + fake.getByIDMutex.Lock() + defer fake.getByIDMutex.Unlock() + fake.GetByIDStub = stub +} + +func (fake *FakeNoticesService) GetByIDArgsForCall(i int) (context.Context, int64) { + fake.getByIDMutex.RLock() + defer fake.getByIDMutex.RUnlock() + argsForCall := fake.getByIDArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeNoticesService) GetByIDReturns(result1 admindb.Notice, result2 error) { + fake.getByIDMutex.Lock() + defer fake.getByIDMutex.Unlock() + fake.GetByIDStub = nil + fake.getByIDReturns = struct { + result1 admindb.Notice + result2 error + }{result1, result2} +} + +func (fake *FakeNoticesService) GetByIDReturnsOnCall(i int, result1 admindb.Notice, result2 error) { + fake.getByIDMutex.Lock() + defer fake.getByIDMutex.Unlock() + fake.GetByIDStub = nil + if fake.getByIDReturnsOnCall == nil { + fake.getByIDReturnsOnCall = make(map[int]struct { + result1 admindb.Notice + result2 error + }) + } + fake.getByIDReturnsOnCall[i] = struct { + result1 admindb.Notice + result2 error + }{result1, result2} +} + +func (fake *FakeNoticesService) RemoveID(arg1 context.Context, arg2 int64) error { + fake.removeIDMutex.Lock() + ret, specificReturn := fake.removeIDReturnsOnCall[len(fake.removeIDArgsForCall)] + fake.removeIDArgsForCall = append(fake.removeIDArgsForCall, struct { + arg1 context.Context + arg2 int64 + }{arg1, arg2}) + stub := fake.RemoveIDStub + fakeReturns := fake.removeIDReturns + fake.recordInvocation("RemoveID", []interface{}{arg1, arg2}) + fake.removeIDMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1 + } + return fakeReturns.result1 +} + +func (fake *FakeNoticesService) RemoveIDCallCount() int { + fake.removeIDMutex.RLock() + defer fake.removeIDMutex.RUnlock() + return len(fake.removeIDArgsForCall) +} + +func (fake *FakeNoticesService) RemoveIDCalls(stub func(context.Context, int64) error) { + fake.removeIDMutex.Lock() + defer fake.removeIDMutex.Unlock() + fake.RemoveIDStub = stub +} + +func (fake *FakeNoticesService) RemoveIDArgsForCall(i int) (context.Context, int64) { + fake.removeIDMutex.RLock() + defer fake.removeIDMutex.RUnlock() + argsForCall := fake.removeIDArgsForCall[i] + return argsForCall.arg1, argsForCall.arg2 +} + +func (fake *FakeNoticesService) RemoveIDReturns(result1 error) { + fake.removeIDMutex.Lock() + defer fake.removeIDMutex.Unlock() + fake.RemoveIDStub = nil + fake.removeIDReturns = struct { + result1 error + }{result1} +} + +func (fake *FakeNoticesService) RemoveIDReturnsOnCall(i int, result1 error) { + fake.removeIDMutex.Lock() + defer fake.removeIDMutex.Unlock() + fake.RemoveIDStub = nil + if fake.removeIDReturnsOnCall == nil { + fake.removeIDReturnsOnCall = make(map[int]struct { + result1 error + }) + } + fake.removeIDReturnsOnCall[i] = struct { + result1 error + }{result1} +} + +func (fake *FakeNoticesService) Save(arg1 context.Context, arg2 admindb.Notice) (int64, error) { + fake.saveMutex.Lock() + ret, specificReturn := fake.saveReturnsOnCall[len(fake.saveArgsForCall)] + fake.saveArgsForCall = append(fake.saveArgsForCall, struct { + arg1 context.Context + arg2 admindb.Notice + }{arg1, arg2}) + stub := fake.SaveStub + fakeReturns := fake.saveReturns + fake.recordInvocation("Save", []interface{}{arg1, arg2}) + fake.saveMutex.Unlock() + if stub != nil { + return stub(arg1, arg2) + } + if specificReturn { + return ret.result1, ret.result2 + } + return fakeReturns.result1, fakeReturns.result2 +} + +func (fake *FakeNoticesService) SaveCallCount() int { + fake.saveMutex.RLock() + defer fake.saveMutex.RUnlock() + return len(fake.saveArgsForCall) +} + +func (fake *FakeNoticesService) SaveCalls(stub func(context.Context, admindb.Notice) (int64, error)) { + fake.saveMutex.Lock() + defer fake.saveMutex.Unlock() + fake.SaveStub = stub +} + +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) { + fake.saveMutex.Lock() + defer fake.saveMutex.Unlock() + fake.SaveStub = nil + fake.saveReturns = struct { + result1 int64 + result2 error + }{result1, result2} +} + +func (fake *FakeNoticesService) SaveReturnsOnCall(i int, result1 int64, result2 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 + }) + } + fake.saveReturnsOnCall[i] = struct { + result1 int64 + result2 error + }{result1, result2} +} + +func (fake *FakeNoticesService) Invocations() map[string][][]interface{} { + fake.invocationsMutex.RLock() + defer fake.invocationsMutex.RUnlock() + fake.getByIDMutex.RLock() + defer fake.getByIDMutex.RUnlock() + fake.removeIDMutex.RLock() + defer fake.removeIDMutex.RUnlock() + fake.saveMutex.RLock() + defer fake.saveMutex.RUnlock() + copiedInvocations := map[string][][]interface{}{} + for key, value := range fake.invocations { + copiedInvocations[key] = value + } + return copiedInvocations +} + +func (fake *FakeNoticesService) recordInvocation(key string, args []interface{}) { + fake.invocationsMutex.Lock() + defer fake.invocationsMutex.Unlock() + if fake.invocations == nil { + fake.invocations = map[string][][]interface{}{} + } + if fake.invocations[key] == nil { + fake.invocations[key] = [][]interface{}{} + } + fake.invocations[key] = append(fake.invocations[key], args) +} + +var _ admindb.NoticesService = new(FakeNoticesService) diff --git a/admindb/sqlite/migrations/3-notices.sql b/admindb/sqlite/migrations/3-notices.sql new file mode 100644 index 0000000..f977b96 --- /dev/null +++ b/admindb/sqlite/migrations/3-notices.sql @@ -0,0 +1,44 @@ +-- +migrate Up +CREATE TABLE notices ( + id integer PRIMARY KEY AUTOINCREMENT NOT NULL, + title text NOT NULL, + content text NOT NULL, + 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), + + -- make sure the notices exist + FOREIGN KEY ( notice_id ) REFERENCES notices( "id" ) +); + +INSERT INTO notices (title, content, language) VALUES +('Description', 'Basic description of this Room.', 'en-GB'), +('News', 'Some recent updates...', 'en-GB'), +('Code of conduct', 'We expect each other to ... +* be considerate +* be respectful +* be responsible +* be dedicated +* be empathetic +', 'en-GB'), +('Privacy Policy', 'To be updated', 'en-GB'), +('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'); + +-- +migrate Down +DROP TABLE notices; +DROP TABLE pinned_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 074b9c6..838cb84 100644 --- a/admindb/sqlite/models/boil_table_names.go +++ b/admindb/sqlite/models/boil_table_names.go @@ -4,9 +4,13 @@ package models var TableNames = struct { - AllowList string - AuthFallback string + AllowList string + AuthFallback string + Notices string + PinnedNotices string }{ - AllowList: "allow_list", - AuthFallback: "auth_fallback", + AllowList: "allow_list", + AuthFallback: "auth_fallback", + Notices: "notices", + PinnedNotices: "pinned_notices", } diff --git a/admindb/sqlite/models/notices.go b/admindb/sqlite/models/notices.go new file mode 100644 index 0000000..0eb26ba --- /dev/null +++ b/admindb/sqlite/models/notices.go @@ -0,0 +1,959 @@ +// 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" +) + +// Notice is an object representing the database table. +type Notice struct { + ID int64 `boil:"id" json:"id" toml:"id" yaml:"id"` + Title string `boil:"title" json:"title" toml:"title" yaml:"title"` + Content string `boil:"content" json:"content" toml:"content" yaml:"content"` + Language string `boil:"language" json:"language" toml:"language" yaml:"language"` + + R *noticeR `boil:"-" json:"-" toml:"-" yaml:"-"` + L noticeL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var NoticeColumns = struct { + ID string + Title string + Content string + Language string +}{ + ID: "id", + Title: "title", + Content: "content", + Language: "language", +} + +// Generated where + +var NoticeWhere = struct { + ID whereHelperint64 + Title whereHelperstring + Content whereHelperstring + Language whereHelperstring +}{ + ID: whereHelperint64{field: "\"notices\".\"id\""}, + Title: whereHelperstring{field: "\"notices\".\"title\""}, + Content: whereHelperstring{field: "\"notices\".\"content\""}, + Language: whereHelperstring{field: "\"notices\".\"language\""}, +} + +// NoticeRels is where relationship names are stored. +var NoticeRels = struct { + PinnedNotice string +}{ + PinnedNotice: "PinnedNotice", +} + +// noticeR is where relationships are stored. +type noticeR struct { + PinnedNotice *PinnedNotice `boil:"PinnedNotice" json:"PinnedNotice" toml:"PinnedNotice" yaml:"PinnedNotice"` +} + +// NewStruct creates a new relationship struct +func (*noticeR) NewStruct() *noticeR { + return ¬iceR{} +} + +// noticeL is where Load methods for each relationship are stored. +type noticeL struct{} + +var ( + noticeAllColumns = []string{"id", "title", "content", "language"} + noticeColumnsWithoutDefault = []string{} + noticeColumnsWithDefault = []string{"id", "title", "content", "language"} + noticePrimaryKeyColumns = []string{"id"} +) + +type ( + // NoticeSlice is an alias for a slice of pointers to Notice. + // This should generally be used opposed to []Notice. + NoticeSlice []*Notice + // NoticeHook is the signature for custom Notice hook methods + NoticeHook func(context.Context, boil.ContextExecutor, *Notice) error + + noticeQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + noticeType = reflect.TypeOf(&Notice{}) + noticeMapping = queries.MakeStructMapping(noticeType) + noticePrimaryKeyMapping, _ = queries.BindMapping(noticeType, noticeMapping, noticePrimaryKeyColumns) + noticeInsertCacheMut sync.RWMutex + noticeInsertCache = make(map[string]insertCache) + noticeUpdateCacheMut sync.RWMutex + noticeUpdateCache = make(map[string]updateCache) + noticeUpsertCacheMut sync.RWMutex + noticeUpsertCache = 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 noticeBeforeInsertHooks []NoticeHook +var noticeBeforeUpdateHooks []NoticeHook +var noticeBeforeDeleteHooks []NoticeHook +var noticeBeforeUpsertHooks []NoticeHook + +var noticeAfterInsertHooks []NoticeHook +var noticeAfterSelectHooks []NoticeHook +var noticeAfterUpdateHooks []NoticeHook +var noticeAfterDeleteHooks []NoticeHook +var noticeAfterUpsertHooks []NoticeHook + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *Notice) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *Notice) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *Notice) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *Notice) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *Notice) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *Notice) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *Notice) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *Notice) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *Notice) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range noticeAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddNoticeHook registers your hook function for all future operations. +func AddNoticeHook(hookPoint boil.HookPoint, noticeHook NoticeHook) { + switch hookPoint { + case boil.BeforeInsertHook: + noticeBeforeInsertHooks = append(noticeBeforeInsertHooks, noticeHook) + case boil.BeforeUpdateHook: + noticeBeforeUpdateHooks = append(noticeBeforeUpdateHooks, noticeHook) + case boil.BeforeDeleteHook: + noticeBeforeDeleteHooks = append(noticeBeforeDeleteHooks, noticeHook) + case boil.BeforeUpsertHook: + noticeBeforeUpsertHooks = append(noticeBeforeUpsertHooks, noticeHook) + case boil.AfterInsertHook: + noticeAfterInsertHooks = append(noticeAfterInsertHooks, noticeHook) + case boil.AfterSelectHook: + noticeAfterSelectHooks = append(noticeAfterSelectHooks, noticeHook) + case boil.AfterUpdateHook: + noticeAfterUpdateHooks = append(noticeAfterUpdateHooks, noticeHook) + case boil.AfterDeleteHook: + noticeAfterDeleteHooks = append(noticeAfterDeleteHooks, noticeHook) + case boil.AfterUpsertHook: + noticeAfterUpsertHooks = append(noticeAfterUpsertHooks, noticeHook) + } +} + +// One returns a single notice record from the query. +func (q noticeQuery) One(ctx context.Context, exec boil.ContextExecutor) (*Notice, error) { + o := &Notice{} + + 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 notices") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all Notice records from the query. +func (q noticeQuery) All(ctx context.Context, exec boil.ContextExecutor) (NoticeSlice, error) { + var o []*Notice + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to Notice slice") + } + + if len(noticeAfterSelectHooks) != 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 Notice records in the query. +func (q noticeQuery) 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 notices rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q noticeQuery) 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 notices exists") + } + + 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), + } + + queryMods = append(queryMods, mods...) + + query := PinnedNotices(queryMods...) + queries.SetFrom(query.Query, "\"pinned_notices\"") + + 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 { + var slice []*Notice + var object *Notice + + if singular { + object = maybeNotice.(*Notice) + } else { + slice = *maybeNotice.(*[]*Notice) + } + + args := make([]interface{}, 0, 1) + if singular { + if object.R == nil { + object.R = ¬iceR{} + } + args = append(args, object.ID) + } else { + Outer: + for _, obj := range slice { + if obj.R == nil { + obj.R = ¬iceR{} + } + + for _, a := range args { + if a == obj.ID { + continue Outer + } + } + + args = append(args, obj.ID) + } + } + + if len(args) == 0 { + return nil + } + + query := NewQuery( + qm.From(`pinned_notices`), + qm.WhereIn(`pinned_notices.notice_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 PinnedNotice") + } + + var resultSlice []*PinnedNotice + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice PinnedNotice") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results of eager load for pinned_notices") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for pinned_notices") + } + + if len(noticeAfterSelectHooks) != 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.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 + if foreign.R == nil { + foreign.R = &pinnedNoticeR{} + } + foreign.R.Notice = local + break + } + } + } + + 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 { + 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") + } + } 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} + + 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 foreign table") + } + + related.NoticeID = o.ID + + } + + if o.R == nil { + o.R = ¬iceR{ + PinnedNotice: related, + } + } else { + o.R.PinnedNotice = related + } + + if related.R == nil { + related.R = &pinnedNoticeR{ + Notice: o, + } + } else { + related.R.Notice = o + } + return nil +} + +// Notices retrieves all the records using an executor. +func Notices(mods ...qm.QueryMod) noticeQuery { + mods = append(mods, qm.From("\"notices\"")) + return noticeQuery{NewQuery(mods...)} +} + +// FindNotice retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindNotice(ctx context.Context, exec boil.ContextExecutor, iD int64, selectCols ...string) (*Notice, error) { + noticeObj := &Notice{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"notices\" where \"id\"=?", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, noticeObj) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from notices") + } + + return noticeObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *Notice) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no notices provided for insertion") + } + + var err error + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(noticeColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + noticeInsertCacheMut.RLock() + cache, cached := noticeInsertCache[key] + noticeInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + noticeAllColumns, + noticeColumnsWithDefault, + noticeColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(noticeType, noticeMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(noticeType, noticeMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"notices\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"notices\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + cache.retQuery = fmt.Sprintf("SELECT \"%s\" FROM \"notices\" WHERE %s", strings.Join(returnColumns, "\",\""), strmangle.WhereClause("\"", "\"", 0, noticePrimaryKeyColumns)) + } + + 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 notices") + } + + 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] == noticeMapping["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 notices") + } + +CacheNoHooks: + if !cached { + noticeInsertCacheMut.Lock() + noticeInsertCache[key] = cache + noticeInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the Notice. +// 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 *Notice) 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) + noticeUpdateCacheMut.RLock() + cache, cached := noticeUpdateCache[key] + noticeUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + noticeAllColumns, + noticePrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update notices, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"notices\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 0, wl), + strmangle.WhereClause("\"", "\"", 0, noticePrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(noticeType, noticeMapping, append(wl, noticePrimaryKeyColumns...)) + 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 notices row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for notices") + } + + if !cached { + noticeUpdateCacheMut.Lock() + noticeUpdateCache[key] = cache + noticeUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q noticeQuery) 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 notices") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for notices") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o NoticeSlice) 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)), noticePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"notices\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 0, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, noticePrimaryKeyColumns, 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 notice slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all notice") + } + return rowsAff, nil +} + +// Delete deletes a single Notice record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *Notice) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no Notice provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), noticePrimaryKeyMapping) + sql := "DELETE FROM \"notices\" 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 notices") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for notices") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q noticeQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no noticeQuery 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 notices") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for notices") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o NoticeSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(noticeBeforeDeleteHooks) != 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)), noticePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"notices\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, noticePrimaryKeyColumns, 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 notice slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for notices") + } + + if len(noticeAfterDeleteHooks) != 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 *Notice) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindNotice(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 *NoticeSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := NoticeSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), noticePrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"notices\".* FROM \"notices\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 0, noticePrimaryKeyColumns, 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 NoticeSlice") + } + + *o = slice + + return nil +} + +// NoticeExists checks if the Notice row exists. +func NoticeExists(ctx context.Context, exec boil.ContextExecutor, iD int64) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"notices\" 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 notices exists") + } + + return exists, nil +} diff --git a/admindb/sqlite/models/pinned_notices.go b/admindb/sqlite/models/pinned_notices.go new file mode 100644 index 0000000..38e46d0 --- /dev/null +++ b/admindb/sqlite/models/pinned_notices.go @@ -0,0 +1,943 @@ +// 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/new.go b/admindb/sqlite/new.go index 1737643..1148d2c 100644 --- a/admindb/sqlite/new.go +++ b/admindb/sqlite/new.go @@ -23,6 +23,9 @@ type Database struct { AllowList admindb.AllowListService Aliases admindb.AliasService + + PinnedNotices admindb.PinnedNoticesService + Notices admindb.NoticesService } // Open looks for a database file 'fname' @@ -55,11 +58,13 @@ func Open(r repo.Interface) (*Database, error) { } admindb := &Database{ - db: db, - AuthWithSSB: AuthWithSSB{db}, - AuthFallback: AuthFallback{db}, - AllowList: AllowList{db}, - Aliases: Aliases{db}, + db: db, + AuthWithSSB: AuthWithSSB{db}, + AuthFallback: AuthFallback{db}, + AllowList: AllowList{db}, + Aliases: Aliases{db}, + PinnedNotices: PinnedNotices{db}, + Notices: Notices{db}, } return admindb, nil diff --git a/admindb/sqlite/notices.go b/admindb/sqlite/notices.go new file mode 100644 index 0000000..730f08b --- /dev/null +++ b/admindb/sqlite/notices.go @@ -0,0 +1,102 @@ +package sqlite + +import ( + "context" + "database/sql" + "fmt" + + "github.com/volatiletech/sqlboiler/v4/boil" + + "github.com/friendsofgo/errors" + "github.com/ssb-ngi-pointer/go-ssb-room/admindb" + "github.com/ssb-ngi-pointer/go-ssb-room/admindb/sqlite/models" +) + +// make sure to implement interfaces correctly +var _ admindb.PinnedNoticesService = (*PinnedNotices)(nil) + +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) + } + + return fmt.Errorf("TODO: set fixed page %s to %d:%s", name, pageID, lang) +} + +// make sure to implement interfaces correctly +var _ admindb.NoticesService = (*Notices)(nil) + +type Notices struct { + db *sql.DB +} + +func (ndb Notices) GetByID(ctx context.Context, id int64) (admindb.Notice, error) { + var n admindb.Notice + + dbEntry, err := models.FindNotice(ctx, ndb.db, id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return n, admindb.ErrNotFound + } + return n, err + } + + // convert models type to admindb type + n.ID = dbEntry.ID + n.Title = dbEntry.Title + n.Language = dbEntry.Language + n.Content = dbEntry.Content + + return n, nil +} + +func (ndb Notices) RemoveID(ctx context.Context, id int64) error { + dbEntry, err := models.FindNotice(ctx, ndb.db, id) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return admindb.ErrNotFound + } + return err + } + + _, err = dbEntry.Delete(ctx, ndb.db) + if err != nil { + return err + } + + return nil +} + +func (ndb 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")) + if err != nil { + return err + } + p.ID = newEntry.ID + return nil + } + + var existing models.Notice + existing.ID = p.ID + existing.Title = p.Title + existing.Content = p.Content + existing.Language = p.Language + _, err := existing.Update(ctx, ndb.db, boil.Whitelist("title", "content", "language")) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return admindb.ErrNotFound + } + return err + } + + return nil +} diff --git a/admindb/sqlite/notices_test.go b/admindb/sqlite/notices_test.go new file mode 100644 index 0000000..a93c357 --- /dev/null +++ b/admindb/sqlite/notices_test.go @@ -0,0 +1,75 @@ +package sqlite + +import ( + "context" + "fmt" + "math/rand" + "os" + "path/filepath" + "testing" + + "github.com/ssb-ngi-pointer/go-ssb-room/admindb" + "github.com/ssb-ngi-pointer/go-ssb-room/internal/repo" + "github.com/stretchr/testify/require" +) + +func TestNoticesCRUD(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) + + // boil.DebugWriter = os.Stderr + // boil.DebugMode = true + + t.Run("not found", func(t *testing.T) { + r := require.New(t) + + _, err = db.Notices.GetByID(ctx, 9999) + r.Error(err) + r.EqualError(err, admindb.ErrNotFound.Error()) + + err = db.Notices.RemoveID(ctx, 9999) + r.Error(err) + r.EqualError(err, admindb.ErrNotFound.Error()) + }) + + t.Run("new and update", func(t *testing.T) { + r := require.New(t) + var n admindb.Notice + + n.Title = fmt.Sprintf("Test notice %d", rand.Int()) + n.Content = `# This is **not** a test!` + n.Language = "en-GB" + + err := db.Notices.Save(ctx, &n) + r.NoError(err, "failed to save") + r.NotEqual(0, n.ID, "should have a fresh id") + + got, err := db.Notices.GetByID(ctx, n.ID) + r.NoError(err, "failed to get saved entry") + r.Equal(n.Title, got.Title) + r.Equal(n.ID, got.ID) + r.Equal(n.Language, got.Language) + + oldID := n.ID + n.Title = fmt.Sprintf("Updated test notice %d", rand.Int()) + err = db.Notices.Save(ctx, &n) + r.NoError(err, "failed to save") + r.Equal(oldID, n.ID, "should have the same ID") + + // be gone + err = db.Notices.RemoveID(ctx, oldID) + r.NoError(err) + + _, err = db.Notices.GetByID(ctx, oldID) + r.Error(err) + r.EqualError(err, admindb.ErrNotFound.Error()) + }) +} diff --git a/admindb/types.go b/admindb/types.go index 5cd1e24..ef8230d 100644 --- a/admindb/types.go +++ b/admindb/types.go @@ -62,3 +62,31 @@ func (r *DBFeedRef) Scan(src interface{}) error { func (r DBFeedRef) Value() (driver.Value, error) { return driver.Value(r.Ref()), nil } + +// PinnedNoticeName holds a name of a well known part of the page with a fixed location. +// These also double as the i18n labels. +type PinnedNoticeName string + +// These are the well known names that the room page will display +const ( + NoticeDescription PinnedNoticeName = "NoticeDescription" + NoticeNews PinnedNoticeName = "NoticeNews" + NoticePrivacyPolicy PinnedNoticeName = "NoticePrivacyPolicy" + NoticeCodeOfConduct PinnedNoticeName = "NoticeCodeOfConduct" +) + +// Valid returns true if the page name is well known. +func (fpn PinnedNoticeName) Valid() bool { + return fpn == NoticeNews || + fpn == NoticeDescription || + fpn == NoticePrivacyPolicy || + fpn == NoticeCodeOfConduct +} + +// Notice holds the title and content of a page that is user generated +type Notice struct { + ID int64 + Title string + Content string + Language string +} diff --git a/cmd/server/main.go b/cmd/server/main.go index 7b5b860..255f1e4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -217,6 +217,7 @@ func runroomsrv() error { db.AuthWithSSB, db.AuthFallback, db.AllowList, + db.Notices, ) if err != nil { return fmt.Errorf("failed to create HTTPdashboard handler: %w", err) diff --git a/go.mod b/go.mod index e101288..96b4410 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/nicksnyder/go-i18n/v2 v2.1.2 github.com/pkg/errors v0.9.1 github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351 - github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect + github.com/russross/blackfriday/v2 v2.1.0 github.com/stretchr/testify v1.6.1 github.com/unrolled/secure v1.0.8 github.com/vcraescu/go-paginator/v2 v2.0.0 diff --git a/go.sum b/go.sum index ffe2575..fc7736c 100644 --- a/go.sum +++ b/go.sum @@ -347,8 +347,11 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.4.0 h1:LUa41nrWTQNGhzdsZ5lTnkwbNjj6rXTdazA1cSdjkOY= github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -357,9 +360,8 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxr github.com/shurcooL/go v0.0.0-20190121191506-3fef8c783dec/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go v0.0.0-20190330031554-6713ea532688/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b h1:4kg1wyftSKxLtnPAvcRWakIPpokB9w780/KwrNLnfPA= github.com/shurcooL/httpfs v0.0.0-20190527155220-6a4d4a70508b/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= diff --git a/web/handlers/admin/allow_list.go b/web/handlers/admin/allow_list.go index 5b25bfe..dc231e7 100644 --- a/web/handlers/admin/allow_list.go +++ b/web/handlers/admin/allow_list.go @@ -151,7 +151,7 @@ func (h allowListH) removeConfirm(rw http.ResponseWriter, req *http.Request) (in func (h allowListH) remove(rw http.ResponseWriter, req *http.Request) { err := req.ParseForm() if err != nil { - err = weberrors.ErrBadRequest{Where: "ID", Details: err} + err = weberrors.ErrBadRequest{Where: "Form data", Details: err} // TODO "flash" errors http.Redirect(rw, req, redirectTo, http.StatusFound) return diff --git a/web/handlers/admin/handler.go b/web/handlers/admin/handler.go index 1d94350..607b527 100644 --- a/web/handlers/admin/handler.go +++ b/web/handlers/admin/handler.go @@ -17,14 +17,23 @@ import ( var HTMLTemplates = []string{ "admin/dashboard.tmpl", "admin/menu.tmpl", + "admin/allow-list.tmpl", "admin/allow-list-remove-confirm.tmpl", + + "admin/notice-edit.tmpl", } // Handler supplies the elevated access pages to known users. // It is not registering on the mux router like other pages to clean up the authorize flow. -func Handler(r *render.Renderer, roomState *roomstate.Manager, al admindb.AllowListService) http.Handler { +func Handler( + r *render.Renderer, + roomState *roomstate.Manager, + al admindb.AllowListService, + ndb admindb.NoticesService, +) http.Handler { mux := &http.ServeMux{} + // TODO: configure 404 handler mux.HandleFunc("/dashboard", r.HTML("admin/dashboard.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) { lst := roomState.List() @@ -47,6 +56,13 @@ func Handler(r *render.Renderer, roomState *roomstate.Manager, al admindb.AllowL mux.HandleFunc("/members/remove/confirm", r.HTML("admin/allow-list-remove-confirm.tmpl", ah.removeConfirm)) mux.HandleFunc("/members/remove", ah.remove) + var nh = noticeHandler{ + r: r, + db: ndb, + } + mux.HandleFunc("/notice/edit", r.HTML("admin/notice-edit.tmpl", nh.edit)) + mux.HandleFunc("/notice/save", nh.save) + return customStripPrefix("/admin", mux) } diff --git a/web/handlers/admin/notices.go b/web/handlers/admin/notices.go new file mode 100644 index 0000000..40efe33 --- /dev/null +++ b/web/handlers/admin/notices.go @@ -0,0 +1,90 @@ +package admin + +import ( + "errors" + "html/template" + "net/http" + "strconv" + "strings" + + "github.com/gorilla/csrf" + "github.com/russross/blackfriday/v2" + "github.com/ssb-ngi-pointer/go-ssb-room/admindb" + weberrors "github.com/ssb-ngi-pointer/go-ssb-room/web/errors" + "go.mindeco.de/http/render" +) + +type noticeHandler struct { + r *render.Renderer + + db admindb.NoticesService +} + +func (nh noticeHandler) edit(rw http.ResponseWriter, req *http.Request) (interface{}, error) { + id, err := strconv.ParseInt(req.URL.Query().Get("id"), 10, 64) + if err != nil { + err = weberrors.ErrBadRequest{Where: "ID", Details: err} + return nil, err + } + + n, err := nh.db.GetByID(req.Context(), id) + if err != nil { + if errors.Is(err, admindb.ErrNotFound) { + http.Redirect(rw, req, redirectTo, http.StatusFound) + return nil, ErrRedirected + } + return nil, err + } + + // https://github.com/russross/blackfriday/issues/575 + fixedContent := strings.Replace(n.Content, "\r\n", "\n", -1) + + contentBytes := []byte(fixedContent) + preview := blackfriday.Run(contentBytes) + + return map[string]interface{}{ + "Notice": n, + "ContentPreview": template.HTML(preview), + // "Debug": string(preview), + // "DebugHex": hex.Dump(contentBytes), + csrf.TemplateTag: csrf.TemplateField(req), + }, nil +} + +func (nh noticeHandler) save(rw http.ResponseWriter, req *http.Request) { + err := req.ParseForm() + if err != nil { + err = weberrors.ErrBadRequest{Where: "form data", Details: err} + nh.r.Error(rw, req, http.StatusInternalServerError, err) + return + } + + redirect := req.FormValue("redirect") + if redirect == "" { + redirect = "/" + } + + var n admindb.Notice + n.ID, err = strconv.ParseInt(req.FormValue("id"), 10, 64) + if err != nil { + err = weberrors.ErrBadRequest{Where: "id", Details: err} + nh.r.Error(rw, req, http.StatusInternalServerError, err) + return + } + + n.Title = req.FormValue("title") + + n.Content = req.FormValue("content") + // https://github.com/russross/blackfriday/issues/575 + n.Content = strings.Replace(n.Content, "\r\n", "\n", -1) + + err = nh.db.Save(req.Context(), &n) + if err != nil { + nh.r.Error(rw, req, http.StatusInternalServerError, err) + return + } + + // TODO: update langauge + + http.Redirect(rw, req, redirect, http.StatusTemporaryRedirect) +} diff --git a/web/handlers/http.go b/web/handlers/http.go index 641aae3..5e55ab7 100644 --- a/web/handlers/http.go +++ b/web/handlers/http.go @@ -25,6 +25,13 @@ import ( "github.com/ssb-ngi-pointer/go-ssb-room/web/router" ) +var HTMLTempaltes = []string{ + "landing/index.tmpl", + "landing/about.tmpl", + "notice.tmpl", + "error.tmpl", +} + // New initializes the whole web stack for rooms, with all the sub-modules and routing. func New( logger logging.Interface, @@ -33,6 +40,7 @@ func New( as admindb.AuthWithSSBService, fs admindb.AuthFallbackService, al admindb.AllowListService, + ns admindb.NoticesService, ) (http.Handler, error) { m := router.CompleteApp() @@ -47,11 +55,7 @@ func New( render.SetLogger(logger), render.BaseTemplates("base.tmpl", "menu.tmpl"), render.AddTemplates(concatTemplates( - []string{ - "landing/index.tmpl", - "landing/about.tmpl", - "error.tmpl", - }, + HTMLTempaltes, news.HTMLTemplates, roomsAuth.HTMLTemplates, admin.HTMLTemplates, @@ -189,12 +193,16 @@ func New( news.Handler(m, r) roomsAuth.Handler(m, r, a) - adminHandler := a.Authenticate(admin.Handler(r, roomState, al)) + adminHandler := a.Authenticate(admin.Handler(r, roomState, al, ns)) mainMux.Handle("/admin/", adminHandler) m.Get(router.CompleteIndex).Handler(r.StaticHTML("landing/index.tmpl")) m.Get(router.CompleteAbout).Handler(r.StaticHTML("landing/about.tmpl")) + var nr noticeRenderer + nr.notices = ns + m.Get(router.CompleteNotice).Handler(r.HTML("notice.tmpl", nr.render)) + m.PathPrefix("/assets/").Handler(http.StripPrefix("/assets/", http.FileServer(web.Assets))) m.NotFoundHandler = r.HTML("error.tmpl", func(rw http.ResponseWriter, req *http.Request) (interface{}, error) { diff --git a/web/handlers/notices.go b/web/handlers/notices.go new file mode 100644 index 0000000..85f8264 --- /dev/null +++ b/web/handlers/notices.go @@ -0,0 +1,43 @@ +package handlers + +import ( + "html/template" + "net/http" + "strconv" + + "github.com/russross/blackfriday/v2" + + "github.com/ssb-ngi-pointer/go-ssb-room/admindb" + "github.com/ssb-ngi-pointer/go-ssb-room/web/errors" +) + +type noticeRenderer struct { + notices admindb.NoticesService +} + +type noticeData struct { + ID int64 + Title, Language string + Content template.HTML +} + +func (pr noticeRenderer) render(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) + if err != nil { + return nil, err + } + + markdown := blackfriday.Run([]byte(notice.Content), blackfriday.WithNoExtensions()) + + return noticeData{ + ID: noticeID, + Title: notice.Title, + Content: template.HTML(markdown), + Language: notice.Language, + }, nil +} diff --git a/web/i18n/defaults/active.en.toml b/web/i18n/defaults/active.en.toml index 25a6922..bdad47c 100644 --- a/web/i18n/defaults/active.en.toml +++ b/web/i18n/defaults/active.en.toml @@ -1,4 +1,6 @@ Confirm = "Confirm" +Save = "Save" +Preview = "Preview" PageNotFound = "The requested page was not found." LandingTitle = "ohai my room" @@ -33,6 +35,8 @@ 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/admin.go b/web/router/admin.go index 8590533..6860fb2 100644 --- a/web/router/admin.go +++ b/web/router/admin.go @@ -13,6 +13,9 @@ const ( AdminAllowListAdd = "admin:allow-list:add" AdminAllowListRemoveConfirm = "admin:allow-list:remove:confirm" AdminAllowListRemove = "admin:allow-list:remove" + + AdminNoticeEdit = "admin:notice:edit" + AdminNoticeSave = "admin:notice:save" ) // Admin constructs a mux.Router containing the routes for the admin dashboard and settings pages @@ -29,5 +32,8 @@ func Admin(m *mux.Router) *mux.Router { m.Path("/members/remove/confirm").Methods("GET").Name(AdminAllowListRemoveConfirm) m.Path("/members/remove").Methods("POST").Name(AdminAllowListRemove) + m.Path("/notice/edit").Methods("GET").Name(AdminNoticeEdit) + m.Path("/notice/save").Methods("POST").Name(AdminNoticeSave) + return m } diff --git a/web/router/complete.go b/web/router/complete.go index d22b25f..1cc3168 100644 --- a/web/router/complete.go +++ b/web/router/complete.go @@ -8,8 +8,9 @@ import ( // constant names for the named routes const ( - CompleteIndex = "complete:index" - CompleteAbout = "complete:about" + CompleteIndex = "complete:index" + CompleteAbout = "complete:about" + CompleteNotice = "complete:notice" ) // CompleteApp constructs a mux.Router containing the routes for batch Complete html frontend @@ -22,6 +23,7 @@ func CompleteApp() *mux.Router { m.Path("/").Methods("GET").Name(CompleteIndex) m.Path("/about").Methods("GET").Name(CompleteAbout) + m.Path("/notice").Methods("GET").Name(CompleteNotice) return m } diff --git a/web/templates/admin/notice-edit.tmpl b/web/templates/admin/notice-edit.tmpl new file mode 100644 index 0000000..a69d780 --- /dev/null +++ b/web/templates/admin/notice-edit.tmpl @@ -0,0 +1,56 @@ +{{ define "title" }}{{i18n "NoticeEditTitle"}}{{ end }} +{{ define "content" }} + +
+
+ {{ .csrfField }} + + +
+ + + +

TODO: language dropdown

+
+ +
+
+
+

{{i18n "Preview"}}

+ {{.ContentPreview}} +
+ + + +{{end}} \ No newline at end of file diff --git a/web/templates/landing/index.tmpl b/web/templates/landing/index.tmpl index 6ab10d5..93e3958 100644 --- a/web/templates/landing/index.tmpl +++ b/web/templates/landing/index.tmpl @@ -40,5 +40,4 @@ suspendisse in est ante in. Imperdiet nulla malesuada pellentesque elit. Ut faucibus pulvinar elementum integer enim neque. Consequat semper viverra nam libero justo laoreet.

- {{end}} \ No newline at end of file diff --git a/web/templates/notice.tmpl b/web/templates/notice.tmpl new file mode 100644 index 0000000..7e11bb7 --- /dev/null +++ b/web/templates/notice.tmpl @@ -0,0 +1,18 @@ +{{ define "title" }}{{.Title}}{{ end }} +{{ define "content" }} +
+

{{.Title}}

+ + {{.Content}} + +
+ {{.Language}} + + {{if is_logged_in}} + {{i18n "NoticeEditTitle"}} + {{end}} +
+{{end}} \ No newline at end of file