changeset 5327:c008e13fa1d1 extented-report

Merged.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Sun, 30 May 2021 13:18:50 +0200
parents 96ceb150ea46 (current diff) 313bf3f3a8b1 (diff)
children bc8c082487b2
files pkg/controllers/routes.go
diffstat 6 files changed, 205 insertions(+), 9 deletions(-) [+]
line wrap: on
line diff
--- a/pkg/controllers/report.go	Sun May 30 13:18:10 2021 +0200
+++ b/pkg/controllers/report.go	Sun May 30 13:18:50 2021 +0200
@@ -19,15 +19,86 @@
 	"net/http"
 	"os"
 	"path/filepath"
+	"sort"
+	"strings"
 
 	"gemma.intevation.de/gemma/pkg/config"
 	"gemma.intevation.de/gemma/pkg/middleware"
 	"gemma.intevation.de/gemma/pkg/xlsx"
 
+	mw "gemma.intevation.de/gemma/pkg/middleware"
+
 	"github.com/360EntSecGroup-Skylar/excelize/v2"
 	"github.com/gorilla/mux"
 )
 
+func listReports(req *http.Request) (jr mw.JSONResult, err error) {
+	path := config.ReportPath()
+	if path == "" {
+		err = mw.JSONError{
+			Code:    http.StatusNotFound,
+			Message: http.StatusText(http.StatusNotFound),
+		}
+		return
+	}
+
+	// This would be easier with Go 1.16+.
+
+	dir, err := os.Open(path)
+	if err != nil {
+		log.Printf("error: %v\n", err)
+		err = mw.JSONError{
+			Code:    http.StatusInternalServerError,
+			Message: "Listing report templates failed.",
+		}
+		return
+	}
+	defer dir.Close()
+	files, err := dir.Readdirnames(-1)
+	if err != nil {
+		log.Printf("error: %v\n", err)
+		err = mw.JSONError{
+			Code:    http.StatusInternalServerError,
+			Message: "Listing report templates failed.",
+		}
+		return
+	}
+
+	pairs := map[string]int{}
+
+all:
+	for _, file := range files {
+		var mask int
+		switch {
+		case strings.HasSuffix(file, ".xlsx"):
+			mask = 1
+		case strings.HasSuffix(file, ".yaml"):
+			mask = 2
+		default:
+			continue all
+		}
+		basename := filepath.Base(file)
+		name := strings.TrimSuffix(basename, filepath.Ext(basename))
+		pairs[name] |= mask
+	}
+
+	var reports []string
+	for name, mask := range pairs {
+		if mask == 3 {
+			reports = append(reports, name)
+		}
+	}
+	sort.Strings(reports)
+
+	out := struct {
+		Reports []string `json:"reports"`
+	}{
+		Reports: reports,
+	}
+	jr = mw.JSONResult{Result: out}
+	return
+}
+
 func report(rw http.ResponseWriter, req *http.Request) {
 
 	path := config.ReportPath()
--- a/pkg/controllers/routes.go	Sun May 30 13:18:10 2021 +0200
+++ b/pkg/controllers/routes.go	Sun May 30 13:18:50 2021 +0200
@@ -325,6 +325,12 @@
 
 	// Handler for reporting
 
+	api.Handle("/data/reports",
+		waterwayAdmin(&mw.JSONHandler{
+			Handle: listReports,
+			NoConn: true,
+		})).Methods(http.MethodGet)
+
 	api.Handle("/data/report/{name:[a-zA-Z0-9_]+}", waterwayAdmin(
 		mw.DBConn(http.HandlerFunc(report)))).Methods(http.MethodGet)
 
--- a/pkg/xlsx/templater.go	Sun May 30 13:18:10 2021 +0200
+++ b/pkg/xlsx/templater.go	Sun May 30 13:18:50 2021 +0200
@@ -162,9 +162,9 @@
 }
 
 func (e *executor) copy(action *Action) error {
-	if len(action.Location) != 2 {
-		return fmt.Errorf("length location = %d (expect 2)",
-			len(action.Source))
+	if n := len(action.Location); !(n == 1 || n == 2) {
+		return fmt.Errorf("length location = %d (expect 1 or 2)",
+			len(action.Location))
 	}
 
 	vars := e.vars()
@@ -193,10 +193,26 @@
 		return a, b
 	}
 
+	var location []string
+
+	if len(action.Location) == 1 {
+		location = []string{action.Location[0], action.Location[0]}
+	} else {
+		location = action.Location
+	}
+
+	var destination string
+
+	if action.Destination == "" {
+		destination = location[0]
+	} else {
+		destination = action.Destination
+	}
+
 	var (
-		s1       = expand(action.Location[0])
-		s2       = expand(action.Location[1])
-		d1       = expand(action.Destination)
+		s1       = expand(location[0])
+		s2       = expand(location[1])
+		d1       = expand(destination)
 		sx1, sy1 = split(s1)
 		sx2, sy2 = split(s2)
 		dx1, dy1 = split(d1)
--- a/schema/gemma.sql	Sun May 30 13:18:10 2021 +0200
+++ b/schema/gemma.sql	Sun May 30 13:18:50 2021 +0200
@@ -384,7 +384,8 @@
         -- keep username length compatible with role identifier
         country char(2) NOT NULL REFERENCES countries,
         map_extent box2d NOT NULL,
-        email_address varchar NOT NULL
+        email_address varchar NOT NULL,
+        report_reciever boolean NOT NULL DEFAULT false
     )
 ;
 
@@ -492,7 +493,8 @@
             CAST('' AS varchar) AS pw,
             p.country,
             p.map_extent,
-            p.email_address
+            p.email_address,
+            p.report_reciever
         FROM internal.user_profiles p
             JOIN pg_roles u ON p.username = u.rolname
             JOIN pg_auth_members a ON u.oid = a.member
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/schema/updates/1450/01.report_reciever.sql	Sun May 30 13:18:50 2021 +0200
@@ -0,0 +1,101 @@
+ALTER TABLE internal.user_profiles
+  ADD COLUMN report_reciever boolean NOT NULL DEFAULT false;
+
+CREATE OR REPLACE VIEW list_users WITH (security_barrier) AS
+    SELECT
+        r.rolname,
+        p.username,
+        CAST('' AS varchar) AS pw,
+        p.country,
+        p.map_extent,
+        p.email_address,
+        p.report_reciever
+    FROM internal.user_profiles p
+        JOIN pg_roles u ON p.username = u.rolname
+        JOIN pg_auth_members a ON u.oid = a.member
+        JOIN pg_roles r ON a.roleid = r.oid
+    WHERE p.username = current_user
+        OR pg_has_role('waterway_admin', 'MEMBER')
+            AND p.country = (
+                SELECT country FROM internal.user_profiles
+                    WHERE username = current_user)
+            AND r.rolname <> 'sys_admin'
+        OR pg_has_role('sys_admin', 'MEMBER')
+;
+
+CREATE OR REPLACE FUNCTION internal.update_user() RETURNS trigger
+AS $$
+DECLARE
+    cur_username varchar;
+BEGIN
+    cur_username = OLD.username;
+
+    IF NEW.username <> cur_username
+    THEN
+        EXECUTE format(
+            'ALTER ROLE %I RENAME TO %I', cur_username, NEW.username);
+        cur_username = NEW.username;
+    END IF;
+
+    UPDATE internal.user_profiles p
+        SET (username, country, map_extent, email_address, report_reciever)
+        = (NEW.username, NEW.country, NEW.map_extent, NEW.email_address, NEW.report_reciever)
+        WHERE p.username = cur_username;
+
+    IF NEW.rolname <> OLD.rolname
+    THEN
+        EXECUTE format(
+            'REVOKE %I FROM %I', OLD.rolname, cur_username);
+        EXECUTE format(
+            'GRANT %I TO %I', NEW.rolname, cur_username);
+    END IF;
+
+    IF NEW.pw IS NOT NULL AND NEW.pw <> ''
+    THEN
+        EXECUTE format(
+            'ALTER ROLE %I PASSWORD %L',
+            cur_username,
+            internal.check_password(NEW.pw));
+    END IF;
+
+    -- Do not leak new password
+    NEW.pw = '';
+    RETURN NEW;
+END;
+$$
+    LANGUAGE plpgsql
+    SECURITY DEFINER;
+
+CREATE OR REPLACE FUNCTION internal.create_user() RETURNS trigger
+AS $$
+BEGIN
+    IF NEW.map_extent IS NULL
+    THEN
+        NEW.map_extent = ST_Extent(CAST(area AS geometry))
+            FROM users.stretches st
+                JOIN users.stretch_countries stc ON stc.stretch_id = st.id
+            WHERE stc.country = NEW.country;
+    END IF;
+
+    IF NEW.username IS NOT NULL
+    -- otherwise let the constraint on user_profiles speak
+    THEN
+        EXECUTE format(
+            'CREATE ROLE %I IN ROLE %I LOGIN PASSWORD %L',
+            NEW.username,
+            NEW.rolname,
+            internal.check_password(NEW.pw));
+    END IF;
+
+    INSERT INTO internal.user_profiles (
+        username, country, map_extent, email_address, report_reciever)
+        VALUES (NEW.username, NEW.country, NEW.map_extent, NEW.email_address, NEW.report_reciever);
+
+    -- Do not leak new password
+    NEW.pw = '';
+    RETURN NEW;
+END;
+$$
+    LANGUAGE plpgsql
+    SECURITY DEFINER;
+
--- a/schema/version.sql	Sun May 30 13:18:10 2021 +0200
+++ b/schema/version.sql	Sun May 30 13:18:50 2021 +0200
@@ -1,1 +1,1 @@
-INSERT INTO gemma_schema_version(version) VALUES (1439);
+INSERT INTO gemma_schema_version(version) VALUES (1450);