(Pinned)Notices

Notices are pages that admins can fill with their content to describe
and customize the room.

Pinned notices are common notices that each room has. Like a description
and privacy policy.

* update models
* simple crud test for basic notices
* edit and save notices as admin
This commit is contained in:
Henry 2021-02-22 17:55:12 +01:00
parent 3c921cef80
commit e5a07fd8bc
25 changed files with 2843 additions and 23 deletions

View File

@ -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

View File

@ -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)

276
admindb/mockdb/pages.go Normal file
View File

@ -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)

View File

@ -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;

View File

@ -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",
}

View File

@ -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 &noticeR{}
}
// 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 = &noticeR{}
}
args = append(args, object.ID)
} else {
Outer:
for _, obj := range slice {
if obj.R == nil {
obj.R = &noticeR{}
}
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 = &noticeR{
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
}

View File

@ -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 = &noticeR{}
}
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 = &noticeR{}
}
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 = &noticeR{
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
}

View File

@ -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

102
admindb/sqlite/notices.go Normal file
View File

@ -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
}

View File

@ -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())
})
}

View File

@ -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
}

View File

@ -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)

2
go.mod
View File

@ -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

6
go.sum
View File

@ -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=

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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) {

43
web/handlers/notices.go Normal file
View File

@ -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
}

View File

@ -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"

View File

@ -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
}

View File

@ -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
}

View File

@ -0,0 +1,56 @@
{{ define "title" }}{{i18n "NoticeEditTitle"}}{{ end }}
{{ define "content" }}
<div id="page-header">
<h1 id="welcome" class="text-lg">{{i18n "NoticeEditTitle"}}: <small>{{.Notice.Title}}<small></h1>
</div>
<div class="">
<form method="POST" action={{urlTo "admin:notice:save" }} class="flex flex-row items-end">
{{ .csrfField }}
<input
type="hidden"
name="id"
value="{{.Notice.ID}}">
<input
type="hidden"
name="redirect"
value="{{urlTo "admin:notice:edit" "id" .Notice.ID}}">
<div class="w-96 grid grid-cols-2 gap-x-4 gap-y-1 mr-4">
<label>Title</label>
<input
type="text"
name="title"
value="{{.Notice.Title}}"
class="shadow rounded border border-transparent h-8 p-1 focus:outline-none focus:ring-2 focus:ring-pink-400 focus:border-transparent">
<textarea
name="content"
id="notice-content"
rows="10"
cols="20"
class="resize border rounded-md"
>{{.Notice.Content}}</textarea>
<p class="text:red">TODO: language dropdown</p>
</div>
<button
type="submit"
class="shadow rounded px-4 h-8 text-gray-100 bg-pink-600 hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-pink-600 focus:ring-opacity-50"
>{{i18n "Save"}}</button>
</form>
</div>
<div>
<h1>{{i18n "Preview"}}</h1>
{{.ContentPreview}}
</div>
<!--
<div>
<h1>Debug</h1>
<pre>{{.Debug}}</pre>
</div>
<div>
<h1>Debug Input</h1>
<pre>{{.DebugHex}}</pre>
</div>
-->
{{end}}

View File

@ -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.</p>
</div>
</div> <!-- /row -->
{{end}}

18
web/templates/notice.tmpl Normal file
View File

@ -0,0 +1,18 @@
{{ define "title" }}{{.Title}}{{ end }}
{{ define "content" }}
<div class="container mx-auto">
<h1 class="text-lg">{{.Title}}</h1>
{{.Content}}
<br>
<span>{{.Language}}</span>
{{if is_logged_in}}
<a
href="{{urlTo "admin:notice:edit" "id" .ID}}"
class="shadow rounded px-4 h-8 text-gray-100 bg-pink-600 hover:bg-pink-700 focus:outline-none focus:ring-2 focus:ring-pink-600 focus:ring-opacity-50"
>{{i18n "NoticeEditTitle"}}</a>
{{end}}
</div>
{{end}}