Incidents
This commit is contained in:
parent
7acb89ce6c
commit
cac95e1b25
14 changed files with 1308 additions and 40 deletions
9
internal/jsontypes/location.go
Normal file
9
internal/jsontypes/location.go
Normal file
|
@ -0,0 +1,9 @@
|
|||
package jsontypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type Location struct {
|
||||
json.RawMessage
|
||||
}
|
|
@ -26,7 +26,7 @@ type Store interface {
|
|||
type store struct {
|
||||
}
|
||||
|
||||
func New() *store {
|
||||
func NewStore() *store {
|
||||
return new(store)
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ func CtxWithStore(ctx context.Context, s Store) context.Context {
|
|||
func FromCtx(ctx context.Context) Store {
|
||||
s, ok := ctx.Value(StoreCtxKey).(Store)
|
||||
if !ok {
|
||||
return New()
|
||||
return NewStore()
|
||||
}
|
||||
|
||||
return s
|
||||
|
|
301
pkg/database/incidents.sql.go
Normal file
301
pkg/database/incidents.sql.go
Normal file
|
@ -0,0 +1,301 @@
|
|||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.27.0
|
||||
// source: incidents.sql
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
const addToIncident = `-- name: AddToIncident :exec
|
||||
WITH inp AS (
|
||||
SELECT
|
||||
UNNEST($2::UUID[]) id,
|
||||
UNNEST($3::JSONB[]) notes
|
||||
) INSERT INTO incidents_calls(
|
||||
incident_id,
|
||||
call_id,
|
||||
calls_tbl_id,
|
||||
call_date,
|
||||
notes
|
||||
)
|
||||
SELECT
|
||||
$1::UUID,
|
||||
inp.id,
|
||||
inp.id,
|
||||
c.call_date,
|
||||
inp.notes
|
||||
FROM inp
|
||||
JOIN calls c ON c.id = inp.id
|
||||
`
|
||||
|
||||
func (q *Queries) AddToIncident(ctx context.Context, incidentID uuid.UUID, callIds []uuid.UUID, notes [][]byte) error {
|
||||
_, err := q.db.Exec(ctx, addToIncident, incidentID, callIds, notes)
|
||||
return err
|
||||
}
|
||||
|
||||
const createIncident = `-- name: CreateIncident :one
|
||||
INSERT INTO incidents (
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
start_time,
|
||||
end_time,
|
||||
location,
|
||||
metadata
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5,
|
||||
$6,
|
||||
$7
|
||||
)
|
||||
RETURNING id, name, description, start_time, end_time, location, metadata
|
||||
`
|
||||
|
||||
type CreateIncidentParams struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
StartTime pgtype.Timestamptz `json:"start_time"`
|
||||
EndTime pgtype.Timestamptz `json:"end_time"`
|
||||
Location []byte `json:"location"`
|
||||
Metadata jsontypes.Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateIncident(ctx context.Context, arg CreateIncidentParams) (Incident, error) {
|
||||
row := q.db.QueryRow(ctx, createIncident,
|
||||
arg.ID,
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.StartTime,
|
||||
arg.EndTime,
|
||||
arg.Location,
|
||||
arg.Metadata,
|
||||
)
|
||||
var i Incident
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.StartTime,
|
||||
&i.EndTime,
|
||||
&i.Location,
|
||||
&i.Metadata,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteIncident = `-- name: DeleteIncident :exec
|
||||
DELETE FROM incidents CASCADE WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
||||
_, err := q.db.Exec(ctx, deleteIncident, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const getIncident = `-- name: GetIncident :one
|
||||
SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.description,
|
||||
i.start_time,
|
||||
i.end_time,
|
||||
i.location,
|
||||
i.metadata
|
||||
FROM incidents i
|
||||
WHERE i.id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetIncident(ctx context.Context, id uuid.UUID) (Incident, error) {
|
||||
row := q.db.QueryRow(ctx, getIncident, id)
|
||||
var i Incident
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.StartTime,
|
||||
&i.EndTime,
|
||||
&i.Location,
|
||||
&i.Metadata,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const incidentCalls = `-- name: IncidentCalls :many
|
||||
SELECT
|
||||
ic.incident_id, call_date,
|
||||
ic.call_id,
|
||||
ic.notes
|
||||
FROM incidents_calls ic
|
||||
`
|
||||
|
||||
type IncidentCallsRow struct {
|
||||
IncidentID uuid.UUID `json:"incident_id"`
|
||||
CallDate pgtype.Timestamptz `json:"call_date"`
|
||||
CallID uuid.UUID `json:"call_id"`
|
||||
Notes []byte `json:"notes"`
|
||||
}
|
||||
|
||||
// INCOMPLETE
|
||||
func (q *Queries) IncidentCalls(ctx context.Context) ([]IncidentCallsRow, error) {
|
||||
rows, err := q.db.Query(ctx, incidentCalls)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []IncidentCallsRow
|
||||
for rows.Next() {
|
||||
var i IncidentCallsRow
|
||||
if err := rows.Scan(
|
||||
&i.IncidentID,
|
||||
&i.CallDate,
|
||||
&i.CallID,
|
||||
&i.Notes,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listIncidentsCount = `-- name: ListIncidentsCount :one
|
||||
SELECT COUNT(*)
|
||||
FROM incidents i
|
||||
WHERE
|
||||
CASE WHEN $1::TIMESTAMPTZ IS NOT NULL THEN
|
||||
i.start_time >= $1 ELSE TRUE END AND
|
||||
CASE WHEN $2::TIMESTAMPTZ IS NOT NULL THEN
|
||||
i.start_time <= $2 ELSE TRUE END
|
||||
`
|
||||
|
||||
func (q *Queries) ListIncidentsCount(ctx context.Context, start pgtype.Timestamptz, end pgtype.Timestamptz) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, listIncidentsCount, start, end)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const listIncidentsP = `-- name: ListIncidentsP :many
|
||||
SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.description,
|
||||
i.start_time,
|
||||
i.end_time,
|
||||
i.location,
|
||||
i.metadata
|
||||
FROM incidents i
|
||||
WHERE
|
||||
CASE WHEN $1::TIMESTAMPTZ IS NOT NULL THEN
|
||||
i.start_time >= $1 ELSE TRUE END AND
|
||||
CASE WHEN $2::TIMESTAMPTZ IS NOT NULL THEN
|
||||
i.start_time <= $2 ELSE TRUE END
|
||||
ORDER BY
|
||||
CASE WHEN $3::TEXT = 'asc' THEN i.start_time END ASC,
|
||||
CASE WHEN $3::TEXT = 'desc' THEN i.start_time END DESC
|
||||
OFFSET $4 ROWS
|
||||
FETCH NEXT $5 ROWS ONLY
|
||||
`
|
||||
|
||||
type ListIncidentsPParams struct {
|
||||
Start pgtype.Timestamptz `json:"start"`
|
||||
End pgtype.Timestamptz `json:"end"`
|
||||
Direction string `json:"direction"`
|
||||
Offset int32 `json:"offset"`
|
||||
PerPage int32 `json:"per_page"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListIncidentsP(ctx context.Context, arg ListIncidentsPParams) ([]Incident, error) {
|
||||
rows, err := q.db.Query(ctx, listIncidentsP,
|
||||
arg.Start,
|
||||
arg.End,
|
||||
arg.Direction,
|
||||
arg.Offset,
|
||||
arg.PerPage,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []Incident
|
||||
for rows.Next() {
|
||||
var i Incident
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.StartTime,
|
||||
&i.EndTime,
|
||||
&i.Location,
|
||||
&i.Metadata,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateIncident = `-- name: UpdateIncident :one
|
||||
UPDATE incidents
|
||||
SET
|
||||
name = COALESCE($1, name),
|
||||
description = COALESCE($2, description),
|
||||
start_time = COALESCE($3, start_time),
|
||||
end_time = COALESCE($4, end_time),
|
||||
location = COALESCE($5, location),
|
||||
metadata = COALESCE($6, metadata)
|
||||
WHERE
|
||||
id = $7
|
||||
RETURNING id, name, description, start_time, end_time, location, metadata
|
||||
`
|
||||
|
||||
type UpdateIncidentParams struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
StartTime pgtype.Timestamptz `json:"start_time"`
|
||||
EndTime pgtype.Timestamptz `json:"end_time"`
|
||||
Location []byte `json:"location"`
|
||||
Metadata jsontypes.Metadata `json:"metadata"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateIncident(ctx context.Context, arg UpdateIncidentParams) (Incident, error) {
|
||||
row := q.db.QueryRow(ctx, updateIncident,
|
||||
arg.Name,
|
||||
arg.Description,
|
||||
arg.StartTime,
|
||||
arg.EndTime,
|
||||
arg.Location,
|
||||
arg.Metadata,
|
||||
arg.ID,
|
||||
)
|
||||
var i Incident
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Description,
|
||||
&i.StartTime,
|
||||
&i.EndTime,
|
||||
&i.Location,
|
||||
&i.Metadata,
|
||||
)
|
||||
return i, err
|
||||
}
|
|
@ -181,6 +181,55 @@ func (_c *Store_AddLearnedTalkgroup_Call) RunAndReturn(run func(context.Context,
|
|||
return _c
|
||||
}
|
||||
|
||||
// AddToIncident provides a mock function with given fields: ctx, incidentID, callIds, notes
|
||||
func (_m *Store) AddToIncident(ctx context.Context, incidentID uuid.UUID, callIds []uuid.UUID, notes [][]byte) error {
|
||||
ret := _m.Called(ctx, incidentID, callIds, notes)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for AddToIncident")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID, []uuid.UUID, [][]byte) error); ok {
|
||||
r0 = rf(ctx, incidentID, callIds, notes)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_AddToIncident_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'AddToIncident'
|
||||
type Store_AddToIncident_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// AddToIncident is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - incidentID uuid.UUID
|
||||
// - callIds []uuid.UUID
|
||||
// - notes [][]byte
|
||||
func (_e *Store_Expecter) AddToIncident(ctx interface{}, incidentID interface{}, callIds interface{}, notes interface{}) *Store_AddToIncident_Call {
|
||||
return &Store_AddToIncident_Call{Call: _e.mock.On("AddToIncident", ctx, incidentID, callIds, notes)}
|
||||
}
|
||||
|
||||
func (_c *Store_AddToIncident_Call) Run(run func(ctx context.Context, incidentID uuid.UUID, callIds []uuid.UUID, notes [][]byte)) *Store_AddToIncident_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(uuid.UUID), args[2].([]uuid.UUID), args[3].([][]byte))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_AddToIncident_Call) Return(_a0 error) *Store_AddToIncident_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_AddToIncident_Call) RunAndReturn(run func(context.Context, uuid.UUID, []uuid.UUID, [][]byte) error) *Store_AddToIncident_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// BulkSetTalkgroupTags provides a mock function with given fields: ctx, tgs, tags
|
||||
func (_m *Store) BulkSetTalkgroupTags(ctx context.Context, tgs database.TGTuples, tags []string) error {
|
||||
ret := _m.Called(ctx, tgs, tags)
|
||||
|
@ -346,6 +395,63 @@ func (_c *Store_CreateAPIKey_Call) RunAndReturn(run func(context.Context, int, p
|
|||
return _c
|
||||
}
|
||||
|
||||
// CreateIncident provides a mock function with given fields: ctx, arg
|
||||
func (_m *Store) CreateIncident(ctx context.Context, arg database.CreateIncidentParams) (database.Incident, error) {
|
||||
ret := _m.Called(ctx, arg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for CreateIncident")
|
||||
}
|
||||
|
||||
var r0 database.Incident
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.CreateIncidentParams) (database.Incident, error)); ok {
|
||||
return rf(ctx, arg)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.CreateIncidentParams) database.Incident); ok {
|
||||
r0 = rf(ctx, arg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(database.Incident)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, database.CreateIncidentParams) error); ok {
|
||||
r1 = rf(ctx, arg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_CreateIncident_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'CreateIncident'
|
||||
type Store_CreateIncident_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// CreateIncident is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - arg database.CreateIncidentParams
|
||||
func (_e *Store_Expecter) CreateIncident(ctx interface{}, arg interface{}) *Store_CreateIncident_Call {
|
||||
return &Store_CreateIncident_Call{Call: _e.mock.On("CreateIncident", ctx, arg)}
|
||||
}
|
||||
|
||||
func (_c *Store_CreateIncident_Call) Run(run func(ctx context.Context, arg database.CreateIncidentParams)) *Store_CreateIncident_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(database.CreateIncidentParams))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_CreateIncident_Call) Return(_a0 database.Incident, _a1 error) *Store_CreateIncident_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_CreateIncident_Call) RunAndReturn(run func(context.Context, database.CreateIncidentParams) (database.Incident, error)) *Store_CreateIncident_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// CreatePartition provides a mock function with given fields: ctx, parentTable, partitionName, start, end
|
||||
func (_m *Store) CreatePartition(ctx context.Context, parentTable string, partitionName string, start time.Time, end time.Time) error {
|
||||
ret := _m.Called(ctx, parentTable, partitionName, start, end)
|
||||
|
@ -642,6 +748,53 @@ func (_c *Store_DeleteAPIKey_Call) RunAndReturn(run func(context.Context, string
|
|||
return _c
|
||||
}
|
||||
|
||||
// DeleteIncident provides a mock function with given fields: ctx, id
|
||||
func (_m *Store) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for DeleteIncident")
|
||||
}
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) error); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Store_DeleteIncident_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'DeleteIncident'
|
||||
type Store_DeleteIncident_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// DeleteIncident is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - id uuid.UUID
|
||||
func (_e *Store_Expecter) DeleteIncident(ctx interface{}, id interface{}) *Store_DeleteIncident_Call {
|
||||
return &Store_DeleteIncident_Call{Call: _e.mock.On("DeleteIncident", ctx, id)}
|
||||
}
|
||||
|
||||
func (_c *Store_DeleteIncident_Call) Run(run func(ctx context.Context, id uuid.UUID)) *Store_DeleteIncident_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(uuid.UUID))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_DeleteIncident_Call) Return(_a0 error) *Store_DeleteIncident_Call {
|
||||
_c.Call.Return(_a0)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_DeleteIncident_Call) RunAndReturn(run func(context.Context, uuid.UUID) error) *Store_DeleteIncident_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// DeleteSystem provides a mock function with given fields: ctx, id
|
||||
func (_m *Store) DeleteSystem(ctx context.Context, id int) error {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
@ -1167,6 +1320,63 @@ func (_c *Store_GetDatabaseSize_Call) RunAndReturn(run func(context.Context) (st
|
|||
return _c
|
||||
}
|
||||
|
||||
// GetIncident provides a mock function with given fields: ctx, id
|
||||
func (_m *Store) GetIncident(ctx context.Context, id uuid.UUID) (database.Incident, error) {
|
||||
ret := _m.Called(ctx, id)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for GetIncident")
|
||||
}
|
||||
|
||||
var r0 database.Incident
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) (database.Incident, error)); ok {
|
||||
return rf(ctx, id)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, uuid.UUID) database.Incident); ok {
|
||||
r0 = rf(ctx, id)
|
||||
} else {
|
||||
r0 = ret.Get(0).(database.Incident)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, uuid.UUID) error); ok {
|
||||
r1 = rf(ctx, id)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_GetIncident_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetIncident'
|
||||
type Store_GetIncident_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// GetIncident is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - id uuid.UUID
|
||||
func (_e *Store_Expecter) GetIncident(ctx interface{}, id interface{}) *Store_GetIncident_Call {
|
||||
return &Store_GetIncident_Call{Call: _e.mock.On("GetIncident", ctx, id)}
|
||||
}
|
||||
|
||||
func (_c *Store_GetIncident_Call) Run(run func(ctx context.Context, id uuid.UUID)) *Store_GetIncident_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(uuid.UUID))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetIncident_Call) Return(_a0 database.Incident, _a1 error) *Store_GetIncident_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_GetIncident_Call) RunAndReturn(run func(context.Context, uuid.UUID) (database.Incident, error)) *Store_GetIncident_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// GetSystemName provides a mock function with given fields: ctx, systemID
|
||||
func (_m *Store) GetSystemName(ctx context.Context, systemID int) (string, error) {
|
||||
ret := _m.Called(ctx, systemID)
|
||||
|
@ -2384,6 +2594,64 @@ func (_c *Store_InTx_Call) RunAndReturn(run func(context.Context, func(database.
|
|||
return _c
|
||||
}
|
||||
|
||||
// IncidentCalls provides a mock function with given fields: ctx
|
||||
func (_m *Store) IncidentCalls(ctx context.Context) ([]database.IncidentCallsRow, error) {
|
||||
ret := _m.Called(ctx)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for IncidentCalls")
|
||||
}
|
||||
|
||||
var r0 []database.IncidentCallsRow
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context) ([]database.IncidentCallsRow, error)); ok {
|
||||
return rf(ctx)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context) []database.IncidentCallsRow); ok {
|
||||
r0 = rf(ctx)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]database.IncidentCallsRow)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context) error); ok {
|
||||
r1 = rf(ctx)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_IncidentCalls_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'IncidentCalls'
|
||||
type Store_IncidentCalls_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// IncidentCalls is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
func (_e *Store_Expecter) IncidentCalls(ctx interface{}) *Store_IncidentCalls_Call {
|
||||
return &Store_IncidentCalls_Call{Call: _e.mock.On("IncidentCalls", ctx)}
|
||||
}
|
||||
|
||||
func (_c *Store_IncidentCalls_Call) Run(run func(ctx context.Context)) *Store_IncidentCalls_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_IncidentCalls_Call) Return(_a0 []database.IncidentCallsRow, _a1 error) *Store_IncidentCalls_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_IncidentCalls_Call) RunAndReturn(run func(context.Context) ([]database.IncidentCallsRow, error)) *Store_IncidentCalls_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListCallsCount provides a mock function with given fields: ctx, arg
|
||||
func (_m *Store) ListCallsCount(ctx context.Context, arg database.ListCallsCountParams) (int64, error) {
|
||||
ret := _m.Called(ctx, arg)
|
||||
|
@ -2500,6 +2768,123 @@ func (_c *Store_ListCallsP_Call) RunAndReturn(run func(context.Context, database
|
|||
return _c
|
||||
}
|
||||
|
||||
// ListIncidentsCount provides a mock function with given fields: ctx, start, end
|
||||
func (_m *Store) ListIncidentsCount(ctx context.Context, start pgtype.Timestamptz, end pgtype.Timestamptz) (int64, error) {
|
||||
ret := _m.Called(ctx, start, end)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListIncidentsCount")
|
||||
}
|
||||
|
||||
var r0 int64
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, pgtype.Timestamptz, pgtype.Timestamptz) (int64, error)); ok {
|
||||
return rf(ctx, start, end)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, pgtype.Timestamptz, pgtype.Timestamptz) int64); ok {
|
||||
r0 = rf(ctx, start, end)
|
||||
} else {
|
||||
r0 = ret.Get(0).(int64)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, pgtype.Timestamptz, pgtype.Timestamptz) error); ok {
|
||||
r1 = rf(ctx, start, end)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_ListIncidentsCount_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListIncidentsCount'
|
||||
type Store_ListIncidentsCount_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListIncidentsCount is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - start pgtype.Timestamptz
|
||||
// - end pgtype.Timestamptz
|
||||
func (_e *Store_Expecter) ListIncidentsCount(ctx interface{}, start interface{}, end interface{}) *Store_ListIncidentsCount_Call {
|
||||
return &Store_ListIncidentsCount_Call{Call: _e.mock.On("ListIncidentsCount", ctx, start, end)}
|
||||
}
|
||||
|
||||
func (_c *Store_ListIncidentsCount_Call) Run(run func(ctx context.Context, start pgtype.Timestamptz, end pgtype.Timestamptz)) *Store_ListIncidentsCount_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(pgtype.Timestamptz), args[2].(pgtype.Timestamptz))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_ListIncidentsCount_Call) Return(_a0 int64, _a1 error) *Store_ListIncidentsCount_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_ListIncidentsCount_Call) RunAndReturn(run func(context.Context, pgtype.Timestamptz, pgtype.Timestamptz) (int64, error)) *Store_ListIncidentsCount_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// ListIncidentsP provides a mock function with given fields: ctx, arg
|
||||
func (_m *Store) ListIncidentsP(ctx context.Context, arg database.ListIncidentsPParams) ([]database.Incident, error) {
|
||||
ret := _m.Called(ctx, arg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for ListIncidentsP")
|
||||
}
|
||||
|
||||
var r0 []database.Incident
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.ListIncidentsPParams) ([]database.Incident, error)); ok {
|
||||
return rf(ctx, arg)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.ListIncidentsPParams) []database.Incident); ok {
|
||||
r0 = rf(ctx, arg)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]database.Incident)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, database.ListIncidentsPParams) error); ok {
|
||||
r1 = rf(ctx, arg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_ListIncidentsP_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ListIncidentsP'
|
||||
type Store_ListIncidentsP_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// ListIncidentsP is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - arg database.ListIncidentsPParams
|
||||
func (_e *Store_Expecter) ListIncidentsP(ctx interface{}, arg interface{}) *Store_ListIncidentsP_Call {
|
||||
return &Store_ListIncidentsP_Call{Call: _e.mock.On("ListIncidentsP", ctx, arg)}
|
||||
}
|
||||
|
||||
func (_c *Store_ListIncidentsP_Call) Run(run func(ctx context.Context, arg database.ListIncidentsPParams)) *Store_ListIncidentsP_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(database.ListIncidentsPParams))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_ListIncidentsP_Call) Return(_a0 []database.Incident, _a1 error) *Store_ListIncidentsP_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_ListIncidentsP_Call) RunAndReturn(run func(context.Context, database.ListIncidentsPParams) ([]database.Incident, error)) *Store_ListIncidentsP_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// RestoreTalkgroupVersion provides a mock function with given fields: ctx, versionIds
|
||||
func (_m *Store) RestoreTalkgroupVersion(ctx context.Context, versionIds int) (database.Talkgroup, error) {
|
||||
ret := _m.Called(ctx, versionIds)
|
||||
|
@ -2859,6 +3244,63 @@ func (_c *Store_SweepCalls_Call) RunAndReturn(run func(context.Context, pgtype.T
|
|||
return _c
|
||||
}
|
||||
|
||||
// UpdateIncident provides a mock function with given fields: ctx, arg
|
||||
func (_m *Store) UpdateIncident(ctx context.Context, arg database.UpdateIncidentParams) (database.Incident, error) {
|
||||
ret := _m.Called(ctx, arg)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for UpdateIncident")
|
||||
}
|
||||
|
||||
var r0 database.Incident
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.UpdateIncidentParams) (database.Incident, error)); ok {
|
||||
return rf(ctx, arg)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func(context.Context, database.UpdateIncidentParams) database.Incident); ok {
|
||||
r0 = rf(ctx, arg)
|
||||
} else {
|
||||
r0 = ret.Get(0).(database.Incident)
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func(context.Context, database.UpdateIncidentParams) error); ok {
|
||||
r1 = rf(ctx, arg)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Store_UpdateIncident_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateIncident'
|
||||
type Store_UpdateIncident_Call struct {
|
||||
*mock.Call
|
||||
}
|
||||
|
||||
// UpdateIncident is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - arg database.UpdateIncidentParams
|
||||
func (_e *Store_Expecter) UpdateIncident(ctx interface{}, arg interface{}) *Store_UpdateIncident_Call {
|
||||
return &Store_UpdateIncident_Call{Call: _e.mock.On("UpdateIncident", ctx, arg)}
|
||||
}
|
||||
|
||||
func (_c *Store_UpdateIncident_Call) Run(run func(ctx context.Context, arg database.UpdateIncidentParams)) *Store_UpdateIncident_Call {
|
||||
_c.Call.Run(func(args mock.Arguments) {
|
||||
run(args[0].(context.Context), args[1].(database.UpdateIncidentParams))
|
||||
})
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_UpdateIncident_Call) Return(_a0 database.Incident, _a1 error) *Store_UpdateIncident_Call {
|
||||
_c.Call.Return(_a0, _a1)
|
||||
return _c
|
||||
}
|
||||
|
||||
func (_c *Store_UpdateIncident_Call) RunAndReturn(run func(context.Context, database.UpdateIncidentParams) (database.Incident, error)) *Store_UpdateIncident_Call {
|
||||
_c.Call.Return(run)
|
||||
return _c
|
||||
}
|
||||
|
||||
// UpdatePassword provides a mock function with given fields: ctx, username, password
|
||||
func (_m *Store) UpdatePassword(ctx context.Context, username string, password string) error {
|
||||
ret := _m.Called(ctx, username, password)
|
||||
|
|
|
@ -56,13 +56,13 @@ type Call struct {
|
|||
}
|
||||
|
||||
type Incident struct {
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
StartTime pgtype.Timestamp `json:"start_time,omitempty"`
|
||||
EndTime pgtype.Timestamp `json:"end_time,omitempty"`
|
||||
Location []byte `json:"location,omitempty"`
|
||||
Metadata []byte `json:"metadata,omitempty"`
|
||||
ID uuid.UUID `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
StartTime pgtype.Timestamptz `json:"start_time,omitempty"`
|
||||
EndTime pgtype.Timestamptz `json:"end_time,omitempty"`
|
||||
Location []byte `json:"location,omitempty"`
|
||||
Metadata jsontypes.Metadata `json:"metadata,omitempty"`
|
||||
}
|
||||
|
||||
type IncidentsCall struct {
|
||||
|
|
|
@ -15,12 +15,15 @@ type Querier interface {
|
|||
AddAlert(ctx context.Context, arg AddAlertParams) error
|
||||
AddCall(ctx context.Context, arg AddCallParams) error
|
||||
AddLearnedTalkgroup(ctx context.Context, arg AddLearnedTalkgroupParams) (Talkgroup, error)
|
||||
AddToIncident(ctx context.Context, incidentID uuid.UUID, callIds []uuid.UUID, notes [][]byte) error
|
||||
// This is used to sweep calls that are part of an incident prior to pruning a partition.
|
||||
CleanupSweptCalls(ctx context.Context, rangeStart pgtype.Timestamptz, rangeEnd pgtype.Timestamptz) (int64, error)
|
||||
CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error)
|
||||
CreateIncident(ctx context.Context, arg CreateIncidentParams) (Incident, error)
|
||||
CreateSystem(ctx context.Context, iD int, name string) error
|
||||
CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
|
||||
DeleteAPIKey(ctx context.Context, apiKey string) error
|
||||
DeleteIncident(ctx context.Context, id uuid.UUID) error
|
||||
DeleteSystem(ctx context.Context, id int) error
|
||||
DeleteTalkgroup(ctx context.Context, systemID int32, tGID int32) error
|
||||
DeleteUser(ctx context.Context, username string) error
|
||||
|
@ -29,6 +32,7 @@ type Querier interface {
|
|||
GetAppPrefs(ctx context.Context, appName string, uid int) ([]byte, error)
|
||||
GetCallAudioByID(ctx context.Context, id uuid.UUID) (GetCallAudioByIDRow, error)
|
||||
GetDatabaseSize(ctx context.Context) (string, error)
|
||||
GetIncident(ctx context.Context, id uuid.UUID) (Incident, error)
|
||||
GetSystemName(ctx context.Context, systemID int) (string, error)
|
||||
GetTalkgroup(ctx context.Context, systemID int32, tGID int32) (GetTalkgroupRow, error)
|
||||
GetTalkgroupIDsByTags(ctx context.Context, anyTags []string, allTags []string, notTags []string) ([]GetTalkgroupIDsByTagsRow, error)
|
||||
|
@ -46,8 +50,12 @@ type Querier interface {
|
|||
GetUserByUID(ctx context.Context, id int) (User, error)
|
||||
GetUserByUsername(ctx context.Context, username string) (User, error)
|
||||
GetUsers(ctx context.Context) ([]User, error)
|
||||
// INCOMPLETE
|
||||
IncidentCalls(ctx context.Context) ([]IncidentCallsRow, error)
|
||||
ListCallsCount(ctx context.Context, arg ListCallsCountParams) (int64, error)
|
||||
ListCallsP(ctx context.Context, arg ListCallsPParams) ([]ListCallsPRow, error)
|
||||
ListIncidentsCount(ctx context.Context, start pgtype.Timestamptz, end pgtype.Timestamptz) (int64, error)
|
||||
ListIncidentsP(ctx context.Context, arg ListIncidentsPParams) ([]Incident, error)
|
||||
RestoreTalkgroupVersion(ctx context.Context, versionIds int) (Talkgroup, error)
|
||||
SetAppPrefs(ctx context.Context, appName string, prefs []byte, uid int) error
|
||||
SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error
|
||||
|
@ -55,6 +63,7 @@ type Querier interface {
|
|||
StoreDeletedTGVersion(ctx context.Context, systemID *int32, tGID *int32, submitter *int32) error
|
||||
StoreTGVersion(ctx context.Context, arg []StoreTGVersionParams) *StoreTGVersionBatchResults
|
||||
SweepCalls(ctx context.Context, rangeStart pgtype.Timestamptz, rangeEnd pgtype.Timestamptz) (int64, error)
|
||||
UpdateIncident(ctx context.Context, arg UpdateIncidentParams) (Incident, error)
|
||||
UpdatePassword(ctx context.Context, username string, password string) error
|
||||
UpdateTalkgroup(ctx context.Context, arg UpdateTalkgroupParams) (Talkgroup, error)
|
||||
UpsertTalkgroup(ctx context.Context, arg []UpsertTalkgroupParams) *UpsertTalkgroupBatchResults
|
||||
|
|
16
pkg/incidents/incident.go
Normal file
16
pkg/incidents/incident.go
Normal file
|
@ -0,0 +1,16 @@
|
|||
package incidents
|
||||
|
||||
import (
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Incident struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
StartTime *jsontypes.Time `json:"startTime"`
|
||||
EndTime *jsontypes.Time `json:"endTime"`
|
||||
Location jsontypes.Location `json:"location"`
|
||||
Metadata jsontypes.Metadata `json:"metadata"`
|
||||
}
|
159
pkg/incidents/incstore/store.go
Normal file
159
pkg/incidents/incstore/store.go
Normal file
|
@ -0,0 +1,159 @@
|
|||
package incstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/common"
|
||||
"dynatron.me/x/stillbox/internal/jsontypes"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/incidents"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type IncidentsParams struct {
|
||||
common.Pagination
|
||||
Direction *common.SortDirection `json:"dir"`
|
||||
|
||||
Start *jsontypes.Time `json:"start"`
|
||||
End *jsontypes.Time `json:"end"`
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
// CreateIncident creates an incident.
|
||||
CreateIncident(ctx context.Context, inc incidents.Incident) (database.Incident, error)
|
||||
|
||||
// AddToIncident adds the specified call IDs to an incident.
|
||||
// If not nil, notes must be valid json.
|
||||
AddToIncident(ctx context.Context, incidentID uuid.UUID, callIDs []uuid.UUID, notes []byte) error
|
||||
|
||||
// Incidents gets incidents matching parameters and pagination.
|
||||
Incidents(ctx context.Context, p IncidentsParams) (incs []database.Incident, totalCount int, err error)
|
||||
|
||||
// Incident gets a single incident.
|
||||
Incident(ctx context.Context, id uuid.UUID) (database.Incident, error)
|
||||
|
||||
// UpdateIncident updates an incident.
|
||||
UpdateIncident(ctx context.Context, id uuid.UUID, p UpdateIncidentParams) (database.Incident, error)
|
||||
|
||||
// DeleteIncident deletes an incident.
|
||||
DeleteIncident(ctx context.Context, id uuid.UUID) error
|
||||
}
|
||||
|
||||
type store struct {
|
||||
}
|
||||
|
||||
type storeCtxKey string
|
||||
|
||||
const StoreCtxKey storeCtxKey = "store"
|
||||
|
||||
func CtxWithStore(ctx context.Context, s Store) context.Context {
|
||||
return context.WithValue(ctx, StoreCtxKey, s)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) Store {
|
||||
s, ok := ctx.Value(StoreCtxKey).(Store)
|
||||
if !ok {
|
||||
return NewStore()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func NewStore() Store {
|
||||
return &store{}
|
||||
}
|
||||
|
||||
func (s *store) CreateIncident(ctx context.Context, inc incidents.Incident) (database.Incident, error) {
|
||||
db := database.FromCtx(ctx)
|
||||
dbInc, err := db.CreateIncident(ctx, database.CreateIncidentParams{
|
||||
ID: uuid.New(),
|
||||
Name: inc.Name,
|
||||
Description: inc.Description,
|
||||
StartTime: inc.StartTime.PGTypeTSTZ(),
|
||||
EndTime: inc.EndTime.PGTypeTSTZ(),
|
||||
Location: inc.Location.RawMessage,
|
||||
Metadata: inc.Metadata,
|
||||
})
|
||||
|
||||
return dbInc, err
|
||||
}
|
||||
|
||||
func (s *store) AddToIncident(ctx context.Context, incidentID uuid.UUID, callIDs []uuid.UUID, notes []byte) error {
|
||||
db := database.FromCtx(ctx)
|
||||
|
||||
var noteAr [][]byte
|
||||
if notes != nil {
|
||||
noteAr = make([][]byte, len(callIDs))
|
||||
for i := range callIDs {
|
||||
noteAr[i] = notes
|
||||
}
|
||||
}
|
||||
|
||||
return db.AddToIncident(ctx, incidentID, callIDs, noteAr)
|
||||
}
|
||||
|
||||
func (s *store) Incidents(ctx context.Context, p IncidentsParams) (rows []database.Incident, totalCount int, err error) {
|
||||
db := database.FromCtx(ctx)
|
||||
|
||||
offset, perPage := p.Pagination.OffsetPerPage(100)
|
||||
dbParam := database.ListIncidentsPParams{
|
||||
Start: p.Start.PGTypeTSTZ(),
|
||||
End: p.End.PGTypeTSTZ(),
|
||||
Direction: p.Direction.DirString(common.DirAsc),
|
||||
Offset: offset,
|
||||
PerPage: perPage,
|
||||
}
|
||||
|
||||
var count int64
|
||||
txErr := db.InTx(ctx, func(db database.Store) error {
|
||||
var err error
|
||||
count, err = db.ListIncidentsCount(ctx, dbParam.Start, dbParam.End)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rows, err = db.ListIncidentsP(ctx, dbParam)
|
||||
return err
|
||||
}, pgx.TxOptions{})
|
||||
if txErr != nil {
|
||||
return nil, 0, txErr
|
||||
}
|
||||
|
||||
return rows, int(count), err
|
||||
}
|
||||
|
||||
func (s *store) Incident(ctx context.Context, id uuid.UUID) (database.Incident, error) {
|
||||
return database.FromCtx(ctx).GetIncident(ctx, id)
|
||||
}
|
||||
|
||||
type UpdateIncidentParams struct {
|
||||
Name *string `json:"name"`
|
||||
Description *string `json:"description"`
|
||||
StartTime *jsontypes.Time `json:"startTime"`
|
||||
EndTime *jsontypes.Time `json:"endTime"`
|
||||
Location []byte `json:"location"`
|
||||
Metadata jsontypes.Metadata `json:"metadata"`
|
||||
}
|
||||
|
||||
func (uip UpdateIncidentParams) toDBUIP(id uuid.UUID) database.UpdateIncidentParams {
|
||||
return database.UpdateIncidentParams{
|
||||
ID: id,
|
||||
Name: uip.Name,
|
||||
Description: uip.Description,
|
||||
StartTime: uip.StartTime.PGTypeTSTZ(),
|
||||
EndTime: uip.EndTime.PGTypeTSTZ(),
|
||||
Location: uip.Location,
|
||||
Metadata: uip.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *store) UpdateIncident(ctx context.Context, id uuid.UUID, p UpdateIncidentParams) (database.Incident, error) {
|
||||
db := database.FromCtx(ctx)
|
||||
|
||||
return db.UpdateIncident(ctx, p.toDBUIP(id))
|
||||
}
|
||||
|
||||
func (s *store) DeleteIncident(ctx context.Context, id uuid.UUID) error {
|
||||
return database.FromCtx(ctx).DeleteIncident(ctx, id)
|
||||
}
|
|
@ -32,6 +32,7 @@ func (a *api) Subrouter() http.Handler {
|
|||
r.Mount("/talkgroup", new(talkgroupAPI).Subrouter())
|
||||
r.Mount("/call", new(callsAPI).Subrouter())
|
||||
r.Mount("/user", new(usersAPI).Subrouter())
|
||||
r.Mount("/incident", new(incidentsAPI).Subrouter())
|
||||
|
||||
return r
|
||||
}
|
||||
|
|
152
pkg/rest/incidents.go
Normal file
152
pkg/rest/incidents.go
Normal file
|
@ -0,0 +1,152 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"dynatron.me/x/stillbox/internal/forms"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/incidents"
|
||||
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type incidentsAPI struct {
|
||||
}
|
||||
|
||||
func (ia *incidentsAPI) Subrouter() http.Handler {
|
||||
r := chi.NewMux()
|
||||
|
||||
r.Get(`/{id:[a-f0-9-]+}`, ia.getIncident)
|
||||
|
||||
r.Post(`/create`, ia.createIncident)
|
||||
r.Post(`/`, ia.listIncidents)
|
||||
|
||||
r.Put(`/{id:[a-f0-9]+}`, ia.updateIncident)
|
||||
|
||||
r.Delete(`/{id:[a-f0-9]+}`, ia.deleteIncident)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (ia *incidentsAPI) listIncidents(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
incs := incstore.FromCtx(ctx)
|
||||
|
||||
p := incstore.IncidentsParams{}
|
||||
err := forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
res := struct {
|
||||
Incidents []database.Incident `json:"incidents"`
|
||||
Count int `json:"count"`
|
||||
}{}
|
||||
|
||||
res.Incidents, res.Count, err = incs.Incidents(ctx, p)
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
}
|
||||
|
||||
respond(w, r, res)
|
||||
}
|
||||
|
||||
func (ia *incidentsAPI) createIncident(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
incs := incstore.FromCtx(ctx)
|
||||
|
||||
p := incidents.Incident{}
|
||||
err := forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
inc, err := incs.CreateIncident(ctx, p)
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
}
|
||||
|
||||
respond(w, r, inc)
|
||||
}
|
||||
|
||||
func (ia *incidentsAPI) getIncident(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
incs := incstore.FromCtx(ctx)
|
||||
|
||||
params := struct {
|
||||
ID uuid.UUID `param:"id"`
|
||||
}{}
|
||||
|
||||
err := decodeParams(¶ms, r)
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
inc, err := incs.Incident(ctx, params.ID)
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
}
|
||||
|
||||
respond(w, r, inc)
|
||||
}
|
||||
|
||||
func (ia *incidentsAPI) updateIncident(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
incs := incstore.FromCtx(ctx)
|
||||
|
||||
urlParams := struct {
|
||||
ID uuid.UUID `param:"id"`
|
||||
}{}
|
||||
|
||||
err := decodeParams(&urlParams, r)
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
p := incstore.UpdateIncidentParams{}
|
||||
err = forms.Unmarshal(r, &p, forms.WithTag("json"), forms.WithAcceptBlank(), forms.WithOmitEmpty())
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
inc, err := incs.UpdateIncident(ctx, urlParams.ID, p)
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
}
|
||||
|
||||
respond(w, r, inc)
|
||||
}
|
||||
|
||||
func (ia *incidentsAPI) deleteIncident(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
incs := incstore.FromCtx(ctx)
|
||||
|
||||
urlParams := struct {
|
||||
ID uuid.UUID `param:"id"`
|
||||
}{}
|
||||
|
||||
err := decodeParams(&urlParams, r)
|
||||
if err != nil {
|
||||
wErr(w, r, badRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
err = incs.DeleteIncident(ctx, urlParams.ID)
|
||||
if err != nil {
|
||||
wErr(w, r, autoError(err))
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
|
@ -8,9 +8,11 @@ import (
|
|||
|
||||
"dynatron.me/x/stillbox/pkg/alerting"
|
||||
"dynatron.me/x/stillbox/pkg/auth"
|
||||
"dynatron.me/x/stillbox/pkg/calls/callstore"
|
||||
"dynatron.me/x/stillbox/pkg/config"
|
||||
"dynatron.me/x/stillbox/pkg/database"
|
||||
"dynatron.me/x/stillbox/pkg/database/partman"
|
||||
"dynatron.me/x/stillbox/pkg/incidents/incstore"
|
||||
"dynatron.me/x/stillbox/pkg/nexus"
|
||||
"dynatron.me/x/stillbox/pkg/notify"
|
||||
"dynatron.me/x/stillbox/pkg/rest"
|
||||
|
@ -28,22 +30,24 @@ import (
|
|||
const shutdownTimeout = 5 * time.Second
|
||||
|
||||
type Server struct {
|
||||
auth *auth.Auth
|
||||
conf *config.Configuration
|
||||
db database.Store
|
||||
r *chi.Mux
|
||||
sources sources.Sources
|
||||
sinks sinks.Sinks
|
||||
relayer *sinks.RelayManager
|
||||
nex *nexus.Nexus
|
||||
logger *Logger
|
||||
alerter alerting.Alerter
|
||||
notifier notify.Notifier
|
||||
hup chan os.Signal
|
||||
tgs tgstore.Store
|
||||
rest rest.API
|
||||
partman partman.PartitionManager
|
||||
users users.Store
|
||||
auth *auth.Auth
|
||||
conf *config.Configuration
|
||||
db database.Store
|
||||
r *chi.Mux
|
||||
sources sources.Sources
|
||||
sinks sinks.Sinks
|
||||
relayer *sinks.RelayManager
|
||||
nex *nexus.Nexus
|
||||
logger *Logger
|
||||
alerter alerting.Alerter
|
||||
notifier notify.Notifier
|
||||
hup chan os.Signal
|
||||
tgs tgstore.Store
|
||||
rest rest.API
|
||||
partman partman.PartitionManager
|
||||
users users.Store
|
||||
calls callstore.Store
|
||||
incidents incstore.Store
|
||||
}
|
||||
|
||||
func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
||||
|
@ -70,18 +74,20 @@ func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
|||
api := rest.New()
|
||||
|
||||
srv := &Server{
|
||||
auth: authenticator,
|
||||
conf: cfg,
|
||||
db: db,
|
||||
r: r,
|
||||
nex: nexus.New(),
|
||||
logger: logger,
|
||||
alerter: alerting.New(cfg.Alerting, tgCache, alerting.WithNotifier(notifier)),
|
||||
notifier: notifier,
|
||||
tgs: tgCache,
|
||||
sinks: sinks.NewSinkManager(),
|
||||
rest: api,
|
||||
users: users.NewStore(),
|
||||
auth: authenticator,
|
||||
conf: cfg,
|
||||
db: db,
|
||||
r: r,
|
||||
nex: nexus.New(),
|
||||
logger: logger,
|
||||
alerter: alerting.New(cfg.Alerting, tgCache, alerting.WithNotifier(notifier)),
|
||||
notifier: notifier,
|
||||
tgs: tgCache,
|
||||
sinks: sinks.NewSinkManager(),
|
||||
rest: api,
|
||||
users: users.NewStore(),
|
||||
calls: callstore.NewStore(),
|
||||
incidents: incstore.NewStore(),
|
||||
}
|
||||
|
||||
if cfg.DB.Partition.Enabled {
|
||||
|
@ -129,14 +135,22 @@ func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
|
|||
return srv, nil
|
||||
}
|
||||
|
||||
func (s *Server) addStoresTo(ctx context.Context) context.Context {
|
||||
ctx = database.CtxWithDB(ctx, s.db)
|
||||
ctx = tgstore.CtxWithStore(ctx, s.tgs)
|
||||
ctx = users.CtxWithStore(ctx, s.users)
|
||||
ctx = callstore.CtxWithStore(ctx, s.calls)
|
||||
ctx = incstore.CtxWithStore(ctx, s.incidents)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (s *Server) Go(ctx context.Context) error {
|
||||
defer database.Close(s.db)
|
||||
|
||||
s.installHupHandler()
|
||||
|
||||
ctx = database.CtxWithDB(ctx, s.db)
|
||||
ctx = tgstore.CtxWithStore(ctx, s.tgs)
|
||||
ctx = users.CtxWithStore(ctx, s.users)
|
||||
ctx = s.addStoresTo(ctx)
|
||||
|
||||
httpSrv := &http.Server{
|
||||
Addr: s.conf.Listen,
|
||||
|
|
50
pkg/store/store.go
Normal file
50
pkg/store/store.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"dynatron.me/x/stillbox/pkg/talkgroups/tgstore"
|
||||
"dynatron.me/x/stillbox/pkg/users"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
TG() tgstore.Store
|
||||
User() users.Store
|
||||
}
|
||||
|
||||
type store struct {
|
||||
tg tgstore.Store
|
||||
user users.Store
|
||||
}
|
||||
|
||||
func (s *store) TG() tgstore.Store {
|
||||
return s.tg
|
||||
}
|
||||
|
||||
func (s *store) User() users.Store {
|
||||
return s.user
|
||||
}
|
||||
|
||||
func New() Store {
|
||||
return &store{
|
||||
tg: tgstore.NewCache(),
|
||||
user: users.NewStore(),
|
||||
}
|
||||
}
|
||||
|
||||
type storeCtxKey string
|
||||
|
||||
const StoreCtxKey storeCtxKey = "store"
|
||||
|
||||
func CtxWithStore(ctx context.Context, s Store) context.Context {
|
||||
return context.WithValue(ctx, StoreCtxKey, s)
|
||||
}
|
||||
|
||||
func FromCtx(ctx context.Context) Store {
|
||||
s, ok := ctx.Value(StoreCtxKey).(Store)
|
||||
if !ok {
|
||||
return New()
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
110
sql/postgres/queries/incidents.sql
Normal file
110
sql/postgres/queries/incidents.sql
Normal file
|
@ -0,0 +1,110 @@
|
|||
-- name: AddToIncident :exec
|
||||
WITH inp AS (
|
||||
SELECT
|
||||
UNNEST(@call_ids::UUID[]) id,
|
||||
UNNEST(@notes::JSONB[]) notes
|
||||
) INSERT INTO incidents_calls(
|
||||
incident_id,
|
||||
call_id,
|
||||
calls_tbl_id,
|
||||
call_date,
|
||||
notes
|
||||
)
|
||||
SELECT
|
||||
@incident_id::UUID,
|
||||
inp.id,
|
||||
inp.id,
|
||||
c.call_date,
|
||||
inp.notes
|
||||
FROM inp
|
||||
JOIN calls c ON c.id = inp.id
|
||||
;
|
||||
|
||||
-- name: CreateIncident :one
|
||||
INSERT INTO incidents (
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
start_time,
|
||||
end_time,
|
||||
location,
|
||||
metadata
|
||||
) VALUES (
|
||||
@id,
|
||||
@name,
|
||||
sqlc.narg('description'),
|
||||
sqlc.narg('start_time'),
|
||||
sqlc.narg('end_time'),
|
||||
sqlc.narg('location'),
|
||||
sqlc.narg('metadata')
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
|
||||
-- name: ListIncidentsP :many
|
||||
SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.description,
|
||||
i.start_time,
|
||||
i.end_time,
|
||||
i.location,
|
||||
i.metadata
|
||||
FROM incidents i
|
||||
WHERE
|
||||
CASE WHEN sqlc.narg('start')::TIMESTAMPTZ IS NOT NULL THEN
|
||||
i.start_time >= sqlc.narg('start') ELSE TRUE END AND
|
||||
CASE WHEN sqlc.narg('end')::TIMESTAMPTZ IS NOT NULL THEN
|
||||
i.start_time <= sqlc.narg('end') ELSE TRUE END
|
||||
ORDER BY
|
||||
CASE WHEN @direction::TEXT = 'asc' THEN i.start_time END ASC,
|
||||
CASE WHEN @direction::TEXT = 'desc' THEN i.start_time END DESC
|
||||
OFFSET sqlc.arg('offset') ROWS
|
||||
FETCH NEXT sqlc.arg('per_page') ROWS ONLY
|
||||
;
|
||||
|
||||
-- name: ListIncidentsCount :one
|
||||
SELECT COUNT(*)
|
||||
FROM incidents i
|
||||
WHERE
|
||||
CASE WHEN sqlc.narg('start')::TIMESTAMPTZ IS NOT NULL THEN
|
||||
i.start_time >= sqlc.narg('start') ELSE TRUE END AND
|
||||
CASE WHEN sqlc.narg('end')::TIMESTAMPTZ IS NOT NULL THEN
|
||||
i.start_time <= sqlc.narg('end') ELSE TRUE END
|
||||
;
|
||||
|
||||
-- name: IncidentCalls :many
|
||||
-- INCOMPLETE
|
||||
SELECT
|
||||
ic.incident_id, call_date,
|
||||
ic.call_id,
|
||||
ic.notes
|
||||
FROM incidents_calls ic;
|
||||
|
||||
-- name: GetIncident :one
|
||||
SELECT
|
||||
i.id,
|
||||
i.name,
|
||||
i.description,
|
||||
i.start_time,
|
||||
i.end_time,
|
||||
i.location,
|
||||
i.metadata
|
||||
FROM incidents i
|
||||
WHERE i.id = @id;
|
||||
|
||||
-- name: UpdateIncident :one
|
||||
UPDATE incidents
|
||||
SET
|
||||
name = COALESCE(sqlc.narg('name'), name),
|
||||
description = COALESCE(sqlc.narg('description'), description),
|
||||
start_time = COALESCE(sqlc.narg('start_time'), start_time),
|
||||
end_time = COALESCE(sqlc.narg('end_time'), end_time),
|
||||
location = COALESCE(sqlc.narg('location'), location),
|
||||
metadata = COALESCE(sqlc.narg('metadata'), metadata)
|
||||
WHERE
|
||||
id = @id
|
||||
RETURNING *;
|
||||
|
||||
-- name: DeleteIncident :exec
|
||||
DELETE FROM incidents CASCADE WHERE id = @id;
|
|
@ -37,6 +37,11 @@ sql:
|
|||
import: "dynatron.me/x/stillbox/internal/jsontypes"
|
||||
type: "Metadata"
|
||||
nullable: true
|
||||
- column: "incidents.metadata"
|
||||
go_type:
|
||||
import: "dynatron.me/x/stillbox/internal/jsontypes"
|
||||
type: "Metadata"
|
||||
nullable: true
|
||||
- column: "pg_catalog.pg_tables.tablename"
|
||||
go_type: string
|
||||
nullable: false
|
||||
|
|
Loading…
Reference in a new issue