# HG changeset patch
# User Axel Prel <axel.prel@xcg-consulting.fr>
# Date 1718274359 -7200
#      Thu Jun 13 12:25:59 2024 +0200
# Node ID 1cc6783208a506f3ec6af8e1ccb51fccf7c586e3
# Parent  2f2261ad2e7bc910b49e03ab5713de767ea27818
# EXP-Topic godox
WIP add godox linter

warns about TODOs and FIXMEs

diff --git a/.golangci.yml b/.golangci.yml
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -57,7 +57,7 @@
     - gocritic  # Provides diagnostics that check for bugs, performance and style issues. style, metalinter		v1.12.0
     # - gocyclo  # Computes and checks the cyclomatic complexity of functions	complexity		v1.0.0
     - godot  # Check if comments end in a period	style, comment	✔	v1.25.0
-    # - godox  # Tool for detection of FIXME, TODO and other comment keywords	style, comment		v1.19.0
+    - godox  # Tool for detection of FIXME, TODO and other comment keywords	style, comment		v1.19.0
     - gofmt  # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification	format	✔	v1.0.0
     - gofumpt  # Gofumpt checks whether code was gofumpt-ed.	format	✔	v1.28.0
     - goheader  # Checks is file header matches to pattern	style		v1.28.0
diff --git a/auth/auth.go b/auth/auth.go
--- a/auth/auth.go
+++ b/auth/auth.go
@@ -93,6 +93,10 @@
 	cacheMissCollector prometheus.Counter
 }
 
+func (auth *Auth) Expiration() time.Duration {
+	return auth.expiration
+}
+
 // NewAuth creates a Auth instance.
 func NewAuth(ctx context.Context, options TokenOptions, db *sqlx.DB, log zerolog.Logger) *Auth {
 	if options.CacheMaxSize == 0 {
diff --git a/restapi/auth_middleware.go b/restapi/auth_middleware.go
--- a/restapi/auth_middleware.go
+++ b/restapi/auth_middleware.go
@@ -49,8 +49,7 @@
 					log.Err(err).Msg("failed to read the rednerd-auth cookie")
 				} else {
 					principal = pr
-					// TODO get the duration from the token options
-					if time.Until(time.Unix(principal.StandardClaims.ExpiresAt, 0)) < time.Minute*30 {
+					if time.Until(time.Unix(principal.StandardClaims.ExpiresAt, 0)) < auth.Expiration()*4/5 {
 						_, cookie, err := auth.MakeToken(*principal, true)
 						if err != nil {
 							log.Err(err).Msg("failed to create the rednerd-auth cookie")
diff --git a/restapi/configure_rednerd.go b/restapi/configure_rednerd.go
--- a/restapi/configure_rednerd.go
+++ b/restapi/configure_rednerd.go
@@ -120,7 +120,7 @@
 	api.TemplateManagementTemplateVarlistHandler = handlers.NewTemplateVarlistHandler(
 		config.DB, config.TemplateRenderingEngineRegistry)
 
-	api.RenderingRenderHandler = handlers.NewRenderHandler(renderer)
+	api.RenderingRenderHandler = handlers.NewRenderHandler(renderer, config.DB)
 
 	api.PreServerShutdown = func() {}
 
diff --git a/restapi/handlers/account-template-list.go b/restapi/handlers/account-template-list.go
--- a/restapi/handlers/account-template-list.go
+++ b/restapi/handlers/account-template-list.go
@@ -80,7 +80,8 @@
 			NewAccountTemplateAddUnauthorized()
 	}
 
-	templates, err := h.handle(params.HTTPRequest.Context(), params.Account)
+	templates, err := h.handle(ctx, params.Account)
+
 	if err != nil {
 		return InternalError(err, log, false)
 	}
diff --git a/restapi/handlers/auth-refresh_token.go b/restapi/handlers/auth-refresh_token.go
--- a/restapi/handlers/auth-refresh_token.go
+++ b/restapi/handlers/auth-refresh_token.go
@@ -1,8 +1,14 @@
 package handlers
 
 import (
+	"context"
+	"errors"
+
+	"github.com/Masterminds/squirrel"
 	"github.com/go-openapi/runtime/middleware"
 	"github.com/jmoiron/sqlx"
+	"github.com/rs/zerolog"
+	"orus.io/orus-io/go-orusapi/database"
 
 	"orus.io/orus-io/rednerd/auth"
 	"orus.io/orus-io/rednerd/models"
@@ -20,9 +26,37 @@
 	auth *auth.Auth
 }
 
-func (h *AuthRefreshToken) handle(principal *models.Principal) (string, error) {
-	// TODO Query the db and check it the profile is still valid (ie has an api key set),
-	// is still connected to the account, and the account is still active
+func (h *AuthRefreshToken) handle(
+	ctx context.Context,
+	log zerolog.Logger,
+	principal *models.Principal,
+) (string, error) {
+	db := database.NewSQLHelper(ctx, h.db, log)
+	q := squirrel.Select(models.UserDBColumns...).
+		From(models.UserDBTable).
+		Where(squirrel.Eq{models.UserDBUsernameColumn: principal.Subject})
+	var users []models.UserDB
+	if err := db.Select(&users, q); err != nil {
+		return "", err
+	}
+	if len(users) == 0 {
+		return "", errors.New("user does not exist in the database")
+	}
+	q = squirrel.Select(models.AccountDBColumns...).
+		From(models.AccountDBTable).
+		Where(squirrel.Eq{models.AccountDBNameColumn: principal.Subject})
+	var accounts []models.AccountDB
+	if err := db.Select(&accounts, q); err != nil {
+		return "", err
+	}
+	if len(users) == 0 {
+		return "", errors.New("account does not exist in the database")
+	}
+	account := accounts[0]
+	if !account.IsUser {
+		return "", errors.New("the account but be a user")
+	}
+
 	token, _, err := h.auth.MakeToken(*principal, false)
 
 	return token, err
@@ -32,7 +66,9 @@
 func (h *AuthRefreshToken) Handle(
 	params op_auth.RefreshTokenParams, principal *models.Principal,
 ) middleware.Responder {
-	token, err := h.handle(principal)
+	ctx := params.HTTPRequest.Context()
+	log := zerolog.Ctx(ctx)
+	token, err := h.handle(ctx, *log, principal)
 	if err != nil {
 		return op_auth.NewRefreshTokenDefault(500).WithPayload(models.FromError(err))
 	}
diff --git a/restapi/handlers/render.go b/restapi/handlers/render.go
--- a/restapi/handlers/render.go
+++ b/restapi/handlers/render.go
@@ -3,9 +3,13 @@
 import (
 	"context"
 	"errors"
+	"fmt"
 
+	"github.com/Masterminds/squirrel"
 	"github.com/go-openapi/runtime/middleware"
+	"github.com/jmoiron/sqlx"
 	"github.com/rs/zerolog"
+	"orus.io/orus-io/go-orusapi/database"
 
 	redner "orus.io/orus-io/rednerd/lib"
 	"orus.io/orus-io/rednerd/models"
@@ -13,22 +17,33 @@
 	"orus.io/orus-io/rednerd/utils"
 )
 
-// ErrNoTemplateNorDocuments is returned if the render request has no template
-// nor a document.
-var ErrNoTemplateNorDocument = errors.New("no template nor document. Must have one")
+var (
+	// ErrNoTemplateNorDocuments is returned if the render request has no template
+	// nor a document.
+	ErrNoTemplateNorDocument = errors.New(
+		"no template nor document. Must have one",
+	)
+	// ErrBothTemplateAndDocument is returned if the render request has a template
+	// and a document.
+	ErrBothTemplateAndDocument = errors.New(
+		"render_request cannot accept both a Document and a Template",
+	)
+)
 
 // NewRenderHandler creates a RenderHandler.
-func NewRenderHandler(renderer *redner.Renderer) *RenderHandler {
-	return &RenderHandler{renderer}
+func NewRenderHandler(renderer *redner.Renderer, db *sqlx.DB) *RenderHandler {
+	return &RenderHandler{renderer, db}
 }
 
 // RenderHandler handles POST /api/v1/render.
 type RenderHandler struct {
 	renderer *redner.Renderer
+	db       *sqlx.DB
 }
 
 func (h *RenderHandler) handle(
 	ctx context.Context,
+	log zerolog.Logger,
 	principal *models.Principal,
 	template *models.Template,
 	data models.Dataset,
@@ -36,8 +51,26 @@
 	metadata models.Metadata,
 	toType string,
 ) ([]*models.Document, error) {
-	if template == nil && document == nil {
+	switch {
+	case template == nil && document == nil:
 		return nil, utils.HTTPBadRequest(ErrNoTemplateNorDocument)
+	case template != nil && document != nil:
+		return nil, utils.HTTPBadRequest(ErrBothTemplateAndDocument)
+	default:
+	}
+
+	if template != nil && template.Name != "" {
+		var tDB models.TemplateDB
+		db := database.NewSQLHelper(ctx, h.db, log)
+		q := squirrel.Select(models.TemplateDBColumns...).
+			From(models.TemplateDBTable).
+			Where(squirrel.Eq{models.TemplateDBNameColumn: template.Name})
+		if err := db.Get(&tDB, q); err != nil {
+			return nil, err
+		}
+		if tDB.Account != principal.Subject {
+			return nil, errors.New("user can render on it's own templates only")
+		}
 	}
 
 	documents, err := h.renderer.Render(
@@ -55,18 +88,23 @@
 
 // Handle the incoming request.
 func (h *RenderHandler) Handle(params op.RenderParams, principal *models.Principal) middleware.Responder {
-	accept := params.Request.Accept
-	// TODO if accept is empty, get a 'accept' from the http header
-
-	// TODO checks if the user has render access to the template
-
+	req := params.Request
+	accept := req.Accept
+	if accept == "" {
+		acceptHeader := params.HTTPRequest.Header.Get("Accept")
+		fmt.Printf("hey %s", acceptHeader)
+		accept = models.Accept(acceptHeader)
+	}
+	ctx := params.HTTPRequest.Context()
+	log := zerolog.Ctx(ctx)
 	docs, err := h.handle(
-		params.HTTPRequest.Context(),
+		ctx,
+		*log,
 		principal,
-		params.Request.Template,
-		params.Request.Data,
-		params.Request.Document,
-		params.Request.Metadata,
+		req.Template,
+		req.Data,
+		req.Document,
+		req.Metadata,
 		string(accept),
 	)
 	if err != nil {
@@ -80,12 +118,13 @@
 			return r
 		}
 
-		if params.HTTPRequest.Context().Err() != nil {
+		if ctx.Err() != nil {
 			return op.NewRenderDefault(499)
 		}
 
-		return InternalError(err, zerolog.Ctx(params.HTTPRequest.Context()), false)
+		return InternalError(err, log, false)
 	}
+	fmt.Print("heyyyyy")
 
 	return op.NewRenderOK().WithPayload(docs)
 }
diff --git a/tests/render_test.go b/restapi/handlers/render_test.go
rename from tests/render_test.go
rename to restapi/handlers/render_test.go
--- a/tests/render_test.go
+++ b/restapi/handlers/render_test.go
@@ -1,7 +1,8 @@
-package tests
+package handlers_test
 
 import (
 	"encoding/base64"
+	"fmt"
 	"testing"
 	"time"
 
@@ -18,13 +19,20 @@
 	defer cancel()
 
 	tester := apitester.NewAPITester(ctx, t, nil)
-	defer tester.Close()
-
-	defer tester.Logout()
+	t.Cleanup(func() {
+		tester.Close()
+		tester.Logout()
+	})
 	tester.Login("admin", "admin")
-
 	tester.CreateUser("janedoe", "janedoe")
+	tester.Logout()
 	tester.Login("janedoe", "janedoe")
+	tmpl := models.Template{
+		Language:   "mustache",
+		Produces:   "text/mjml",
+		BodyFormat: "text",
+		Body:       "<mjml> <mj-body> <mj-section> <mj-column> <mj-text>hello {{name}}</mj-text> </mj-column> </mj-section> </mj-body> </mjml>",
+	}
 
 	t.Run("MJML-Template-to-html", func(t *testing.T) {
 		defer tester.SetT(t)()
@@ -33,20 +41,16 @@
 
 		tester.Render(
 			&models.RenderRequest{
-				Accept: "text/html",
-				Template: &models.Template{
-					Language:   "mustache",
-					Produces:   "text/mjml",
-					BodyFormat: "text",
-					Body:       "<mjml> <mj-body> <mj-section> <mj-column> <mj-text>hello {{name}}</mj-text> </mj-column> </mj-section> </mj-body> </mjml>", //nolint:lll
-				},
+				Accept:   "text/html",
+				Template: &tmpl,
 				Data: models.Dataset{
 					apitester.JSONObj{"name": "Nath"},
 				},
 			},
 			&result,
 		)
-		require.Equal(t, 1, len(result))
+
+		require.Len(t, result, 1)
 
 		if body, err := base64.StdEncoding.DecodeString(result[0].Body); assert.NoError(t, err) {
 			assert.Contains(t, string(body), "hello Nath")
@@ -63,23 +67,18 @@
 			),
 		)
 
-		assert.Equal(t, 1, len(logs))
+		assert.Len(t, logs, 1)
 	})
 
 	t.Run("MJML-Saved-Template-to-html", func(t *testing.T) {
+		tmpl.Name = "t1"
 		defer tester.SetT(t)()
 
 		var response models.Template
 
 		tester.CreateTemplate(
 			"janedoe",
-			&models.Template{
-				Name:       "t1",
-				Language:   "mustache",
-				Produces:   "text/mjml",
-				BodyFormat: "text",
-				Body:       "<mjml> <mj-body> <mj-section> <mj-column> <mj-text>hello {{name}}</mj-text> </mj-column> </mj-section> </mj-body> </mjml>", //nolint:lll
-			},
+			&tmpl,
 			&response)
 
 		var result []*models.Document
@@ -96,7 +95,7 @@
 				},
 			},
 			&result)
-		require.Equal(t, 1, len(result))
+		require.Len(t, result, 1)
 
 		if body, err := base64.StdEncoding.DecodeString(result[0].Body); assert.NoError(t, err) {
 			assert.Contains(t, string(body), "hello Nath")
@@ -127,14 +126,14 @@
 			},
 			&result,
 		)
-		require.Equal(t, 1, len(result))
+		require.Len(t, result, 1)
 
 		if body, err := base64.StdEncoding.DecodeString(result[0].Body); assert.NoError(t, err) {
 			assert.Contains(t, string(body), "hello world")
 		}
 
 		t.Log(result[0].RenderErrors)
-		require.Equal(t, 6, len(result[0].RenderErrors))
+		require.Len(t, result[0].RenderErrors, 6)
 		assert.Equal(t, "MJML", result[0].RenderErrors[0].Engine)
 		assert.Equal(t, "MJML", result[0].RenderErrors[1].Engine)
 	})
@@ -165,13 +164,12 @@
 			},
 			&result,
 		)
-		require.Equal(t, 1, len(result))
+		require.Len(t, result, 1)
 
 		if body, err := base64.StdEncoding.DecodeString(result[0].Body); assert.NoError(t, err) {
 			assert.Contains(t, string(body), "hello world")
 		}
-
-		require.Equal(t, 0, len(result[0].RenderErrors))
+		require.Empty(t, result[0].RenderErrors)
 	})
 
 	t.Run("MJML-specific-test", func(t *testing.T) {
@@ -190,12 +188,33 @@
 			},
 			&result,
 		)
-		require.Equal(t, 1, len(result))
+		require.Len(t, result, 1)
 
 		if body, err := base64.StdEncoding.DecodeString(result[0].Body); assert.NoError(t, err) {
 			assert.Contains(t, string(body), "<strong>Client</strong> : client")
 		}
 	})
+	t.Run("MJML-no-accept", func(t *testing.T) {
+		defer tester.SetT(t)()
+		tester.SetHeader("Accept", "text/html")
+		var result []*models.Document
+		tester.Render(
+			&models.RenderRequest{
+				Accept: "",
+				Document: &models.Document{
+					Type:       "text/mjml",
+					BodyFormat: "text",
+					Body:       mjmlSource,
+				},
+			},
+			&result,
+		)
+		fmt.Print("I was HEERR")
+		require.Len(t, result, 1)
+		if body, err := base64.StdEncoding.DecodeString(result[0].Body); assert.NoError(t, err) {
+			assert.Contains(t, string(body), "<strong>Client</strong> : client")
+		}
+	})
 }
 
 const mjmlSource = `
diff --git a/restapi/panic_middleware.go b/restapi/panic_middleware.go
--- a/restapi/panic_middleware.go
+++ b/restapi/panic_middleware.go
@@ -1,7 +1,9 @@
 package restapi
 
 import (
+	"bytes"
 	"context"
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
@@ -44,6 +46,12 @@
 					exc = exc.Str("plainstack", string(b))
 				} else {
 					exc = exc.RawJSON("stacktrace", b)
+					var prettyJSON bytes.Buffer
+					err := json.Indent(&prettyJSON, b, "", "\t")
+					if err != nil {
+						log.Err(err).Msg("")
+					}
+					log.Info().Msgf("pretty %s", prettyJSON.String())
 				}
 
 				log.Error().Dict("exception", exc).Msg("Caught a panic")