@ -18,58 +18,134 @@ limitations under the License.
package main
import (
"fmt"
"html/template"
"net/http"
"path/filepath"
"strings"
"time"
"git.cacert.org/cacert-boardvoting/internal/models"
"github.com/Masterminds/sprig/v3"
"github.com/gorilla/csrf"
)
const motionsPerPage = 10
"git.cacert.org/cacert-boardvoting/internal/models"
)
func ( app * application ) motionList ( w http . ResponseWriter , r * http . Request ) {
if r . URL . Path != "/motions/" {
app . notFound ( w )
func newTemplateCache ( ) ( map [ string ] * template . Template , error ) {
cache := map [ string ] * template . Template { }
return
pages , err := filepath . Glob ( "./ui/html/pages/*.html" )
if err != nil {
return nil , fmt . Errorf ( "could not find page templates: %w" , err )
}
var err error
for _ , page := range pages {
name := filepath . Base ( page )
files := [ ] string {
"./ui/html/base.html" ,
"./ui/html/partials/motion_actions.html" ,
"./ui/html/partials/motion_display.html" ,
"./ui/html/partials/motion_status_class.html" ,
"./ui/html/partials/nav.html" ,
"./ui/html/partials/pagination.html" ,
page ,
}
ctx := r . Context ( )
funcMaps := sprig . FuncMap ( )
funcMaps [ "nl2br" ] = func ( text string ) template . HTML {
// #nosec G203 input is sanitized
return template . HTML ( strings . ReplaceAll ( template . HTMLEscapeString ( text ) , "\n" , "<br>" ) )
}
funcMaps [ "canManageUsers" ] = func ( * models . Voter ) bool {
return false
}
funcMaps [ csrf . TemplateTag ] = csrf . TemplateField
listOptions := & models . MotionListOptions { Limit : motionsPerPage }
ts , err := template . New ( "" ) . Funcs ( funcMaps ) . ParseFiles ( files ... )
if err != nil {
return nil , fmt . Errorf ( "could not parse templates: %w" , err )
}
const queryParamBefore = "before"
const queryParamAfter = "after"
cache [ name ] = ts
}
if r . URL . Query ( ) . Has ( queryParamAfter ) {
var after time . Time
return cache , nil
}
err := after . UnmarshalText ( [ ] byte ( r . URL . Query ( ) . Get ( queryParamAfter ) ) )
if err != nil {
app . clientError ( w , http . StatusBadRequest )
func ( app * application ) render ( w http . ResponseWriter , status int , page string , data interface { } ) {
ts , ok := app . templateCache [ page ]
if ! ok {
app . serverError ( w , fmt . Errorf ( "the template %s does not exist" , page ) )
return
return
}
w . WriteHeader ( status )
err := ts . ExecuteTemplate ( w , "base" , data )
if err != nil {
app . serverError ( w , err )
}
}
type motionListTemplateData struct {
Voter * models . Voter
Flashes [ ] string
Params struct {
Flags struct {
Unvoted bool
}
}
PrevPage , NextPage string
Motions [ ] * models . MotionForDisplay
}
listOptions . After = & after
} else if r . URL . Query ( ) . Has ( queryParamBefore ) {
var before time . Time
func ( m * motionListTemplateData ) setPaginationParameters ( first , last * time . Time ) error {
motions := m . Motions
err := before . UnmarshalText ( [ ] byte ( r . URL . Query ( ) . Get ( queryParamBefore ) ) )
if len ( motions ) > 0 && first . Before ( motions [ len ( motions ) - 1 ] . Proposed ) {
marshalled , err := motions [ len ( motions ) - 1 ] . Proposed . MarshalText ( )
if err != nil {
app . clientError ( w , http . StatusBadRequest )
return fmt . Errorf ( "could not serialize timestamp: %w" , err )
}
m . NextPage = string ( marshalled )
}
return
if len ( motions ) > 0 && last . After ( motions [ 0 ] . Proposed ) {
marshalled , err := motions [ 0 ] . Proposed . MarshalText ( )
if err != nil {
return fmt . Errorf ( "could not serialize timestamp: %w" , err )
}
listOptions . Before = & before
m. PrevPage = string ( marshalled )
}
return nil
}
func ( app * application ) motionList ( w http . ResponseWriter , r * http . Request ) {
if r . URL . Path != "/motions/" {
app . notFound ( w )
return
}
var (
listOptions * models . MotionListOptions
err error
)
listOptions , err = calculateMotionListOptions ( r )
if err != nil {
app . clientError ( w , http . StatusBadRequest )
return
}
ctx := r . Context ( )
motions , err := app . motions . GetMotions ( ctx , listOptions )
if err != nil {
app . serverError ( w , err )
@ -84,72 +160,48 @@ func (app *application) motionList(w http.ResponseWriter, r *http.Request) {
return
}
files := [ ] string {
"./ui/html/base.html" ,
"./ui/html/partials/nav.html" ,
"./ui/html/partials/pagination.html" ,
"./ui/html/partials/motion_actions.html" ,
"./ui/html/partials/motion_display.html" ,
"./ui/html/partials/motion_status_class.html" ,
"./ui/html/pages/motions.html" ,
}
templateData := & motionListTemplateData { Motions : motions }
funcMaps := sprig . FuncMap ( )
funcMaps [ "nl2br" ] = func ( text string ) template . HTML {
// #nosec G203 input is sanitized
return template . HTML ( strings . ReplaceAll ( template . HTMLEscapeString ( text ) , "\n" , "<br>" ) )
}
funcMaps [ "canMangageUsers" ] = func ( * models . Voter ) bool {
return false
}
funcMaps [ csrf . TemplateTag ] = func ( ) template . HTML {
return csrf . TemplateField ( r )
}
ts , err := template . New ( "" ) . Funcs ( funcMaps ) . ParseFiles ( files ... )
err = templateData . setPaginationParameters ( first , last )
if err != nil {
app . serverError ( w , err )
return
}
templateCtx := struct {
Voter * models . Voter
Flashes [ ] string
Params struct {
Flags struct {
Unvoted bool
}
}
PrevPage , NextPage string
Motions [ ] * models . MotionForDisplay
} { Motions : motions }
app . render ( w , http . StatusOK , "motions.html" , & templateData )
}
if len ( motions ) > 0 && first . Before ( motions [ len ( motions ) - 1 ] . Proposed ) {
marshalled , err := motions [ len ( motions ) - 1 ] . Proposed . MarshalText ( )
if err != nil {
app . serverError ( w , err )
func calculateMotionListOptions ( r * http . Request ) ( * models . MotionListOptions , error ) {
const (
queryParamBefore = "before"
queryParamAfter = "after"
motionsPerPage = 10
)
return
}
listOptions := & models . MotionListOptions { Limit : motionsPerPage }
templateCtx . NextPage = string ( marshalled )
}
if len ( motions ) > 0 && last . After ( motions [ 0 ] . Proposed ) {
marshalled, err := motions [ 0 ] . Proposed . MarshalText ( )
if r . URL . Query ( ) . Has ( queryParamAfter ) {
var after time . Time
err := after . UnmarshalText ( [ ] byte ( r . URL . Query ( ) . Get ( queryParamAfter ) ) )
if err != nil {
app . serverError ( w , err )
return nil , fmt . Errorf ( "could not unmarshal timestamp: %w" , err )
}
listOptions . After = & after
} else if r . URL . Query ( ) . Has ( queryParamBefore ) {
var before time . Time
return
err := before . UnmarshalText ( [ ] byte ( r . URL . Query ( ) . Get ( queryParamBefore ) ) )
if err != nil {
return nil , fmt . Errorf ( "could not unmarshal timestamp: %w" , err )
}
templateCtx . PrevPage = string ( marshalled )
listOptions. Before = & before
}
err = ts . ExecuteTemplate ( w , "base" , templateCtx )
if err != nil {
app . serverError ( w , err )
}
return listOptions , nil
}
func ( app * application ) home ( w http . ResponseWriter , r * http . Request ) {