M2-T04: add single-row record CRUD API (patch/delete)
- PATCH/DELETE /api/locations/{person}/{datetime} and /api/poo/{timestamp}
- update only non-PK fields (PK immutable); 404 on missing PK
- delete scoped to exact full PK with rowcount guard (0->404, 1->ok);
no batch/truncate/drop path
- session + CSRF protected; bare ingestion endpoints untouched
- service helpers in app/services/location.py and poo.py; regenerate openapi/
- tests/test_api_record_crud.py
This commit is contained in:
@@ -542,6 +542,262 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/locations/{person}/{datetime}": {
|
||||
"patch": {
|
||||
"tags": [
|
||||
"api-data"
|
||||
],
|
||||
"summary": "Patch Location",
|
||||
"description": "Update the non-PK fields of a single location record.\n\n- ``person`` and ``datetime`` identify the row (composite PK) and are immutable.\n- Only ``latitude``, ``longitude``, and ``altitude`` may be updated.\n- Omitted body fields are left unchanged.\n- Returns **404** if the PK does not exist.",
|
||||
"operationId": "patch_location_api_locations__person___datetime__patch",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "person",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Person"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "datetime",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Datetime"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "X-CSRF-Token",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Csrf-Token"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LocationUpdateRequest",
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LocationRecord"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"api-data"
|
||||
],
|
||||
"summary": "Delete Location Record",
|
||||
"description": "Delete the single location record identified by its composite PK.\n\n- Exactly one row is deleted; **404** if the PK does not exist.\n- No batch delete / truncate path is available.",
|
||||
"operationId": "delete_location_record_api_locations__person___datetime__delete",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "person",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Person"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "datetime",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Datetime"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "X-CSRF-Token",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Csrf-Token"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/poo/{timestamp}": {
|
||||
"patch": {
|
||||
"tags": [
|
||||
"api-data"
|
||||
],
|
||||
"summary": "Patch Poo",
|
||||
"description": "Update the non-PK fields of a single poo record.\n\n- ``timestamp`` is the PK and is immutable.\n- Only ``status``, ``latitude``, and ``longitude`` may be updated.\n- Omitted body fields are left unchanged.\n- Returns **404** if the PK does not exist.",
|
||||
"operationId": "patch_poo_api_poo__timestamp__patch",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "timestamp",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Timestamp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "X-CSRF-Token",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Csrf-Token"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PooUpdateRequest",
|
||||
"default": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PooRecord"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"api-data"
|
||||
],
|
||||
"summary": "Delete Poo",
|
||||
"description": "Delete the single poo record identified by its PK.\n\n- Exactly one row is deleted; **404** if the PK does not exist.\n- No batch delete / truncate path is available.",
|
||||
"operationId": "delete_poo_api_poo__timestamp__delete",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "timestamp",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"title": "Timestamp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "X-CSRF-Token",
|
||||
"in": "header",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "X-Csrf-Token"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Successful Response"
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/HTTPValidationError"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/session": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -1075,6 +1331,46 @@
|
||||
],
|
||||
"title": "LocationRecord"
|
||||
},
|
||||
"LocationUpdateRequest": {
|
||||
"properties": {
|
||||
"latitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Latitude"
|
||||
},
|
||||
"longitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Longitude"
|
||||
},
|
||||
"altitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Altitude"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "LocationUpdateRequest",
|
||||
"description": "PATCH body for a location record — all fields optional; PK fields excluded."
|
||||
},
|
||||
"LocationsResponse": {
|
||||
"properties": {
|
||||
"items": {
|
||||
@@ -1196,6 +1492,46 @@
|
||||
],
|
||||
"title": "PooResponse"
|
||||
},
|
||||
"PooUpdateRequest": {
|
||||
"properties": {
|
||||
"status": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Status"
|
||||
},
|
||||
"latitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Latitude"
|
||||
},
|
||||
"longitude": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"title": "Longitude"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"title": "PooUpdateRequest",
|
||||
"description": "PATCH body for a poo record — all fields optional; PK field excluded."
|
||||
},
|
||||
"PublicIPCheckResponse": {
|
||||
"properties": {
|
||||
"status": {
|
||||
|
||||
@@ -364,6 +364,189 @@ paths:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/api/locations/{person}/{datetime}:
|
||||
patch:
|
||||
tags:
|
||||
- api-data
|
||||
summary: Patch Location
|
||||
description: 'Update the non-PK fields of a single location record.
|
||||
|
||||
|
||||
- ``person`` and ``datetime`` identify the row (composite PK) and are immutable.
|
||||
|
||||
- Only ``latitude``, ``longitude``, and ``altitude`` may be updated.
|
||||
|
||||
- Omitted body fields are left unchanged.
|
||||
|
||||
- Returns **404** if the PK does not exist.'
|
||||
operationId: patch_location_api_locations__person___datetime__patch
|
||||
parameters:
|
||||
- name: person
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Person
|
||||
- name: datetime
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Datetime
|
||||
- name: X-CSRF-Token
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
title: X-Csrf-Token
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LocationUpdateRequest'
|
||||
default: {}
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LocationRecord'
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
delete:
|
||||
tags:
|
||||
- api-data
|
||||
summary: Delete Location Record
|
||||
description: 'Delete the single location record identified by its composite
|
||||
PK.
|
||||
|
||||
|
||||
- Exactly one row is deleted; **404** if the PK does not exist.
|
||||
|
||||
- No batch delete / truncate path is available.'
|
||||
operationId: delete_location_record_api_locations__person___datetime__delete
|
||||
parameters:
|
||||
- name: person
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Person
|
||||
- name: datetime
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Datetime
|
||||
- name: X-CSRF-Token
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
title: X-Csrf-Token
|
||||
responses:
|
||||
'204':
|
||||
description: Successful Response
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/api/poo/{timestamp}:
|
||||
patch:
|
||||
tags:
|
||||
- api-data
|
||||
summary: Patch Poo
|
||||
description: 'Update the non-PK fields of a single poo record.
|
||||
|
||||
|
||||
- ``timestamp`` is the PK and is immutable.
|
||||
|
||||
- Only ``status``, ``latitude``, and ``longitude`` may be updated.
|
||||
|
||||
- Omitted body fields are left unchanged.
|
||||
|
||||
- Returns **404** if the PK does not exist.'
|
||||
operationId: patch_poo_api_poo__timestamp__patch
|
||||
parameters:
|
||||
- name: timestamp
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Timestamp
|
||||
- name: X-CSRF-Token
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
title: X-Csrf-Token
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PooUpdateRequest'
|
||||
default: {}
|
||||
responses:
|
||||
'200':
|
||||
description: Successful Response
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PooRecord'
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
delete:
|
||||
tags:
|
||||
- api-data
|
||||
summary: Delete Poo
|
||||
description: 'Delete the single poo record identified by its PK.
|
||||
|
||||
|
||||
- Exactly one row is deleted; **404** if the PK does not exist.
|
||||
|
||||
- No batch delete / truncate path is available.'
|
||||
operationId: delete_poo_api_poo__timestamp__delete
|
||||
parameters:
|
||||
- name: timestamp
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
title: Timestamp
|
||||
- name: X-CSRF-Token
|
||||
in: header
|
||||
required: false
|
||||
schema:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
title: X-Csrf-Token
|
||||
responses:
|
||||
'204':
|
||||
description: Successful Response
|
||||
'422':
|
||||
description: Validation Error
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HTTPValidationError'
|
||||
/api/session:
|
||||
get:
|
||||
tags:
|
||||
@@ -735,6 +918,27 @@ components:
|
||||
- longitude
|
||||
- altitude
|
||||
title: LocationRecord
|
||||
LocationUpdateRequest:
|
||||
properties:
|
||||
latitude:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: 'null'
|
||||
title: Latitude
|
||||
longitude:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: 'null'
|
||||
title: Longitude
|
||||
altitude:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: 'null'
|
||||
title: Altitude
|
||||
type: object
|
||||
title: LocationUpdateRequest
|
||||
description: PATCH body for a location record — all fields optional; PK fields
|
||||
excluded.
|
||||
LocationsResponse:
|
||||
properties:
|
||||
items:
|
||||
@@ -824,6 +1028,26 @@ components:
|
||||
- limit
|
||||
- offset
|
||||
title: PooResponse
|
||||
PooUpdateRequest:
|
||||
properties:
|
||||
status:
|
||||
anyOf:
|
||||
- type: string
|
||||
- type: 'null'
|
||||
title: Status
|
||||
latitude:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: 'null'
|
||||
title: Latitude
|
||||
longitude:
|
||||
anyOf:
|
||||
- type: number
|
||||
- type: 'null'
|
||||
title: Longitude
|
||||
type: object
|
||||
title: PooUpdateRequest
|
||||
description: PATCH body for a poo record — all fields optional; PK field excluded.
|
||||
PublicIPCheckResponse:
|
||||
properties:
|
||||
status:
|
||||
|
||||
Reference in New Issue
Block a user