view pkg/imports/config.go @ 1704:897d4d8316ad

Import configuration: Made extra attributes updatable, too.
author Sascha L. Teichmann <sascha.teichmann@intevation.de>
date Mon, 07 Jan 2019 15:34:34 +0100
parents 49b89575ab31
children dcbe2a7dc532
line wrap: on
line source

// This is Free Software under GNU Affero General Public License v >= 3.0
// without warranty, see README.md and license for details.
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// License-Filename: LICENSES/AGPL-3.0.txt
//
// Copyright (C) 2018 by via donau
//   – Österreichische Wasserstraßen-Gesellschaft mbH
// Software engineering by Intevation GmbH
//
// Author(s):
//  * Sascha L. Teichmann <sascha.teichmann@intevation.de>

package imports

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"

	"github.com/robfig/cron"

	"gemma.intevation.de/gemma/pkg/auth"
)

type (
	// CronSpec is a string containing a cron line.
	CronSpec string

	// ImportKind is a string which has to be one
	// of the registered import types.
	ImportKind string

	// ConfigAttributes is a map of optional key/value attributes
	// of an import configuration.
	ConfigAttributes map[string]string

	// Config is JSON serialized form of a import configuration.
	Config struct {
		// Kind is the import type.
		Kind ImportKind `json:"kind"`
		// SendEMail indicates if a mail should be be send
		// when the import was changed to states
		// 'pending' or 'failed'.
		SendEMail bool `json:"send-email"`
		// AutoAccept indicates that an import
		// automatically will change from state
		// 'pending' to state 'accepted'.
		AutoAccept bool `json:"auto-accept"`
		// Cron is the cron schedule
		// of this configuration if this value is not
		// nil. If nil the import is not scheduled.
		Cron *CronSpec `json:"cron"`
		// URL is an optional URL used by the import.
		URL *string `json:"url"`
		// Attributes are optional key/value pairs for a configuration.
		Attributes ConfigAttributes `json:"attributes,omitempty"`
	}

	// IDConfig is the same as Config with an ID.
	// Mainly used for server delivered configurations.
	IDConfig struct {
		ID         int64            `json:"id"`
		User       string           `json:"user"`
		Kind       ImportKind       `json:"kind"`
		SendEMail  bool             `json:"send-email"`
		AutoAccept bool             `json:"auto-accept"`
		Cron       *CronSpec        `json:"cron,omitempty"`
		URL        *string          `json:"url,omitempty"`
		Attributes ConfigAttributes `json:"attributes,omitempty"`
	}
)

// Get fetches a value for given key out of the configuration.
// If the key was not found the bool component of the return value
// return false.
func (ca ConfigAttributes) Get(key string) (string, bool) {
	if ca == nil {
		return "", false
	}
	value, found := ca[key]
	return value, found
}

// UnmarshalJSON checks if the incoming string
// is a registered import type.
func (ik *ImportKind) UnmarshalJSON(data []byte) error {
	var s string
	if err := json.Unmarshal(data, &s); err != nil {
		return err
	}

	if !HasImportKindName(s) {
		return fmt.Errorf("Unknown kind '%s'", s)
	}

	*ik = ImportKind(s)

	return nil
}

// UnmarshalJSON checks if the incoming string is
// a valid cron line.
func (cs *CronSpec) UnmarshalJSON(data []byte) error {
	var spec string
	if err := json.Unmarshal(data, &spec); err != nil {
		return err
	}
	if _, err := cron.Parse(spec); err != nil {
		return err
	}
	*cs = CronSpec(spec)

	return nil
}

const (
	configUser = "sys_admin"

	loadConfigSQL = `
SELECT
  username,
  kind,
  send_email,
  auto_accept,
  cron,
  url
FROM waterway.import_configuration
WHERE id = $1`

	loadConfigAttributesSQL = `
SELECT k, v
FROM waterway.import_configuration_attributes
WHERE import_configuration_id = $1`
)

func loadIDConfig(id int64) (*IDConfig, error) {

	ctx := context.Background()
	cfg := &IDConfig{ID: id}

	err := auth.RunAs(ctx, configUser, func(conn *sql.Conn) error {
		var kind ImportKind
		var cron, url sql.NullString
		if err := conn.QueryRowContext(ctx, loadConfigSQL, id).Scan(
			&cfg.User,
			&kind,
			&cfg.SendEMail,
			&cfg.AutoAccept,
			&cron,
			&url,
		); err != nil {
			return err
		}
		cfg.Kind = ImportKind(kind)
		if cron.Valid {
			c := CronSpec(cron.String)
			cfg.Cron = &c
		}
		if url.Valid {
			cfg.URL = &url.String
		}
		// load the extra attributes.
		rows, err := conn.QueryContext(ctx, loadConfigAttributesSQL, id)
		if err != nil {
			return err
		}
		defer rows.Close()
		var attributes map[string]string
		for rows.Next() {
			var k, v string
			if err = rows.Scan(&k, &v); err != nil {
				return err
			}
			if attributes == nil {
				attributes = map[string]string{}
			}
			attributes[k] = v
		}
		if err = rows.Err(); err != nil {
			return err
		}
		if len(attributes) > 0 {
			cfg.Attributes = attributes
		}
		return nil
	})

	switch {
	case err == sql.ErrNoRows:
		return nil, nil
	case err != nil:
		return nil, err
	}

	return cfg, nil
}