736 lines
18 KiB
YAML
736 lines
18 KiB
YAML
openapi: 3.0.3
|
|
info:
|
|
title: Dutchie Analytics API
|
|
description: |
|
|
Comprehensive REST API for Arizona dispensary data aggregation and analytics.
|
|
|
|
## Features
|
|
- Product data with high-resolution images (2000x2000)
|
|
- Brand tracking and market share analysis
|
|
- Daily specials grouped by date
|
|
- Price history and trend analysis
|
|
- Inventory tracking (in stock/out of stock)
|
|
- Real-time scraping with proxy management
|
|
|
|
## Authentication
|
|
All endpoints require JWT authentication via Bearer token.
|
|
|
|
## Rate Limiting
|
|
- Standard users: 100 requests/minute
|
|
- Admin users: 500 requests/minute
|
|
|
|
## Image Storage
|
|
All images are stored in MinIO (S3-compatible) and served via CDN.
|
|
version: 1.0.0
|
|
contact:
|
|
name: API Support
|
|
email: support@dutchieanalytics.com
|
|
license:
|
|
name: Proprietary
|
|
|
|
servers:
|
|
- url: http://localhost:3010/api
|
|
description: Development server
|
|
- url: https://api.dutchieanalytics.com/api
|
|
description: Production server
|
|
|
|
components:
|
|
securitySchemes:
|
|
bearerAuth:
|
|
type: http
|
|
scheme: bearer
|
|
bearerFormat: JWT
|
|
|
|
schemas:
|
|
Store:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
example: 1
|
|
name:
|
|
type: string
|
|
example: "Curaleaf - Phoenix"
|
|
slug:
|
|
type: string
|
|
example: "curaleaf-az-phoenix"
|
|
dutchie_url:
|
|
type: string
|
|
format: uri
|
|
example: "https://dutchie.com/dispensary/curaleaf-phoenix"
|
|
logo_url:
|
|
type: string
|
|
format: uri
|
|
nullable: true
|
|
active:
|
|
type: boolean
|
|
example: true
|
|
scrape_enabled:
|
|
type: boolean
|
|
example: true
|
|
product_count:
|
|
type: integer
|
|
example: 345
|
|
category_count:
|
|
type: integer
|
|
example: 12
|
|
last_scraped_at:
|
|
type: string
|
|
format: date-time
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
updated_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
Product:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
example: 1
|
|
dutchie_product_id:
|
|
type: string
|
|
example: "12345"
|
|
store_id:
|
|
type: integer
|
|
example: 1
|
|
category_id:
|
|
type: integer
|
|
nullable: true
|
|
example: 5
|
|
name:
|
|
type: string
|
|
example: "Blue Dream 3.5g"
|
|
brand:
|
|
type: string
|
|
nullable: true
|
|
example: "Cresco Labs"
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
price:
|
|
type: number
|
|
format: float
|
|
nullable: true
|
|
example: 45.00
|
|
weight:
|
|
type: string
|
|
nullable: true
|
|
example: "3.5g"
|
|
thc_percentage:
|
|
type: number
|
|
format: float
|
|
nullable: true
|
|
example: 24.5
|
|
cbd_percentage:
|
|
type: number
|
|
format: float
|
|
nullable: true
|
|
example: 0.5
|
|
strain_type:
|
|
type: string
|
|
nullable: true
|
|
enum: [indica, sativa, hybrid]
|
|
example: "hybrid"
|
|
in_stock:
|
|
type: boolean
|
|
example: true
|
|
image_url:
|
|
type: string
|
|
format: uri
|
|
nullable: true
|
|
description: "Original Dutchie image URL"
|
|
image_url_full:
|
|
type: string
|
|
format: uri
|
|
nullable: true
|
|
description: "High-resolution image from MinIO (2000x2000)"
|
|
local_image_path:
|
|
type: string
|
|
nullable: true
|
|
description: "MinIO storage path"
|
|
thumbnail_path:
|
|
type: string
|
|
nullable: true
|
|
description: "Thumbnail image path (300x300)"
|
|
medium_path:
|
|
type: string
|
|
nullable: true
|
|
description: "Medium image path (800x800)"
|
|
full_path:
|
|
type: string
|
|
nullable: true
|
|
description: "Full resolution image path (2000x2000)"
|
|
dutchie_url:
|
|
type: string
|
|
format: uri
|
|
nullable: true
|
|
store_name:
|
|
type: string
|
|
example: "Curaleaf - Phoenix"
|
|
category_name:
|
|
type: string
|
|
nullable: true
|
|
example: "Flower"
|
|
last_seen_at:
|
|
type: string
|
|
format: date-time
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
Category:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
example: 1
|
|
store_id:
|
|
type: integer
|
|
example: 1
|
|
parent_id:
|
|
type: integer
|
|
nullable: true
|
|
example: null
|
|
name:
|
|
type: string
|
|
example: "Flower"
|
|
slug:
|
|
type: string
|
|
example: "flower"
|
|
dutchie_url:
|
|
type: string
|
|
format: uri
|
|
nullable: true
|
|
display_order:
|
|
type: integer
|
|
example: 1
|
|
product_count:
|
|
type: integer
|
|
example: 145
|
|
parent_name:
|
|
type: string
|
|
nullable: true
|
|
created_at:
|
|
type: string
|
|
format: date-time
|
|
|
|
Brand:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
example: "Cresco Labs"
|
|
|
|
Special:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
store_id:
|
|
type: integer
|
|
product_id:
|
|
type: integer
|
|
nullable: true
|
|
name:
|
|
type: string
|
|
example: "$5 OFF Blue Dream"
|
|
description:
|
|
type: string
|
|
nullable: true
|
|
discount_amount:
|
|
type: number
|
|
format: float
|
|
nullable: true
|
|
discount_percentage:
|
|
type: number
|
|
format: float
|
|
nullable: true
|
|
valid_date:
|
|
type: string
|
|
format: date
|
|
product_name:
|
|
type: string
|
|
nullable: true
|
|
product_image:
|
|
type: string
|
|
format: uri
|
|
nullable: true
|
|
|
|
Error:
|
|
type: object
|
|
properties:
|
|
error:
|
|
type: string
|
|
example: "Resource not found"
|
|
|
|
security:
|
|
- bearerAuth: []
|
|
|
|
paths:
|
|
/auth/login:
|
|
post:
|
|
tags:
|
|
- Authentication
|
|
summary: Login
|
|
description: Authenticate user and receive JWT token
|
|
security: []
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- email
|
|
- password
|
|
properties:
|
|
email:
|
|
type: string
|
|
format: email
|
|
example: "admin@example.com"
|
|
password:
|
|
type: string
|
|
format: password
|
|
example: "password123"
|
|
responses:
|
|
'200':
|
|
description: Login successful
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
token:
|
|
type: string
|
|
user:
|
|
type: object
|
|
properties:
|
|
id:
|
|
type: integer
|
|
email:
|
|
type: string
|
|
role:
|
|
type: string
|
|
enum: [superadmin, admin, user]
|
|
'401':
|
|
description: Invalid credentials
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Error'
|
|
|
|
/stores:
|
|
get:
|
|
tags:
|
|
- Stores
|
|
summary: Get all stores
|
|
description: Retrieve list of all dispensary stores with product and category counts
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
stores:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Store'
|
|
|
|
post:
|
|
tags:
|
|
- Stores
|
|
summary: Create store
|
|
description: Add a new dispensary store (admin only)
|
|
requestBody:
|
|
required: true
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
required:
|
|
- name
|
|
- slug
|
|
- dutchie_url
|
|
properties:
|
|
name:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
dutchie_url:
|
|
type: string
|
|
format: uri
|
|
active:
|
|
type: boolean
|
|
default: true
|
|
scrape_enabled:
|
|
type: boolean
|
|
default: true
|
|
responses:
|
|
'201':
|
|
description: Store created
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Store'
|
|
'401':
|
|
description: Unauthorized
|
|
|
|
/stores/{id}:
|
|
get:
|
|
tags:
|
|
- Stores
|
|
summary: Get single store
|
|
description: Retrieve detailed information about a specific store
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
example: 1
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Store'
|
|
'404':
|
|
description: Store not found
|
|
|
|
put:
|
|
tags:
|
|
- Stores
|
|
summary: Update store
|
|
description: Update store information (admin only)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
name:
|
|
type: string
|
|
slug:
|
|
type: string
|
|
dutchie_url:
|
|
type: string
|
|
active:
|
|
type: boolean
|
|
scrape_enabled:
|
|
type: boolean
|
|
responses:
|
|
'200':
|
|
description: Store updated
|
|
content:
|
|
application/json:
|
|
schema:
|
|
$ref: '#/components/schemas/Store'
|
|
|
|
delete:
|
|
tags:
|
|
- Stores
|
|
summary: Delete store
|
|
description: Delete a store (superadmin only)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
'200':
|
|
description: Store deleted
|
|
|
|
/stores/{id}/brands:
|
|
get:
|
|
tags:
|
|
- Stores
|
|
summary: Get store brands
|
|
description: Get list of all brands available at a store
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
brands:
|
|
type: array
|
|
items:
|
|
type: string
|
|
example: ["Cresco Labs", "Select", "Timeless"]
|
|
|
|
/stores/{id}/specials:
|
|
get:
|
|
tags:
|
|
- Stores
|
|
summary: Get store specials
|
|
description: Get daily specials for a store on a specific date
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
- name: date
|
|
in: query
|
|
required: false
|
|
schema:
|
|
type: string
|
|
format: date
|
|
description: Date for specials (defaults to today)
|
|
example: "2025-01-15"
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
specials:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Special'
|
|
date:
|
|
type: string
|
|
format: date
|
|
|
|
/stores/{id}/scrape:
|
|
post:
|
|
tags:
|
|
- Stores
|
|
summary: Trigger store scrape
|
|
description: Start scraping process for a store (admin only)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
requestBody:
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
parallel:
|
|
type: integer
|
|
default: 3
|
|
description: Number of parallel scrapers
|
|
userAgent:
|
|
type: string
|
|
description: Custom user agent (optional)
|
|
responses:
|
|
'200':
|
|
description: Scrape started
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
message:
|
|
type: string
|
|
parallel:
|
|
type: integer
|
|
userAgent:
|
|
type: string
|
|
|
|
/stores/{id}/discover-categories:
|
|
post:
|
|
tags:
|
|
- Stores
|
|
summary: Discover categories
|
|
description: Discover and import categories from Dutchie menu (admin only)
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
'200':
|
|
description: Category discovery started
|
|
|
|
/products:
|
|
get:
|
|
tags:
|
|
- Products
|
|
summary: Get products
|
|
description: |
|
|
Retrieve products with filtering, searching, and pagination.
|
|
|
|
## Field Selection
|
|
Use the `fields` parameter to select specific fields:
|
|
- `fields=id,name,price` - Only return specified fields
|
|
- Reduces payload size and improves performance
|
|
parameters:
|
|
- name: store_id
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
description: Filter by store ID
|
|
- name: category_id
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
description: Filter by category ID
|
|
- name: in_stock
|
|
in: query
|
|
schema:
|
|
type: boolean
|
|
description: Filter by stock availability
|
|
- name: search
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Search by product name or brand
|
|
- name: limit
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 50
|
|
maximum: 1000
|
|
description: Number of results per page
|
|
- name: offset
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
default: 0
|
|
description: Pagination offset
|
|
- name: fields
|
|
in: query
|
|
schema:
|
|
type: string
|
|
description: Comma-separated list of fields to return
|
|
example: "id,name,price,brand,in_stock,image_url_full"
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
products:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Product'
|
|
total:
|
|
type: integer
|
|
description: Total number of products matching filters
|
|
limit:
|
|
type: integer
|
|
offset:
|
|
type: integer
|
|
|
|
/products/{id}:
|
|
get:
|
|
tags:
|
|
- Products
|
|
summary: Get single product
|
|
description: Retrieve detailed information about a specific product
|
|
parameters:
|
|
- name: id
|
|
in: path
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
product:
|
|
$ref: '#/components/schemas/Product'
|
|
|
|
/categories:
|
|
get:
|
|
tags:
|
|
- Categories
|
|
summary: Get categories
|
|
description: Retrieve flat list of categories
|
|
parameters:
|
|
- name: store_id
|
|
in: query
|
|
schema:
|
|
type: integer
|
|
description: Filter by store ID
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
categories:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Category'
|
|
|
|
/categories/tree:
|
|
get:
|
|
tags:
|
|
- Categories
|
|
summary: Get category tree
|
|
description: Retrieve hierarchical category structure for a store
|
|
parameters:
|
|
- name: store_id
|
|
in: query
|
|
required: true
|
|
schema:
|
|
type: integer
|
|
responses:
|
|
'200':
|
|
description: Successful response
|
|
content:
|
|
application/json:
|
|
schema:
|
|
type: object
|
|
properties:
|
|
tree:
|
|
type: array
|
|
items:
|
|
allOf:
|
|
- $ref: '#/components/schemas/Category'
|
|
- type: object
|
|
properties:
|
|
children:
|
|
type: array
|
|
items:
|
|
$ref: '#/components/schemas/Category'
|
|
|
|
tags:
|
|
- name: Authentication
|
|
description: User authentication endpoints
|
|
- name: Stores
|
|
description: Dispensary store management
|
|
- name: Products
|
|
description: Product catalog and inventory
|
|
- name: Categories
|
|
description: Product categories and hierarchy
|