System Model¶
Domain Model¶
Scientific Taxonomy¶
Plant records are tied to a centralized taxonomic layer (not disclosed publicly).
The model supports families, genera, species, cultivars, grexes, global taxa, and tenant-owned local cultivated entities.
Data Model¶
High-Level Data Model¶
erDiagram
ORGANIZATION ||--o{ ORG_UNIT : contains
ORGANIZATION ||--o{ PLANT : owns
ORGANIZATION ||--o{ PLACE : manages
ORGANIZATION ||--o{ PLANT_LIST : maintains
ORGANIZATION ||--o{ PUBLIC_PAGE : publishes
ORG_UNIT ||--o{ PLANT : curates
PLACE ||--o{ PLACE : contains
PLACE ||--o{ PLANT : locates
PLANT }o--|| SYSTEM_TAXON : "required taxon_id"
PLANT ||--o{ PHOTO : documents
PLANT }o--o{ PLANT_LIST : included_in
SYSTEM_TAXON ||--o| GLOBAL_TAXON : "species subtype"
SYSTEM_TAXON ||--o| CULTIVATED_ENTITY : "cultivar/grex subtype"
PUBLIC_PAGE ||--o{ PLANT_LIST : exposes
PUBLIC_PAGE ||--o{ PLANT : exposes_selected
ORGANIZATION {
uuid id PK
string public_name
string slug
string visibility
}
ORG_UNIT {
uuid id PK
uuid organization_id FK
uuid parent_id FK
string name
}
PLANT {
uuid id PK
uuid organization_id FK
uuid org_unit_id FK
uuid place_id FK
uuid taxon_id FK "mandatory"
string accession_number
string individual_code
string status
string provenance_type
geometry point
jsonb custom_fields
}
SYSTEM_TAXON {
uuid id PK
string display_name
string normalized_name
string taxon_type
string rank
}
GLOBAL_TAXON {
uuid id PK
uuid system_taxon_id FK
string id
string ipni_id
string family
string genus
string species_epithet
string scientific_name
}
CULTIVATED_ENTITY {
uuid id PK
uuid system_taxon_id FK
string genus
string cultivar_or_grex_name
string breeder_name
string registrar_name
int registration_year
string visibility_scope
}
PLACE {
uuid id PK
uuid organization_id FK
uuid parent_id FK
string name
geometry point
geometry polygon
}
PLANT_LIST {
uuid id PK
uuid organization_id FK
string name
string list_type
string visibility
}
PHOTO {
uuid id PK
uuid PLANT_id FK
string caption
string photo_tag
boolean publication_allowed
}
PUBLIC_PAGE {
uuid id PK
uuid organization_id FK
string slug
boolean visible
} Key Model Idea¶
The central entity of the system is Plant, a digital twin of a specific plant in a living collection. This is not merely a reference row or an abstract species, but an accession record for a specific specimen: with inventory number, status, placement location, organization ownership, photos, lists, and public representation.
The mandatory attribute taxon_id is the core of the model. It links each plant instance to the unified root entity SystemTaxon. Thanks to this, all operational scenarios—tracking, import, search, lists, exchange, QR pages, reporting, and the public map—work not with an arbitrary text plant name, but with a stable taxon identifier.
SystemTaxon acts as the common root entity for two major taxonomic contours:
- globalTaxon — species-level taxa from an authoritative catalog.
- CultivatedEntity — cultivars and grexes that may be recognized global records from registrars, or local records created within a specific organization.
This approach lets the user select a plant from a unified taxon lookup without artificially separating species search from cultivar search. For the user it looks like a single plant reference; internally the system preserves the distinction between scientific species taxa, global cultivars, grexes, and organization-local cultivars.
The architectural value is that all collection data becomes comparable across organizations. The same taxon_id can be used in plant cards, collection lists, wishlists, exchange lists, public pages, and reports. This creates a foundation for network scenarios: the system can match which taxa one organization seeks and another is ready to transfer, without relying on unstable text names, synonyms, local variants, and typos.
In the portfolio the model is shown at a high level. Internal auxiliary entities, visibility mechanisms, access tables, cultivar globalization workflows, and implementation details are intentionally omitted.
API Contracts¶
Endpoint names and attributes are intentionally altered for the public space.
API Design Approach¶
The backend exposes a REST/JSON API for managing living plant collections in a multi-tenant SaaS environment. API contracts are organized around domain capabilities: plant inventory, taxonomy lookup, batch operations, import workflows, public pages, media access and exchange scenarios.
Security is enforced at several layers:
- JWT-based authentication through Spring Security.
- Method-level authorization for sensitive operations.
- Tenant isolation through organization/root tenant context.
- Data-level filtering by tenant ownership.
- Bean Validation for request DTOs.
- Controlled public DTOs for anonymous endpoints.
- Soft delete for recoverable destructive actions.
- Centralized error handling with standard HTTP statuses.
1. Search Plant Instances¶
Purpose¶
Retrieve a paginated list of plant instances available to the current user within the active organization context.
Contract¶
GET /api/inventory/plants
Query Parameters¶
keyword optional text search
organization optional tenant/root context
unit optional department or collection unit
list optional list filter
recursive optional flag for nested units
filter optional faceted filters
page pagination parameter
size pagination parameter
sort sorting parameter
Response¶
{
"items": [
{
"id": "uuid",
"displayName": "string",
"taxon": "summary",
"inventoryCode": "string",
"status": "string",
"location": "summary",
"visibility": "string"
}
],
"page": {
"number": 0,
"size": 25,
"totalElements": 0
}
}
Security Notes¶
- Requires authenticated user.
- Result set is restricted to the tenant context available to the current user.
- Query specifications apply tenant filtering before returning data.
- Filter expressions are parsed into allowlisted criteria rather than executed as raw dynamic queries.
- Pagination and sorting use framework-level pageable parameters.
2. Get Plant Instance Details¶
Purpose¶
Retrieve a single plant instance as a digital record of a real plant in a living collection.
Contract¶
GET /api/inventory/plants/{plantId}
Response¶
{
"id": "uuid",
"taxon": {
"id": "uuid",
"displayName": "string",
"type": "species | cultivar | grex"
},
"inventory": {
"accessionNumber": "string",
"individualCode": "string",
"status": "string"
},
"organization": "summary",
"location": "summary",
"photos": ["summary"],
"customFields": {}
}
Security Notes¶
- Requires authenticated user.
- Access is checked against the plant’s tenant ownership and the user’s organization memberships.
- Internal fields are not exposed through public-facing contracts.
- Non-existing or inaccessible resources are returned through standard error responses.
3. Create Plant Instance¶
Purpose¶
Create a new plant instance in an organization’s living collection.
Contract¶
POST /api/inventory/plants
Request¶
{
"taxonId": "uuid",
"organizationUnitId": "uuid",
"placeId": "uuid",
"inventoryData": {},
"status": "string",
"customFields": {}
}
Response¶
201 Created
{
"id": "uuid",
"displayName": "string",
"taxon": "summary",
"inventoryCode": "string",
"status": "string"
}
Security Notes¶
- Requires authenticated user.
- Requires administrative or editor-level access to the target organizational unit.
- Request body is validated before domain processing.
taxonIdis mandatory and must refer to an allowed species, cultivar or grex visible in the current tenant context.- Creation is delegated to the service layer after authorization and validation.
4. Update Plant Instance¶
Purpose¶
Update an existing plant instance, including inventory metadata, taxon reference, status or organizational placement.
Contract¶
PUT /api/inventory/plants/{plantId}
Request¶
{
"taxonId": "uuid",
"organizationUnitId": "uuid",
"placeId": "uuid",
"inventoryData": {},
"status": "string",
"customFields": {}
}
Response¶
{
"id": "uuid",
"displayName": "string",
"taxon": "summary",
"status": "string",
"location": "summary"
}
Security Notes¶
- Requires authenticated user.
- Requires write access to the existing plant record.
- If the plant is moved to another organizational unit, access to both the current and target contexts must be validated.
- Invalid move attempts are rejected with an access error.
- Request validation is performed through typed request DTOs and Bean Validation.
5. Soft Delete Plant Instance¶
Purpose¶
Move a plant record to trash without physically deleting it from the database.
Contract¶
DELETE /api/inventory/plants/{plantId}
Response¶
204 No Content
Security Notes¶
- Requires authenticated user.
- Requires administrative access to the plant’s organizational context.
- Deletion is recoverable by default.
- Soft-deleted records are excluded from normal queries.
- Permanent deletion is restricted to elevated platform-level roles.
6. Batch Operations on Plant Instances¶
Purpose¶
Perform bulk actions on selected plant instances: move, update, clone, generate label data or mark labels as printed.
Contracts¶
POST /api/inventory/plants/batch/move
POST /api/inventory/plants/batch/update
POST /api/inventory/plants/batch/clone
POST /api/inventory/plants/batch/label-data
POST /api/inventory/plants/batch/mark-printed
Request¶
{
"plantIds": ["uuid"],
"operationPayload": {}
}
Response¶
{
"successCount": 0,
"failureCount": 0,
"results": [
{
"id": "uuid",
"status": "success | failed",
"message": "string"
}
]
}
Security Notes¶
- Requires authenticated user.
- Each affected plant must be checked against tenant ownership and user permissions.
- Batch operations should not bypass per-resource authorization.
- Partial success is supported for operations where some records may fail validation or authorization.
- Input collections are validated before processing.
7. Dictionary Lookup for Plant Forms¶
Purpose¶
Return controlled dictionary values used by plant forms, such as lifecycle states, growing conditions or acquisition forms.
Contract¶
GET /api/inventory/dictionaries/{dictionaryType}
Response¶
[
{
"id": "string",
"code": "string",
"label": "string"
}
]
Security Notes¶
- Requires authenticated user for internal dictionaries.
- Dictionary responses contain only safe reference data.
- Values are controlled by the platform or tenant configuration, not arbitrary free text.
8. Taxon Lookup¶
Purpose¶
Provide a unified lookup over species, cultivars and grexes when creating or editing a plant instance.
Contract¶
GET /api/taxonomy/search
Query Parameters¶
query search term
type optional taxon type filter
page pagination parameter
size pagination parameter
Response¶
{
"items": [
{
"id": "uuid",
"displayName": "string",
"taxonType": "species | cultivar | grex",
"source": "reference | global | local"
}
]
}
Security Notes¶
- Global reference species are available across tenants.
- Global cultivated entities are available across tenants.
- Local cultivated entities are visible only to allowed tenant contexts.
- The API hides internal visibility mechanics and exposes only selectable taxon summaries.
9. Smart Import Workflow¶
Purpose¶
Import existing collection data from spreadsheets through a staged workflow.
Contracts¶
POST /api/import/sessions
POST /api/import/sessions/{sessionId}/sheet
POST /api/import/sessions/{sessionId}/mapping
POST /api/import/sessions/{sessionId}/resolve
POST /api/import/sessions/{sessionId}/execute
GET /api/import/sessions/{sessionId}/status
GET /api/import/sessions/{sessionId}/error-report
Workflow¶
Upload file
→ Select sheet
→ Map columns
→ Resolve values
→ Confirm uncertain matches
→ Execute import
→ Review results
Response Example¶
{
"sessionId": "uuid",
"status": "uploaded | mapped | resolving | ready | executing | completed | failed",
"progress": {
"processedRows": 0,
"totalRows": 0
}
}
Security Notes¶
- Requires authenticated user.
- Requires tenant context and import permission.
- Uploaded files are stored outside the relational database.
- Import sessions are tenant-scoped.
- Ambiguous matches require user confirmation.
- Invalid rows are reported without blocking all valid rows.
- Background jobs are used for long-running import stages.
10. Public Plant Page¶
Purpose¶
Expose a safe public view of a plant record, commonly used as a QR label target.
Contract¶
GET /api/public/plants/{publicSlug}
Response¶
{
"displayName": "string",
"taxon": "public summary",
"organization": "public summary",
"photos": ["public media reference"],
"publicDescription": "string"
}
Security Notes¶
- Does not require authentication.
- Returns only public DTOs.
- Excludes internal identifiers, tenant metadata, private notes, internal inventory fields and restricted photos.
- Requires public visibility settings on the owning organization and plant/list context.
- Media access uses short-lived signed URLs or equivalent controlled delivery.
11. Exchange Match Lookup¶
Purpose¶
Find potential exchange matches between one organization’s wishlist and other organizations’ shared exchange lists.
Contract¶
GET /api/exchange/matches/{wishlistId}
Response¶
{
"wishlist": "summary",
"matches": [
{
"taxon": "summary",
"sourceOrganization": "public or shared summary",
"availableMaterial": "summary",
"visibility": "string"
}
]
}
Security Notes¶
- Requires authenticated user.
- User must have access to the source wishlist.
- Candidate matches are limited to data explicitly shared for exchange or visible to the relevant community scope.
- Matching is based on taxon identifiers rather than raw text names.
- Private collection data is not disclosed through the exchange API.
Error Handling Pattern¶
{
"status": 400,
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": []
}
Typical response statuses:
200 OK successful read or update
201 Created resource created
204 No Content successful deletion
400 Bad Request invalid input or domain validation failure
403 Forbidden authenticated but not allowed
404 Not Found resource missing or not visible
409 Conflict state conflict or resource in use
429 Too Many Requests rate limit exceeded
Security Summary¶
The API design combines framework-level and domain-level protection:
- Spring Security authenticates requests and forms user context.
- JWT carries identity; roles and permissions are resolved server-side.
- Method-level checks protect write operations and administrative actions.
- Tenant-scoped queries prevent data leakage between organizations.
- DTO validation blocks invalid requests before business logic execution.
- Public endpoints use separate DTOs with a restricted field set.
- Soft delete protects against accidental data loss.
- Platform-level operations are separated from tenant operations.
- Audit and login events provide traceability for sensitive actions.