public async Task AddActionAsync(Trigger trigger, TriggerAction action, CancellationToken ct = default)
        {
            using var builder = StoredProcedureBuilder.Create(this.m_ctx.Connection);

            builder.WithParameter("triggerid", trigger.ID, NpgsqlDbType.Bigint);
            builder.WithParameter("channel", (int)action.Channel, NpgsqlDbType.Integer);
            builder.WithParameter("target", action.Target, NpgsqlDbType.Varchar);
            builder.WithParameter("message", action.Message, NpgsqlDbType.Text);
            builder.WithFunction(NetworkApi_CreateAction);

            try {
                var reader = await builder.ExecuteAsync(ct).ConfigureAwait(false);

                if (await reader.ReadAsync(ct).ConfigureAwait(false))
                {
                    action.ID = reader.GetInt64(0);
                }

                await reader.DisposeAsync().ConfigureAwait(false);
            } catch (PostgresException exception) {
                if (exception.SqlState == PostgresErrorCodes.UniqueViolation)
                {
                    throw new FormatException("Trigger action for this channel already exists!");
                }
            }
        }
        public async Task <IActionResult> AddAction(long triggerId, [FromBody] TriggerAction action)
        {
            var invalidResponse = new Response <string>();

            if (action == null)
            {
                invalidResponse.AddError("No actions provided to add.");
                return(this.UnprocessableEntity(invalidResponse));
            }

            if (action.Channel > TriggerChannel.MQTT && string.IsNullOrEmpty(action.Target))
            {
                invalidResponse.AddError("Missing target attribute.");
                return(this.UnprocessableEntity(invalidResponse));
            }

            var trigger = await this.m_triggers.GetAsync(triggerId).ConfigureAwait(false);

            action.TriggerID = triggerId;

            if (trigger == null)
            {
                return(this.NotFound());
            }

            var authForTrigger = await this.AuthenticateUserForSensor(trigger.SensorID).ConfigureAwait(false);

            var error = new Response <string>();

            if (action.Channel > TriggerChannel.MQTT)
            {
                authForTrigger = await this.CreatedTargetedActionAsync(action, error).ConfigureAwait(false);
            }

            if (!authForTrigger)
            {
                return(this.UnprocessableEntity(error));
            }

            try {
                await this.m_triggers.AddActionAsync(trigger, action).ConfigureAwait(false);

                await this.m_mqtt.PublishCommandAsync(CommandType.AddSensor, trigger.SensorID).ConfigureAwait(false);
            } catch (FormatException ex) {
                var response = new Response <string>();

                response.AddError(ex.Message);
                return(this.UnprocessableEntity(response));
            }

            trigger.TriggerActions ??= new List <TriggerAction>();
            trigger.TriggerActions.Add(action);

            return(this.CreatedAtAction(nameof(this.GetById), new { triggerId = trigger.ID }, new Response <Trigger>(trigger)));
        }
        private static async Task <bool> GetTriggerFromReaderAsync(Trigger trigger, DbDataReader reader, CancellationToken ct)
        {
            var id = reader.GetInt64(0);

            trigger.ID             = id;
            trigger.SensorID       = reader.GetString(1);
            trigger.KeyValue       = reader.GetString(2);
            trigger.LowerEdge      = SafeGetValue <double?>(reader, 3);
            trigger.UpperEdge      = SafeGetValue <double?>(reader, 4);
            trigger.FormalLanguage = SafeGetValue <string>(reader, 5);
            trigger.Type           = (TriggerType)reader.GetInt32(6);

            do
            {
                id = reader.GetInt64(0);

                if (trigger.ID != id)
                {
                    return(false);
                }

                if (await reader.IsDBNullAsync(7, ct))
                {
                    var hasNext = await reader.ReadAsync(ct).ConfigureAwait(false);

                    return(!hasNext);
                }

                var action = new TriggerAction {
                    ID        = reader.GetInt64(7),
                    Channel   = (TriggerChannel)reader.GetInt32(8),
                    Target    = SafeGetValue <string>(reader, 9),
                    Message   = reader.GetString(10),
                    TriggerID = id
                };

                trigger.TriggerActions ??= new List <TriggerAction>();
                trigger.TriggerActions.Add(action);
            } while(await reader.ReadAsync(ct));

            return(true);
        }
        private async Task <bool> CreatedTargetedActionAsync(TriggerAction action, Response <string> response)
        {
            bool auth;

            if (action.Target == null)
            {
                return(false);
            }

            if (action.Channel == TriggerChannel.ControlMessage)
            {
                var actuator = await this.m_sensors.GetAsync(action.Target).ConfigureAwait(false);

                if (actuator == null)
                {
                    return(false);
                }

                auth = await this.AuthenticateUserForSensor(actuator, false).ConfigureAwait(false);

                if (!auth)
                {
                    response.AddError("Unable to authorize target sensor/actuator!");
                }
            }
            else
            {
                auth = Uri.TryCreate(action.Target, UriKind.Absolute, out var result) &&
                       result.Scheme == Uri.UriSchemeHttp || result?.Scheme == Uri.UriSchemeHttps;

                if (!auth)
                {
                    response.AddError("Invalid URL in target field.");
                }
            }

            return(auth);
        }