docs(api): generate auth docs and archive p00 change
This commit is contained in:
4
backend/.scribe/.filehashes
Normal file
4
backend/.scribe/.filehashes
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# GENERATED. YOU SHOULDN'T MODIFY OR DELETE THIS FILE.
|
||||||
|
# Scribe uses this file to know when you change something manually in your docs.
|
||||||
|
.scribe/intro.md=63d14186b9cbbb0a80ee87cd913db091
|
||||||
|
.scribe/auth.md=5c5a140c89034600ae349aede2a22ec8
|
||||||
7
backend/.scribe/auth.md
Normal file
7
backend/.scribe/auth.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# Authenticating requests
|
||||||
|
|
||||||
|
To authenticate requests, include an **`Authorization`** header with the value **`"Bearer Bearer {token}"`**.
|
||||||
|
|
||||||
|
All authenticated endpoints are marked with a `requires authentication` badge in the documentation below.
|
||||||
|
|
||||||
|
Get tokens from `POST /api/auth/login`, send access token as `Bearer {token}`, and renew with `POST /api/auth/refresh` before access token expiry.
|
||||||
226
backend/.scribe/endpoints.cache/00.yaml
Normal file
226
backend/.scribe/endpoints.cache/00.yaml
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
## Autogenerated by Scribe. DO NOT MODIFY.
|
||||||
|
|
||||||
|
name: Authentication
|
||||||
|
description: |-
|
||||||
|
|
||||||
|
Endpoints for JWT authentication and session lifecycle.
|
||||||
|
endpoints:
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
httpMethods:
|
||||||
|
- POST
|
||||||
|
uri: api/auth/login
|
||||||
|
metadata:
|
||||||
|
custom: []
|
||||||
|
groupName: Authentication
|
||||||
|
groupDescription: |-
|
||||||
|
|
||||||
|
Endpoints for JWT authentication and session lifecycle.
|
||||||
|
subgroup: ''
|
||||||
|
subgroupDescription: ''
|
||||||
|
title: 'Login and get tokens'
|
||||||
|
description: 'Authenticate with email and password to receive an access token and refresh token.'
|
||||||
|
authenticated: true
|
||||||
|
deprecated: false
|
||||||
|
headers:
|
||||||
|
Authorization: 'Bearer Bearer {token}'
|
||||||
|
Content-Type: application/json
|
||||||
|
Accept: application/json
|
||||||
|
urlParameters: []
|
||||||
|
cleanUrlParameters: []
|
||||||
|
queryParameters: []
|
||||||
|
cleanQueryParameters: []
|
||||||
|
bodyParameters:
|
||||||
|
email:
|
||||||
|
custom: []
|
||||||
|
name: email
|
||||||
|
description: 'User email address.'
|
||||||
|
required: true
|
||||||
|
example: user@example.com
|
||||||
|
type: string
|
||||||
|
enumValues: []
|
||||||
|
exampleWasSpecified: true
|
||||||
|
nullable: false
|
||||||
|
deprecated: false
|
||||||
|
password:
|
||||||
|
custom: []
|
||||||
|
name: password
|
||||||
|
description: 'User password.'
|
||||||
|
required: true
|
||||||
|
example: secret123
|
||||||
|
type: string
|
||||||
|
enumValues: []
|
||||||
|
exampleWasSpecified: true
|
||||||
|
nullable: false
|
||||||
|
deprecated: false
|
||||||
|
cleanBodyParameters:
|
||||||
|
email: user@example.com
|
||||||
|
password: secret123
|
||||||
|
fileParameters: []
|
||||||
|
responses:
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 200
|
||||||
|
content: |-
|
||||||
|
{
|
||||||
|
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||||
|
"refresh_token": "abc123def456",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600,
|
||||||
|
"user": {
|
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"name": "Alice Johnson",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"role": "manager"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 401
|
||||||
|
content: '{"message":"Invalid credentials"}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 403
|
||||||
|
content: '{"message":"Account is inactive"}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 422
|
||||||
|
content: '{"errors":{"email":["The email field is required."],"password":["The password field is required."]}}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
responseFields: []
|
||||||
|
auth:
|
||||||
|
- headers
|
||||||
|
- Authorization
|
||||||
|
- 'Bearer Bearer {token}'
|
||||||
|
controller: null
|
||||||
|
method: null
|
||||||
|
route: null
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
httpMethods:
|
||||||
|
- POST
|
||||||
|
uri: api/auth/refresh
|
||||||
|
metadata:
|
||||||
|
custom: []
|
||||||
|
groupName: Authentication
|
||||||
|
groupDescription: |-
|
||||||
|
|
||||||
|
Endpoints for JWT authentication and session lifecycle.
|
||||||
|
subgroup: ''
|
||||||
|
subgroupDescription: ''
|
||||||
|
title: 'Refresh access token'
|
||||||
|
description: 'Exchange a valid refresh token for a new access token and refresh token pair.'
|
||||||
|
authenticated: true
|
||||||
|
deprecated: false
|
||||||
|
headers:
|
||||||
|
Authorization: 'Bearer Bearer {token}'
|
||||||
|
Content-Type: application/json
|
||||||
|
Accept: application/json
|
||||||
|
urlParameters: []
|
||||||
|
cleanUrlParameters: []
|
||||||
|
queryParameters: []
|
||||||
|
cleanQueryParameters: []
|
||||||
|
bodyParameters:
|
||||||
|
refresh_token:
|
||||||
|
custom: []
|
||||||
|
name: refresh_token
|
||||||
|
description: 'Refresh token returned by login.'
|
||||||
|
required: true
|
||||||
|
example: abc123def456
|
||||||
|
type: string
|
||||||
|
enumValues: []
|
||||||
|
exampleWasSpecified: true
|
||||||
|
nullable: false
|
||||||
|
deprecated: false
|
||||||
|
cleanBodyParameters:
|
||||||
|
refresh_token: abc123def456
|
||||||
|
fileParameters: []
|
||||||
|
responses:
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 200
|
||||||
|
content: |-
|
||||||
|
{
|
||||||
|
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||||
|
"refresh_token": "newtoken123",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600
|
||||||
|
}
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 401
|
||||||
|
content: '{"message":"Invalid or expired refresh token"}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
responseFields: []
|
||||||
|
auth:
|
||||||
|
- headers
|
||||||
|
- Authorization
|
||||||
|
- 'Bearer Bearer {token}'
|
||||||
|
controller: null
|
||||||
|
method: null
|
||||||
|
route: null
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
httpMethods:
|
||||||
|
- POST
|
||||||
|
uri: api/auth/logout
|
||||||
|
metadata:
|
||||||
|
custom: []
|
||||||
|
groupName: Authentication
|
||||||
|
groupDescription: |-
|
||||||
|
|
||||||
|
Endpoints for JWT authentication and session lifecycle.
|
||||||
|
subgroup: ''
|
||||||
|
subgroupDescription: ''
|
||||||
|
title: 'Logout current session'
|
||||||
|
description: 'Invalidate a refresh token and end the active authenticated session.'
|
||||||
|
authenticated: true
|
||||||
|
deprecated: false
|
||||||
|
headers:
|
||||||
|
Authorization: 'Bearer Bearer {token}'
|
||||||
|
Content-Type: application/json
|
||||||
|
Accept: application/json
|
||||||
|
urlParameters: []
|
||||||
|
cleanUrlParameters: []
|
||||||
|
queryParameters: []
|
||||||
|
cleanQueryParameters: []
|
||||||
|
bodyParameters:
|
||||||
|
refresh_token:
|
||||||
|
custom: []
|
||||||
|
name: refresh_token
|
||||||
|
description: 'Optional refresh token to invalidate immediately.'
|
||||||
|
required: false
|
||||||
|
example: abc123def456
|
||||||
|
type: string
|
||||||
|
enumValues: []
|
||||||
|
exampleWasSpecified: true
|
||||||
|
nullable: false
|
||||||
|
deprecated: false
|
||||||
|
cleanBodyParameters:
|
||||||
|
refresh_token: abc123def456
|
||||||
|
fileParameters: []
|
||||||
|
responses:
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 200
|
||||||
|
content: '{"message":"Logged out successfully"}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
responseFields: []
|
||||||
|
auth:
|
||||||
|
- headers
|
||||||
|
- Authorization
|
||||||
|
- 'Bearer Bearer {token}'
|
||||||
|
controller: null
|
||||||
|
method: null
|
||||||
|
route: null
|
||||||
224
backend/.scribe/endpoints/00.yaml
Normal file
224
backend/.scribe/endpoints/00.yaml
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
name: Authentication
|
||||||
|
description: |-
|
||||||
|
|
||||||
|
Endpoints for JWT authentication and session lifecycle.
|
||||||
|
endpoints:
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
httpMethods:
|
||||||
|
- POST
|
||||||
|
uri: api/auth/login
|
||||||
|
metadata:
|
||||||
|
custom: []
|
||||||
|
groupName: Authentication
|
||||||
|
groupDescription: |-
|
||||||
|
|
||||||
|
Endpoints for JWT authentication and session lifecycle.
|
||||||
|
subgroup: ''
|
||||||
|
subgroupDescription: ''
|
||||||
|
title: 'Login and get tokens'
|
||||||
|
description: 'Authenticate with email and password to receive an access token and refresh token.'
|
||||||
|
authenticated: true
|
||||||
|
deprecated: false
|
||||||
|
headers:
|
||||||
|
Authorization: 'Bearer Bearer {token}'
|
||||||
|
Content-Type: application/json
|
||||||
|
Accept: application/json
|
||||||
|
urlParameters: []
|
||||||
|
cleanUrlParameters: []
|
||||||
|
queryParameters: []
|
||||||
|
cleanQueryParameters: []
|
||||||
|
bodyParameters:
|
||||||
|
email:
|
||||||
|
custom: []
|
||||||
|
name: email
|
||||||
|
description: 'User email address.'
|
||||||
|
required: true
|
||||||
|
example: user@example.com
|
||||||
|
type: string
|
||||||
|
enumValues: []
|
||||||
|
exampleWasSpecified: true
|
||||||
|
nullable: false
|
||||||
|
deprecated: false
|
||||||
|
password:
|
||||||
|
custom: []
|
||||||
|
name: password
|
||||||
|
description: 'User password.'
|
||||||
|
required: true
|
||||||
|
example: secret123
|
||||||
|
type: string
|
||||||
|
enumValues: []
|
||||||
|
exampleWasSpecified: true
|
||||||
|
nullable: false
|
||||||
|
deprecated: false
|
||||||
|
cleanBodyParameters:
|
||||||
|
email: user@example.com
|
||||||
|
password: secret123
|
||||||
|
fileParameters: []
|
||||||
|
responses:
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 200
|
||||||
|
content: |-
|
||||||
|
{
|
||||||
|
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||||
|
"refresh_token": "abc123def456",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600,
|
||||||
|
"user": {
|
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"name": "Alice Johnson",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"role": "manager"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 401
|
||||||
|
content: '{"message":"Invalid credentials"}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 403
|
||||||
|
content: '{"message":"Account is inactive"}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 422
|
||||||
|
content: '{"errors":{"email":["The email field is required."],"password":["The password field is required."]}}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
responseFields: []
|
||||||
|
auth:
|
||||||
|
- headers
|
||||||
|
- Authorization
|
||||||
|
- 'Bearer Bearer {token}'
|
||||||
|
controller: null
|
||||||
|
method: null
|
||||||
|
route: null
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
httpMethods:
|
||||||
|
- POST
|
||||||
|
uri: api/auth/refresh
|
||||||
|
metadata:
|
||||||
|
custom: []
|
||||||
|
groupName: Authentication
|
||||||
|
groupDescription: |-
|
||||||
|
|
||||||
|
Endpoints for JWT authentication and session lifecycle.
|
||||||
|
subgroup: ''
|
||||||
|
subgroupDescription: ''
|
||||||
|
title: 'Refresh access token'
|
||||||
|
description: 'Exchange a valid refresh token for a new access token and refresh token pair.'
|
||||||
|
authenticated: true
|
||||||
|
deprecated: false
|
||||||
|
headers:
|
||||||
|
Authorization: 'Bearer Bearer {token}'
|
||||||
|
Content-Type: application/json
|
||||||
|
Accept: application/json
|
||||||
|
urlParameters: []
|
||||||
|
cleanUrlParameters: []
|
||||||
|
queryParameters: []
|
||||||
|
cleanQueryParameters: []
|
||||||
|
bodyParameters:
|
||||||
|
refresh_token:
|
||||||
|
custom: []
|
||||||
|
name: refresh_token
|
||||||
|
description: 'Refresh token returned by login.'
|
||||||
|
required: true
|
||||||
|
example: abc123def456
|
||||||
|
type: string
|
||||||
|
enumValues: []
|
||||||
|
exampleWasSpecified: true
|
||||||
|
nullable: false
|
||||||
|
deprecated: false
|
||||||
|
cleanBodyParameters:
|
||||||
|
refresh_token: abc123def456
|
||||||
|
fileParameters: []
|
||||||
|
responses:
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 200
|
||||||
|
content: |-
|
||||||
|
{
|
||||||
|
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||||
|
"refresh_token": "newtoken123",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600
|
||||||
|
}
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 401
|
||||||
|
content: '{"message":"Invalid or expired refresh token"}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
responseFields: []
|
||||||
|
auth:
|
||||||
|
- headers
|
||||||
|
- Authorization
|
||||||
|
- 'Bearer Bearer {token}'
|
||||||
|
controller: null
|
||||||
|
method: null
|
||||||
|
route: null
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
httpMethods:
|
||||||
|
- POST
|
||||||
|
uri: api/auth/logout
|
||||||
|
metadata:
|
||||||
|
custom: []
|
||||||
|
groupName: Authentication
|
||||||
|
groupDescription: |-
|
||||||
|
|
||||||
|
Endpoints for JWT authentication and session lifecycle.
|
||||||
|
subgroup: ''
|
||||||
|
subgroupDescription: ''
|
||||||
|
title: 'Logout current session'
|
||||||
|
description: 'Invalidate a refresh token and end the active authenticated session.'
|
||||||
|
authenticated: true
|
||||||
|
deprecated: false
|
||||||
|
headers:
|
||||||
|
Authorization: 'Bearer Bearer {token}'
|
||||||
|
Content-Type: application/json
|
||||||
|
Accept: application/json
|
||||||
|
urlParameters: []
|
||||||
|
cleanUrlParameters: []
|
||||||
|
queryParameters: []
|
||||||
|
cleanQueryParameters: []
|
||||||
|
bodyParameters:
|
||||||
|
refresh_token:
|
||||||
|
custom: []
|
||||||
|
name: refresh_token
|
||||||
|
description: 'Optional refresh token to invalidate immediately.'
|
||||||
|
required: false
|
||||||
|
example: abc123def456
|
||||||
|
type: string
|
||||||
|
enumValues: []
|
||||||
|
exampleWasSpecified: true
|
||||||
|
nullable: false
|
||||||
|
deprecated: false
|
||||||
|
cleanBodyParameters:
|
||||||
|
refresh_token: abc123def456
|
||||||
|
fileParameters: []
|
||||||
|
responses:
|
||||||
|
-
|
||||||
|
custom: []
|
||||||
|
status: 200
|
||||||
|
content: '{"message":"Logged out successfully"}'
|
||||||
|
headers: []
|
||||||
|
description: ''
|
||||||
|
responseFields: []
|
||||||
|
auth:
|
||||||
|
- headers
|
||||||
|
- Authorization
|
||||||
|
- 'Bearer Bearer {token}'
|
||||||
|
controller: null
|
||||||
|
method: null
|
||||||
|
route: null
|
||||||
53
backend/.scribe/endpoints/custom.0.yaml
Normal file
53
backend/.scribe/endpoints/custom.0.yaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
# To include an endpoint that isn't a part of your Laravel app (or belongs to a vendor package),
|
||||||
|
# you can define it in a custom.*.yaml file, like this one.
|
||||||
|
# Each custom file should contain an array of endpoints. Here's an example:
|
||||||
|
# See https://scribe.knuckles.wtf/laravel/documenting/custom-endpoints#extra-sorting-groups-in-custom-endpoint-files for more options
|
||||||
|
|
||||||
|
#- httpMethods:
|
||||||
|
# - POST
|
||||||
|
# uri: api/doSomething/{param}
|
||||||
|
# metadata:
|
||||||
|
# groupName: The group the endpoint belongs to. Can be a new group or an existing group.
|
||||||
|
# groupDescription: A description for the group. You don't need to set this for every endpoint; once is enough.
|
||||||
|
# subgroup: You can add a subgroup, too.
|
||||||
|
# title: Do something
|
||||||
|
# description: 'This endpoint allows you to do something.'
|
||||||
|
# authenticated: false
|
||||||
|
# headers:
|
||||||
|
# Content-Type: application/json
|
||||||
|
# Accept: application/json
|
||||||
|
# urlParameters:
|
||||||
|
# param:
|
||||||
|
# name: param
|
||||||
|
# description: A URL param for no reason.
|
||||||
|
# required: true
|
||||||
|
# example: 2
|
||||||
|
# type: integer
|
||||||
|
# queryParameters:
|
||||||
|
# speed:
|
||||||
|
# name: speed
|
||||||
|
# description: How fast the thing should be done. Can be `slow` or `fast`.
|
||||||
|
# required: false
|
||||||
|
# example: fast
|
||||||
|
# type: string
|
||||||
|
# bodyParameters:
|
||||||
|
# something:
|
||||||
|
# name: something
|
||||||
|
# description: The things we should do.
|
||||||
|
# required: true
|
||||||
|
# example:
|
||||||
|
# - string 1
|
||||||
|
# - string 2
|
||||||
|
# type: 'string[]'
|
||||||
|
# responses:
|
||||||
|
# - status: 200
|
||||||
|
# description: 'When the thing was done smoothly.'
|
||||||
|
# content: # Your response content can be an object, an array, a string or empty.
|
||||||
|
# {
|
||||||
|
# "hey": "ho ho ho"
|
||||||
|
# }
|
||||||
|
# responseFields:
|
||||||
|
# hey:
|
||||||
|
# name: hey
|
||||||
|
# description: Who knows?
|
||||||
|
# type: string # This is optional
|
||||||
12
backend/.scribe/intro.md
Normal file
12
backend/.scribe/intro.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Introduction
|
||||||
|
|
||||||
|
Resource planning and capacity management API
|
||||||
|
|
||||||
|
<aside>
|
||||||
|
<strong>Base URL</strong>: <code>http://localhost/api</code>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
Authenticate by sending `Authorization: Bearer {access_token}` on protected endpoints.
|
||||||
|
|
||||||
|
Access tokens are valid for 60 minutes. Use `/api/auth/refresh` with your refresh token to obtain a new access token and refresh token pair.
|
||||||
|
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
"knuckleswtf/scribe": "^5.7",
|
"knuckleswtf/scribe": "^5.7",
|
||||||
"laravel/framework": "^12.0",
|
"laravel/framework": "^12.0",
|
||||||
"laravel/tinker": "^2.10.1",
|
"laravel/tinker": "^2.10.1",
|
||||||
|
"parsedown/parsedown": "1.7.4",
|
||||||
"predis/predis": "^2.0",
|
"predis/predis": "^2.0",
|
||||||
"tymon/jwt-auth": "^2.0"
|
"tymon/jwt-auth": "^2.0"
|
||||||
},
|
},
|
||||||
|
|||||||
18
backend/composer.lock
generated
18
backend/composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "2782b93777d132b40f4d1f45c0483384",
|
"content-hash": "eb1f270f832bd2bd086e4cccb3a4945d",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "brick/math",
|
"name": "brick/math",
|
||||||
@@ -3043,24 +3043,24 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parsedown/parsedown",
|
"name": "parsedown/parsedown",
|
||||||
"version": "1.8.0",
|
"version": "1.7.4",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/parsedown/parsedown.git",
|
"url": "https://github.com/parsedown/parsedown.git",
|
||||||
"reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7"
|
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/parsedown/parsedown/zipball/96baaad00f71ba04d76e45b4620f54d3beabd6f7",
|
"url": "https://api.github.com/repos/parsedown/parsedown/zipball/cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
|
||||||
"reference": "96baaad00f71ba04d76e45b4620f54d3beabd6f7",
|
"reference": "cb17b6477dfff935958ba01325f2e8a2bfa6dab3",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
"ext-mbstring": "*",
|
"ext-mbstring": "*",
|
||||||
"php": ">=7.1"
|
"php": ">=5.3.0"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "^7.5|^8.5|^9.6"
|
"phpunit/phpunit": "^4.8.35"
|
||||||
},
|
},
|
||||||
"type": "library",
|
"type": "library",
|
||||||
"autoload": {
|
"autoload": {
|
||||||
@@ -3087,9 +3087,9 @@
|
|||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/parsedown/parsedown/issues",
|
"issues": "https://github.com/parsedown/parsedown/issues",
|
||||||
"source": "https://github.com/parsedown/parsedown/tree/1.8.0"
|
"source": "https://github.com/parsedown/parsedown/tree/1.7.4"
|
||||||
},
|
},
|
||||||
"time": "2026-02-16T11:41:01+00:00"
|
"time": "2019-12-30T22:54:17+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpoption/phpoption",
|
"name": "phpoption/phpoption",
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use Knuckles\Scribe\Config\AuthIn;
|
|||||||
use Knuckles\Scribe\Config\Defaults;
|
use Knuckles\Scribe\Config\Defaults;
|
||||||
use Knuckles\Scribe\Extracting\Strategies;
|
use Knuckles\Scribe\Extracting\Strategies;
|
||||||
|
|
||||||
use function Knuckles\Scribe\Config\configureStrategy;
|
|
||||||
use function Knuckles\Scribe\Config\removeStrategies;
|
use function Knuckles\Scribe\Config\removeStrategies;
|
||||||
|
|
||||||
// Only the most common configs are shown. See the https://scribe.knuckles.wtf/laravel/reference/config for all.
|
// Only the most common configs are shown. See the https://scribe.knuckles.wtf/laravel/reference/config for all.
|
||||||
@@ -45,7 +44,7 @@ return [
|
|||||||
|
|
||||||
// Exclude these routes even if they matched the rules above.
|
// Exclude these routes even if they matched the rules above.
|
||||||
'exclude' => [
|
'exclude' => [
|
||||||
// 'GET /health', 'admin.*'
|
'api/user',
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
@@ -231,15 +230,9 @@ return [
|
|||||||
'bodyParameters' => [
|
'bodyParameters' => [
|
||||||
...Defaults::BODY_PARAMETERS_STRATEGIES,
|
...Defaults::BODY_PARAMETERS_STRATEGIES,
|
||||||
],
|
],
|
||||||
'responses' => configureStrategy(
|
'responses' => removeStrategies(
|
||||||
Defaults::RESPONSES_STRATEGIES,
|
Defaults::RESPONSES_STRATEGIES,
|
||||||
Strategies\Responses\ResponseCalls::withSettings(
|
[Strategies\Responses\ResponseCalls::class],
|
||||||
only: ['GET *'],
|
|
||||||
// Recommended: disable debug mode in response calls to avoid error stack traces in responses
|
|
||||||
config: [
|
|
||||||
'app.debug' => false,
|
|
||||||
]
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
'responseFields' => [
|
'responseFields' => [
|
||||||
...Defaults::RESPONSE_FIELDS_STRATEGIES,
|
...Defaults::RESPONSE_FIELDS_STRATEGIES,
|
||||||
|
|||||||
393
backend/public/vendor/scribe/css/theme-default.print.css
vendored
Normal file
393
backend/public/vendor/scribe/css/theme-default.print.css
vendored
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
/* Copied from https://github.com/slatedocs/slate/blob/c4b4c0b8f83e891ca9fab6bbe9a1a88d5fe41292/stylesheets/print.css and unminified */
|
||||||
|
/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: sans-serif;
|
||||||
|
-ms-text-size-adjust: 100%;
|
||||||
|
-webkit-text-size-adjust: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
main,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section,
|
||||||
|
summary {
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
|
||||||
|
audio,
|
||||||
|
canvas,
|
||||||
|
progress,
|
||||||
|
video {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: baseline
|
||||||
|
}
|
||||||
|
|
||||||
|
audio:not([controls]) {
|
||||||
|
display: none;
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden],
|
||||||
|
template {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent
|
||||||
|
}
|
||||||
|
|
||||||
|
a:active,
|
||||||
|
a:hover {
|
||||||
|
outline: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: 1px dotted
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
dfn {
|
||||||
|
font-style: italic
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0
|
||||||
|
}
|
||||||
|
|
||||||
|
mark {
|
||||||
|
background: #ff0;
|
||||||
|
color: #000
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
border: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
svg:not(:root) {
|
||||||
|
overflow: hidden
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 1em 40px
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
pre,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace;
|
||||||
|
font-size: 1em
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
color: inherit;
|
||||||
|
font: inherit;
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
overflow: visible
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
html input[type="button"],
|
||||||
|
input[type="reset"],
|
||||||
|
input[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
button[disabled],
|
||||||
|
html input[disabled] {
|
||||||
|
cursor: default
|
||||||
|
}
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
input::-moz-focus-inner {
|
||||||
|
border: 0;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
line-height: normal
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="number"]::-webkit-inner-spin-button,
|
||||||
|
input[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="search"] {
|
||||||
|
-webkit-appearance: textfield;
|
||||||
|
box-sizing: content-box
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="search"]::-webkit-search-cancel-button,
|
||||||
|
input[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
border: 1px solid #c0c0c0;
|
||||||
|
margin: 0 2px;
|
||||||
|
padding: 0.35em 0.625em 0.75em
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
border: 0;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
optgroup {
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h1,
|
||||||
|
.content h2,
|
||||||
|
.content h3,
|
||||||
|
.content h4,
|
||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
|
||||||
|
font-size: 14px
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h1,
|
||||||
|
.content h2,
|
||||||
|
.content h3,
|
||||||
|
.content h4 {
|
||||||
|
font-weight: bold
|
||||||
|
}
|
||||||
|
|
||||||
|
.content pre,
|
||||||
|
.content code {
|
||||||
|
font-family: Consolas, Menlo, Monaco, "Lucida Console", "Liberation Mono", "DejaVu Sans Mono", "Bitstream Vera Sans Mono", "Courier New", monospace, serif;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.5
|
||||||
|
}
|
||||||
|
|
||||||
|
.content pre,
|
||||||
|
.content code {
|
||||||
|
word-break: break-all;
|
||||||
|
-webkit-hyphens: auto;
|
||||||
|
-ms-hyphens: auto;
|
||||||
|
hyphens: auto
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'slate';
|
||||||
|
src: url(../fonts/slate.eot?-syv14m);
|
||||||
|
src: url(../fonts/slate.eot?#iefix-syv14m) format("embedded-opentype"), url(../fonts/slate.woff2?-syv14m) format("woff2"), url(../fonts/slate.woff?-syv14m) format("woff"), url(../fonts/slate.ttf?-syv14m) format("truetype"), url(../fonts/slate.svg?-syv14m#slate) format("svg");
|
||||||
|
font-weight: normal;
|
||||||
|
font-style: normal
|
||||||
|
}
|
||||||
|
|
||||||
|
.content aside.warning:before,
|
||||||
|
.content aside.notice:before,
|
||||||
|
.content aside.success:before {
|
||||||
|
font-family: 'slate';
|
||||||
|
speak: none;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: normal;
|
||||||
|
font-variant: normal;
|
||||||
|
text-transform: none;
|
||||||
|
line-height: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
.content aside.warning:before {
|
||||||
|
content: "\e600"
|
||||||
|
}
|
||||||
|
|
||||||
|
.content aside.notice:before {
|
||||||
|
content: "\e602"
|
||||||
|
}
|
||||||
|
|
||||||
|
.content aside.success:before {
|
||||||
|
content: "\e606"
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify,
|
||||||
|
.toc-footer,
|
||||||
|
.lang-selector,
|
||||||
|
.search,
|
||||||
|
#nav-button {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
.tocify-wrapper>img {
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
font-size: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
.content pre,
|
||||||
|
.content code {
|
||||||
|
border: 1px solid #999;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-size: 0.8em
|
||||||
|
}
|
||||||
|
|
||||||
|
.content pre code {
|
||||||
|
border: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.content pre {
|
||||||
|
padding: 1.3em
|
||||||
|
}
|
||||||
|
|
||||||
|
.content code {
|
||||||
|
padding: 0.2em
|
||||||
|
}
|
||||||
|
|
||||||
|
.content table {
|
||||||
|
border: 1px solid #999
|
||||||
|
}
|
||||||
|
|
||||||
|
.content table tr {
|
||||||
|
border-bottom: 1px solid #999
|
||||||
|
}
|
||||||
|
|
||||||
|
.content table td,
|
||||||
|
.content table th {
|
||||||
|
padding: 0.7em
|
||||||
|
}
|
||||||
|
|
||||||
|
.content p {
|
||||||
|
line-height: 1.5
|
||||||
|
}
|
||||||
|
|
||||||
|
.content a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #000
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
padding-bottom: 0.5em;
|
||||||
|
margin-top: 1em;
|
||||||
|
margin-bottom: 21px;
|
||||||
|
border: 2px solid #ccc;
|
||||||
|
border-width: 2px 0;
|
||||||
|
text-align: center
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h2 {
|
||||||
|
font-size: 1.8em;
|
||||||
|
margin-top: 2em;
|
||||||
|
border-top: 2px solid #ccc;
|
||||||
|
padding-top: 0.8em
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h1+h2,
|
||||||
|
.content h1+div+h2 {
|
||||||
|
border-top: none;
|
||||||
|
padding-top: 0;
|
||||||
|
margin-top: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h3,
|
||||||
|
.content h4 {
|
||||||
|
font-size: 0.8em;
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 0.8em;
|
||||||
|
text-transform: uppercase
|
||||||
|
}
|
||||||
|
|
||||||
|
.content h5,
|
||||||
|
.content h6 {
|
||||||
|
text-transform: uppercase
|
||||||
|
}
|
||||||
|
|
||||||
|
.content aside {
|
||||||
|
padding: 1em;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
line-height: 1.6
|
||||||
|
}
|
||||||
|
|
||||||
|
.content aside:before {
|
||||||
|
vertical-align: middle;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
font-size: 14px
|
||||||
|
}
|
||||||
1094
backend/public/vendor/scribe/css/theme-default.style.css
vendored
Normal file
1094
backend/public/vendor/scribe/css/theme-default.style.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
backend/public/vendor/scribe/images/navbar.png
vendored
Normal file
BIN
backend/public/vendor/scribe/images/navbar.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 96 B |
149
backend/public/vendor/scribe/js/theme-default-5.7.0.js
vendored
Normal file
149
backend/public/vendor/scribe/js/theme-default-5.7.0.js
vendored
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
const updateHash = function (id) {
|
||||||
|
window.location.hash = `#${id}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const navButton = document.getElementById('nav-button');
|
||||||
|
const menuWrapper = document.querySelector('.tocify-wrapper');
|
||||||
|
function toggleSidebar(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (menuWrapper) {
|
||||||
|
menuWrapper.classList.toggle('open');
|
||||||
|
navButton.classList.toggle('open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function closeSidebar() {
|
||||||
|
if (menuWrapper) {
|
||||||
|
menuWrapper.classList.remove('open');
|
||||||
|
navButton.classList.remove('open');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
navButton.addEventListener('click', toggleSidebar);
|
||||||
|
|
||||||
|
window.hljs.highlightAll();
|
||||||
|
|
||||||
|
const wrapper = document.getElementById('toc');
|
||||||
|
// https://jets.js.org/
|
||||||
|
window.jets = new window.Jets({
|
||||||
|
// *OR - Selects elements whose values contains at least one part of search substring
|
||||||
|
searchSelector: '*OR',
|
||||||
|
searchTag: '#input-search',
|
||||||
|
contentTag: '#toc li',
|
||||||
|
didSearch: function(term) {
|
||||||
|
wrapper.classList.toggle('jets-searching', String(term).length > 0)
|
||||||
|
},
|
||||||
|
// map these accent keys to plain values
|
||||||
|
diacriticsMap: {
|
||||||
|
a: 'ÀÁÂÃÄÅàáâãäåĀāąĄ',
|
||||||
|
c: 'ÇçćĆčČ',
|
||||||
|
d: 'đĐďĎ',
|
||||||
|
e: 'ÈÉÊËèéêëěĚĒēęĘ',
|
||||||
|
i: 'ÌÍÎÏìíîïĪī',
|
||||||
|
l: 'łŁ',
|
||||||
|
n: 'ÑñňŇńŃ',
|
||||||
|
o: 'ÒÓÔÕÕÖØòóôõöøŌō',
|
||||||
|
r: 'řŘ',
|
||||||
|
s: 'ŠšśŚ',
|
||||||
|
t: 'ťŤ',
|
||||||
|
u: 'ÙÚÛÜùúûüůŮŪū',
|
||||||
|
y: 'ŸÿýÝ',
|
||||||
|
z: 'ŽžżŻźŹ'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
function hashChange() {
|
||||||
|
const currentItems = document.querySelectorAll('.tocify-subheader.visible, .tocify-item.tocify-focus');
|
||||||
|
Array.from(currentItems).forEach((elem) => {
|
||||||
|
elem.classList.remove('visible', 'tocify-focus');
|
||||||
|
});
|
||||||
|
|
||||||
|
const currentTag = document.querySelector(`a[href="${window.location.hash}"]`);
|
||||||
|
if (currentTag) {
|
||||||
|
const parent = currentTag.closest('.tocify-subheader');
|
||||||
|
if (parent) {
|
||||||
|
parent.classList.add('visible');
|
||||||
|
}
|
||||||
|
|
||||||
|
const siblings = currentTag.closest('.tocify-header');
|
||||||
|
if (siblings) {
|
||||||
|
Array.from(siblings.querySelectorAll('.tocify-subheader')).forEach((elem) => {
|
||||||
|
elem.classList.add('visible');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTag.parentElement.classList.add('tocify-focus');
|
||||||
|
|
||||||
|
// wait for dom changes to be done
|
||||||
|
setTimeout(() => {
|
||||||
|
currentTag.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'center' });
|
||||||
|
// only close the sidebar on level-2 events
|
||||||
|
if (currentTag.parentElement.classList.contains('level-2')) {
|
||||||
|
closeSidebar();
|
||||||
|
}
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let languages = JSON.parse(document.body.getAttribute('data-languages'));
|
||||||
|
// Support a key => value object where the key is the name, or an array of strings where the value is the name
|
||||||
|
if (!Array.isArray(languages)) {
|
||||||
|
languages = Object.values(languages);
|
||||||
|
}
|
||||||
|
// if there is no language use the first one
|
||||||
|
const currentLanguage = window.localStorage.getItem('language') || languages[0];
|
||||||
|
const languageStyle = document.getElementById('language-style');
|
||||||
|
const langSelector = document.querySelectorAll('.lang-selector button.lang-button');
|
||||||
|
|
||||||
|
function setActiveLanguage(newLanguage) {
|
||||||
|
window.localStorage.setItem('language', newLanguage);
|
||||||
|
if (!languageStyle) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStyle = languages.map((language) => {
|
||||||
|
return language === newLanguage
|
||||||
|
// the current one should be visible
|
||||||
|
? `body .content .${language}-example pre { display: block; }`
|
||||||
|
// the inactive one should be hidden
|
||||||
|
: `body .content .${language}-example pre { display: none; }`;
|
||||||
|
}).join(`\n`);
|
||||||
|
|
||||||
|
Array.from(langSelector).forEach((elem) => {
|
||||||
|
elem.classList.toggle('active', elem.getAttribute('data-language-name') === newLanguage);
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeHash = window.location.hash.slice(1);
|
||||||
|
|
||||||
|
languageStyle.innerHTML = newStyle;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
updateHash(activeHash);
|
||||||
|
}, 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
setActiveLanguage(currentLanguage);
|
||||||
|
|
||||||
|
Array.from(langSelector).forEach((elem) => {
|
||||||
|
elem.addEventListener('click', () => {
|
||||||
|
const newLanguage = elem.getAttribute('data-language-name');
|
||||||
|
setActiveLanguage(newLanguage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('hashchange', hashChange, false);
|
||||||
|
|
||||||
|
const divs = document.querySelectorAll('.content h1[id], .content h2[id]');
|
||||||
|
|
||||||
|
document.addEventListener('scroll', () => {
|
||||||
|
divs.forEach(item => {
|
||||||
|
const rect = item.getBoundingClientRect();
|
||||||
|
if (rect.top > 0 && rect.top < 150) {
|
||||||
|
const location = window.location.toString().split('#')[0];
|
||||||
|
history.replaceState(null, null, location + '#' + item.id);
|
||||||
|
hashChange();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
hashChange();
|
||||||
|
});
|
||||||
289
backend/public/vendor/scribe/js/tryitout-5.7.0.js
vendored
Normal file
289
backend/public/vendor/scribe/js/tryitout-5.7.0.js
vendored
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
window.abortControllers = {};
|
||||||
|
|
||||||
|
function cacheAuthValue() {
|
||||||
|
// Whenever the auth header is set for one endpoint, cache it for the others
|
||||||
|
window.lastAuthValue = '';
|
||||||
|
let authInputs = document.querySelectorAll(`.auth-value`)
|
||||||
|
authInputs.forEach(el => {
|
||||||
|
el.addEventListener('input', (event) => {
|
||||||
|
window.lastAuthValue = event.target.value;
|
||||||
|
authInputs.forEach(otherInput => {
|
||||||
|
if (otherInput === el) return;
|
||||||
|
// Don't block the main thread
|
||||||
|
setTimeout(() => {
|
||||||
|
otherInput.value = window.lastAuthValue;
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('DOMContentLoaded', cacheAuthValue);
|
||||||
|
|
||||||
|
function getCookie(name) {
|
||||||
|
if (!document.cookie) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cookies = document.cookie.split(';')
|
||||||
|
.map(c => c.trim())
|
||||||
|
.filter(c => c.startsWith(name + '='));
|
||||||
|
|
||||||
|
if (cookies.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return decodeURIComponent(cookies[0].split('=')[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function tryItOut(endpointId) {
|
||||||
|
document.querySelector(`#btn-tryout-${endpointId}`).hidden = true;
|
||||||
|
document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = false;
|
||||||
|
const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`).hidden = false;
|
||||||
|
executeBtn.disabled = false;
|
||||||
|
|
||||||
|
// Show all input fields
|
||||||
|
document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`)
|
||||||
|
.forEach(el => el.style.display = 'block');
|
||||||
|
|
||||||
|
if (document.querySelector(`#form-${endpointId}`).dataset.authed === "1") {
|
||||||
|
const authElement = document.querySelector(`#auth-${endpointId}`);
|
||||||
|
authElement && (authElement.hidden = false);
|
||||||
|
}
|
||||||
|
// Expand all nested fields
|
||||||
|
document.querySelectorAll(`#form-${endpointId} details`)
|
||||||
|
.forEach(el => el.open = true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelTryOut(endpointId) {
|
||||||
|
if (window.abortControllers[endpointId]) {
|
||||||
|
window.abortControllers[endpointId].abort();
|
||||||
|
delete window.abortControllers[endpointId];
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelector(`#btn-tryout-${endpointId}`).hidden = false;
|
||||||
|
const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`);
|
||||||
|
executeBtn.hidden = true;
|
||||||
|
executeBtn.textContent = executeBtn.dataset.initialText;
|
||||||
|
document.querySelector(`#btn-canceltryout-${endpointId}`).hidden = true;
|
||||||
|
// Hide inputs
|
||||||
|
document.querySelectorAll(`input[data-endpoint=${endpointId}],label[data-endpoint=${endpointId}]`)
|
||||||
|
.forEach(el => el.style.display = 'none');
|
||||||
|
document.querySelectorAll(`#form-${endpointId} details`)
|
||||||
|
.forEach(el => el.open = false);
|
||||||
|
const authElement = document.querySelector(`#auth-${endpointId}`);
|
||||||
|
authElement && (authElement.hidden = true);
|
||||||
|
|
||||||
|
document.querySelector('#execution-results-' + endpointId).hidden = true;
|
||||||
|
document.querySelector('#execution-error-' + endpointId).hidden = true;
|
||||||
|
|
||||||
|
// Revert to sample code blocks
|
||||||
|
document.querySelector('#example-requests-' + endpointId).hidden = false;
|
||||||
|
document.querySelector('#example-responses-' + endpointId).hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeAPICall(method, path, body = {}, query = {}, headers = {}, endpointId = null) {
|
||||||
|
console.log({endpointId, path, body, query, headers});
|
||||||
|
|
||||||
|
if (!(body instanceof FormData) && typeof body !== "string") {
|
||||||
|
body = JSON.stringify(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = new URL(window.tryItOutBaseUrl + '/' + path.replace(/^\//, ''));
|
||||||
|
|
||||||
|
// We need this function because if you try to set an array or object directly to a URLSearchParams object,
|
||||||
|
// you'll get [object Object] or the array.toString()
|
||||||
|
function addItemToSearchParamsObject(key, value, searchParams) {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach((v, i) => {
|
||||||
|
// Append {filters: [first, second]} as filters[0]=first&filters[1]second
|
||||||
|
addItemToSearchParamsObject(key + '[' + i + ']', v, searchParams);
|
||||||
|
})
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
Object.keys(value).forEach((i) => {
|
||||||
|
// Append {filters: {name: first}} as filters[name]=first
|
||||||
|
addItemToSearchParamsObject(key + '[' + i + ']', value[i], searchParams);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
searchParams.append(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.keys(query)
|
||||||
|
.forEach(key => addItemToSearchParamsObject(key, query[key], url.searchParams));
|
||||||
|
|
||||||
|
window.abortControllers[endpointId] = new AbortController();
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body: method === 'GET' ? undefined : body,
|
||||||
|
signal: window.abortControllers[endpointId].signal,
|
||||||
|
referrer: window.tryItOutBaseUrl,
|
||||||
|
mode: 'cors',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
})
|
||||||
|
.then(response => Promise.all([response.status, response.statusText, response.text(), response.headers]));
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideCodeSamples(endpointId) {
|
||||||
|
document.querySelector('#example-requests-' + endpointId).hidden = true;
|
||||||
|
document.querySelector('#example-responses-' + endpointId).hidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleResponse(endpointId, response, status, headers) {
|
||||||
|
hideCodeSamples(endpointId);
|
||||||
|
|
||||||
|
// Hide error views
|
||||||
|
document.querySelector('#execution-error-' + endpointId).hidden = true;
|
||||||
|
|
||||||
|
const responseContentEl = document.querySelector('#execution-response-content-' + endpointId);
|
||||||
|
|
||||||
|
// Check if the response contains Laravel's dd() default dump output
|
||||||
|
const isLaravelDump = response.includes('Sfdump');
|
||||||
|
|
||||||
|
// If it's a Laravel dd() dump, use innerHTML to render it safely
|
||||||
|
if (isLaravelDump) {
|
||||||
|
responseContentEl.innerHTML = response === '' ? responseContentEl.dataset.emptyResponseText : response;
|
||||||
|
} else {
|
||||||
|
// Otherwise, stick to textContent for regular responses
|
||||||
|
responseContentEl.textContent = response === '' ? responseContentEl.dataset.emptyResponseText : response;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prettify it if it's JSON
|
||||||
|
let isJson = false;
|
||||||
|
try {
|
||||||
|
const jsonParsed = JSON.parse(response);
|
||||||
|
if (jsonParsed !== null) {
|
||||||
|
isJson = true;
|
||||||
|
response = JSON.stringify(jsonParsed, null, 4);
|
||||||
|
responseContentEl.textContent = response;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
isJson && window.hljs.highlightElement(responseContentEl);
|
||||||
|
const statusEl = document.querySelector('#execution-response-status-' + endpointId);
|
||||||
|
statusEl.textContent = ` (${status})`;
|
||||||
|
document.querySelector('#execution-results-' + endpointId).hidden = false;
|
||||||
|
statusEl.scrollIntoView({behavior: "smooth", block: "center"});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleError(endpointId, err) {
|
||||||
|
hideCodeSamples(endpointId);
|
||||||
|
// Hide response views
|
||||||
|
document.querySelector('#execution-results-' + endpointId).hidden = true;
|
||||||
|
|
||||||
|
// Show error views
|
||||||
|
let errorMessage = err.message || err;
|
||||||
|
const $errorMessageEl = document.querySelector('#execution-error-message-' + endpointId);
|
||||||
|
$errorMessageEl.textContent = errorMessage + $errorMessageEl.textContent;
|
||||||
|
const errorEl = document.querySelector('#execution-error-' + endpointId);
|
||||||
|
errorEl.hidden = false;
|
||||||
|
errorEl.scrollIntoView({behavior: "smooth", block: "center"});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async function executeTryOut(endpointId, form) {
|
||||||
|
const executeBtn = document.querySelector(`#btn-executetryout-${endpointId}`);
|
||||||
|
executeBtn.textContent = executeBtn.dataset.loadingText;
|
||||||
|
executeBtn.disabled = true;
|
||||||
|
executeBtn.scrollIntoView({behavior: "smooth", block: "center"});
|
||||||
|
|
||||||
|
let body;
|
||||||
|
let setter;
|
||||||
|
if (form.dataset.hasfiles === "1") {
|
||||||
|
body = new FormData();
|
||||||
|
setter = (name, value) => body.append(name, value);
|
||||||
|
} else if (form.dataset.isarraybody === "1") {
|
||||||
|
body = [];
|
||||||
|
setter = (name, value) => _.set(body, name, value);
|
||||||
|
} else {
|
||||||
|
body = {};
|
||||||
|
setter = (name, value) => _.set(body, name, value);
|
||||||
|
}
|
||||||
|
const bodyParameters = form.querySelectorAll('input[data-component=body]');
|
||||||
|
bodyParameters.forEach(el => {
|
||||||
|
let value = el.value;
|
||||||
|
|
||||||
|
if (el.type === 'number' && typeof value === 'string') {
|
||||||
|
value = parseFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.type === 'file' && el.files[0]) {
|
||||||
|
setter(el.name, el.files[0]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.type !== 'radio') {
|
||||||
|
if (value === "" && el.required === false) {
|
||||||
|
// Don't include empty optional values in the request
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setter(el.name, value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (el.checked) {
|
||||||
|
value = (value === 'false') ? false : true;
|
||||||
|
setter(el.name, value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const query = {};
|
||||||
|
const queryParameters = form.querySelectorAll('input[data-component=query]');
|
||||||
|
queryParameters.forEach(el => {
|
||||||
|
if (el.type !== 'radio' || (el.type === 'radio' && el.checked)) {
|
||||||
|
if (el.value === '') {
|
||||||
|
// Don't include empty values in the request
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_.set(query, el.name, el.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let path = form.dataset.path;
|
||||||
|
const urlParameters = form.querySelectorAll('input[data-component=url]');
|
||||||
|
urlParameters.forEach(el => (path = path.replace(new RegExp(`\\{${el.name}\\??}`), el.value)));
|
||||||
|
|
||||||
|
const headers = Object.fromEntries(Array.from(form.querySelectorAll('input[data-component=header]'))
|
||||||
|
.map(el => [el.name, el.value]));
|
||||||
|
|
||||||
|
// When using FormData, the browser sets the correct content-type + boundary
|
||||||
|
let method = form.dataset.method;
|
||||||
|
if (body instanceof FormData) {
|
||||||
|
delete headers['Content-Type'];
|
||||||
|
|
||||||
|
// When using FormData with PUT or PATCH, use method spoofing so PHP can access the post body
|
||||||
|
if (['PUT', 'PATCH'].includes(form.dataset.method)) {
|
||||||
|
method = 'POST';
|
||||||
|
setter('_method', form.dataset.method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let preflightPromise = Promise.resolve();
|
||||||
|
if (window.useCsrf && window.csrfUrl) {
|
||||||
|
preflightPromise = makeAPICall('GET', window.csrfUrl).then(() => {
|
||||||
|
headers['X-XSRF-TOKEN'] = getCookie('XSRF-TOKEN');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return preflightPromise.then(() => makeAPICall(method, path, body, query, headers, endpointId))
|
||||||
|
.then(([responseStatus, statusText, responseContent, responseHeaders]) => {
|
||||||
|
handleResponse(endpointId, responseContent, responseStatus, responseHeaders)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
if (err.name === "AbortError") {
|
||||||
|
console.log("Request cancelled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
console.log("Error while making request: ", err);
|
||||||
|
handleError(endpointId, err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
executeBtn.disabled = false;
|
||||||
|
executeBtn.textContent = executeBtn.dataset.initialText;
|
||||||
|
});
|
||||||
|
}
|
||||||
668
backend/resources/views/scribe/index.blade.php
Normal file
668
backend/resources/views/scribe/index.blade.php
Normal file
@@ -0,0 +1,668 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||||
|
<title>Headroom API</title>
|
||||||
|
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Open+Sans&display=swap" rel="stylesheet">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="{{ asset("/vendor/scribe/css/theme-default.style.css") }}" media="screen">
|
||||||
|
<link rel="stylesheet" href="{{ asset("/vendor/scribe/css/theme-default.print.css") }}" media="print">
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.10/lodash.min.js"></script>
|
||||||
|
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/styles/obsidian.min.css">
|
||||||
|
<script src="https://unpkg.com/@highlightjs/cdn-assets@11.6.0/highlight.min.js"></script>
|
||||||
|
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jets/0.14.1/jets.min.js"></script>
|
||||||
|
|
||||||
|
<style id="language-style">
|
||||||
|
/* starts out as display none and is replaced with js later */
|
||||||
|
body .content .bash-example code { display: none; }
|
||||||
|
body .content .javascript-example code { display: none; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var tryItOutBaseUrl = "http://localhost/api";
|
||||||
|
var useCsrf = Boolean();
|
||||||
|
var csrfUrl = "/sanctum/csrf-cookie";
|
||||||
|
</script>
|
||||||
|
<script src="{{ asset("/vendor/scribe/js/tryitout-5.7.0.js") }}"></script>
|
||||||
|
|
||||||
|
<script src="{{ asset("/vendor/scribe/js/theme-default-5.7.0.js") }}"></script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body data-languages="["bash","javascript"]">
|
||||||
|
|
||||||
|
<a href="#" id="nav-button">
|
||||||
|
<span>
|
||||||
|
MENU
|
||||||
|
<img src="{{ asset("/vendor/scribe/images/navbar.png") }}" alt="navbar-image"/>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
<div class="tocify-wrapper">
|
||||||
|
|
||||||
|
<div class="lang-selector">
|
||||||
|
<button type="button" class="lang-button" data-language-name="bash">bash</button>
|
||||||
|
<button type="button" class="lang-button" data-language-name="javascript">javascript</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search">
|
||||||
|
<input type="text" class="search" id="input-search" placeholder="Search">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="toc">
|
||||||
|
<ul id="tocify-header-introduction" class="tocify-header">
|
||||||
|
<li class="tocify-item level-1" data-unique="introduction">
|
||||||
|
<a href="#introduction">Introduction</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul id="tocify-header-authenticating-requests" class="tocify-header">
|
||||||
|
<li class="tocify-item level-1" data-unique="authenticating-requests">
|
||||||
|
<a href="#authenticating-requests">Authenticating requests</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul id="tocify-header-authentication" class="tocify-header">
|
||||||
|
<li class="tocify-item level-1" data-unique="authentication">
|
||||||
|
<a href="#authentication">Authentication</a>
|
||||||
|
</li>
|
||||||
|
<ul id="tocify-subheader-authentication" class="tocify-subheader">
|
||||||
|
<li class="tocify-item level-2" data-unique="authentication-POSTapi-auth-login">
|
||||||
|
<a href="#authentication-POSTapi-auth-login">Login and get tokens</a>
|
||||||
|
</li>
|
||||||
|
<li class="tocify-item level-2" data-unique="authentication-POSTapi-auth-refresh">
|
||||||
|
<a href="#authentication-POSTapi-auth-refresh">Refresh access token</a>
|
||||||
|
</li>
|
||||||
|
<li class="tocify-item level-2" data-unique="authentication-POSTapi-auth-logout">
|
||||||
|
<a href="#authentication-POSTapi-auth-logout">Logout current session</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="toc-footer" id="toc-footer">
|
||||||
|
<li style="padding-bottom: 5px;"><a href="{{ route("scribe.postman") }}">View Postman collection</a></li>
|
||||||
|
<li style="padding-bottom: 5px;"><a href="{{ route("scribe.openapi") }}">View OpenAPI spec</a></li>
|
||||||
|
<li><a href="http://github.com/knuckleswtf/scribe">Documentation powered by Scribe ✍</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<ul class="toc-footer" id="last-updated">
|
||||||
|
<li>Last updated: February 18, 2026</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="page-wrapper">
|
||||||
|
<div class="dark-box"></div>
|
||||||
|
<div class="content">
|
||||||
|
<h1 id="introduction">Introduction</h1>
|
||||||
|
<p>Resource planning and capacity management API</p>
|
||||||
|
<aside>
|
||||||
|
<strong>Base URL</strong>: <code>http://localhost/api</code>
|
||||||
|
</aside>
|
||||||
|
<pre><code>Authenticate by sending `Authorization: Bearer {access_token}` on protected endpoints.
|
||||||
|
|
||||||
|
Access tokens are valid for 60 minutes. Use `/api/auth/refresh` with your refresh token to obtain a new access token and refresh token pair.</code></pre>
|
||||||
|
|
||||||
|
<h1 id="authenticating-requests">Authenticating requests</h1>
|
||||||
|
<p>To authenticate requests, include an <strong><code>Authorization</code></strong> header with the value <strong><code>"Bearer Bearer {token}"</code></strong>.</p>
|
||||||
|
<p>All authenticated endpoints are marked with a <code>requires authentication</code> badge in the documentation below.</p>
|
||||||
|
<p>Get tokens from <code>POST /api/auth/login</code>, send access token as <code>Bearer {token}</code>, and renew with <code>POST /api/auth/refresh</code> before access token expiry.</p>
|
||||||
|
|
||||||
|
<h1 id="authentication">Authentication</h1>
|
||||||
|
|
||||||
|
<p>Endpoints for JWT authentication and session lifecycle.</p>
|
||||||
|
|
||||||
|
<h2 id="authentication-POSTapi-auth-login">Login and get tokens</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<small class="badge badge-darkred">requires authentication</small>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Authenticate with email and password to receive an access token and refresh token.</p>
|
||||||
|
|
||||||
|
<span id="example-requests-POSTapi-auth-login">
|
||||||
|
<blockquote>Example request:</blockquote>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="bash-example">
|
||||||
|
<pre><code class="language-bash">curl --request POST \
|
||||||
|
"http://localhost/api/api/auth/login" \
|
||||||
|
--header "Authorization: Bearer Bearer {token}" \
|
||||||
|
--header "Content-Type: application/json" \
|
||||||
|
--header "Accept: application/json" \
|
||||||
|
--data "{
|
||||||
|
\"email\": \"user@example.com\",
|
||||||
|
\"password\": \"secret123\"
|
||||||
|
}"
|
||||||
|
</code></pre></div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="javascript-example">
|
||||||
|
<pre><code class="language-javascript">const url = new URL(
|
||||||
|
"http://localhost/api/api/auth/login"
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Authorization": "Bearer Bearer {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = {
|
||||||
|
"email": "user@example.com",
|
||||||
|
"password": "secret123"
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
}).then(response => response.json());</code></pre></div>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span id="example-responses-POSTapi-auth-login">
|
||||||
|
<blockquote>
|
||||||
|
<p>Example response (200):</p>
|
||||||
|
</blockquote>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<code class="language-json" style="max-height: 300px;">{
|
||||||
|
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||||
|
"refresh_token": "abc123def456",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600,
|
||||||
|
"user": {
|
||||||
|
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"name": "Alice Johnson",
|
||||||
|
"email": "user@example.com",
|
||||||
|
"role": "manager"
|
||||||
|
}
|
||||||
|
}</code>
|
||||||
|
</pre>
|
||||||
|
<blockquote>
|
||||||
|
<p>Example response (401):</p>
|
||||||
|
</blockquote>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<code class="language-json" style="max-height: 300px;">{
|
||||||
|
"message": "Invalid credentials"
|
||||||
|
}</code>
|
||||||
|
</pre>
|
||||||
|
<blockquote>
|
||||||
|
<p>Example response (403):</p>
|
||||||
|
</blockquote>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<code class="language-json" style="max-height: 300px;">{
|
||||||
|
"message": "Account is inactive"
|
||||||
|
}</code>
|
||||||
|
</pre>
|
||||||
|
<blockquote>
|
||||||
|
<p>Example response (422):</p>
|
||||||
|
</blockquote>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<code class="language-json" style="max-height: 300px;">{
|
||||||
|
"errors": {
|
||||||
|
"email": [
|
||||||
|
"The email field is required."
|
||||||
|
],
|
||||||
|
"password": [
|
||||||
|
"The password field is required."
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}</code>
|
||||||
|
</pre>
|
||||||
|
</span>
|
||||||
|
<span id="execution-results-POSTapi-auth-login" hidden>
|
||||||
|
<blockquote>Received response<span
|
||||||
|
id="execution-response-status-POSTapi-auth-login"></span>:
|
||||||
|
</blockquote>
|
||||||
|
<pre class="json"><code id="execution-response-content-POSTapi-auth-login"
|
||||||
|
data-empty-response-text="<Empty response>" style="max-height: 400px;"></code></pre>
|
||||||
|
</span>
|
||||||
|
<span id="execution-error-POSTapi-auth-login" hidden>
|
||||||
|
<blockquote>Request failed with error:</blockquote>
|
||||||
|
<pre><code id="execution-error-message-POSTapi-auth-login">
|
||||||
|
|
||||||
|
Tip: Check that you're properly connected to the network.
|
||||||
|
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
|
||||||
|
You can check the Dev Tools console for debugging information.</code></pre>
|
||||||
|
</span>
|
||||||
|
<form id="form-POSTapi-auth-login" data-method="POST"
|
||||||
|
data-path="api/auth/login"
|
||||||
|
data-authed="1"
|
||||||
|
data-hasfiles="0"
|
||||||
|
data-isarraybody="0"
|
||||||
|
autocomplete="off"
|
||||||
|
onsubmit="event.preventDefault(); executeTryOut('POSTapi-auth-login', this);">
|
||||||
|
<h3>
|
||||||
|
Request
|
||||||
|
<button type="button"
|
||||||
|
style="background-color: #8fbcd4; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-tryout-POSTapi-auth-login"
|
||||||
|
onclick="tryItOut('POSTapi-auth-login');">Try it out ⚡
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
style="background-color: #c97a7e; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-canceltryout-POSTapi-auth-login"
|
||||||
|
onclick="cancelTryOut('POSTapi-auth-login');" hidden>Cancel 🛑
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
style="background-color: #6ac174; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-executetryout-POSTapi-auth-login"
|
||||||
|
data-initial-text="Send Request 💥"
|
||||||
|
data-loading-text="⏱ Sending..."
|
||||||
|
hidden>Send Request 💥
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
<small class="badge badge-black">POST</small>
|
||||||
|
<b><code>api/auth/login</code></b>
|
||||||
|
</p>
|
||||||
|
<h4 class="fancy-heading-panel"><b>Headers</b></h4>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Authorization</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Authorization" class="auth-value" data-endpoint="POSTapi-auth-login"
|
||||||
|
value="Bearer Bearer {token}"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>Bearer Bearer {token}</code></p>
|
||||||
|
</div>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Content-Type</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Content-Type" data-endpoint="POSTapi-auth-login"
|
||||||
|
value="application/json"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>application/json</code></p>
|
||||||
|
</div>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Accept</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Accept" data-endpoint="POSTapi-auth-login"
|
||||||
|
value="application/json"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>application/json</code></p>
|
||||||
|
</div>
|
||||||
|
<h4 class="fancy-heading-panel"><b>Body Parameters</b></h4>
|
||||||
|
<div style=" padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>email</code></b>
|
||||||
|
<small>string</small>
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="email" data-endpoint="POSTapi-auth-login"
|
||||||
|
value="user@example.com"
|
||||||
|
data-component="body">
|
||||||
|
<br>
|
||||||
|
<p>User email address. Example: <code>user@example.com</code></p>
|
||||||
|
</div>
|
||||||
|
<div style=" padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>password</code></b>
|
||||||
|
<small>string</small>
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="password" data-endpoint="POSTapi-auth-login"
|
||||||
|
value="secret123"
|
||||||
|
data-component="body">
|
||||||
|
<br>
|
||||||
|
<p>User password. Example: <code>secret123</code></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2 id="authentication-POSTapi-auth-refresh">Refresh access token</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<small class="badge badge-darkred">requires authentication</small>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Exchange a valid refresh token for a new access token and refresh token pair.</p>
|
||||||
|
|
||||||
|
<span id="example-requests-POSTapi-auth-refresh">
|
||||||
|
<blockquote>Example request:</blockquote>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="bash-example">
|
||||||
|
<pre><code class="language-bash">curl --request POST \
|
||||||
|
"http://localhost/api/api/auth/refresh" \
|
||||||
|
--header "Authorization: Bearer Bearer {token}" \
|
||||||
|
--header "Content-Type: application/json" \
|
||||||
|
--header "Accept: application/json" \
|
||||||
|
--data "{
|
||||||
|
\"refresh_token\": \"abc123def456\"
|
||||||
|
}"
|
||||||
|
</code></pre></div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="javascript-example">
|
||||||
|
<pre><code class="language-javascript">const url = new URL(
|
||||||
|
"http://localhost/api/api/auth/refresh"
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Authorization": "Bearer Bearer {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = {
|
||||||
|
"refresh_token": "abc123def456"
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
}).then(response => response.json());</code></pre></div>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span id="example-responses-POSTapi-auth-refresh">
|
||||||
|
<blockquote>
|
||||||
|
<p>Example response (200):</p>
|
||||||
|
</blockquote>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<code class="language-json" style="max-height: 300px;">{
|
||||||
|
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
|
||||||
|
"refresh_token": "newtoken123",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600
|
||||||
|
}</code>
|
||||||
|
</pre>
|
||||||
|
<blockquote>
|
||||||
|
<p>Example response (401):</p>
|
||||||
|
</blockquote>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<code class="language-json" style="max-height: 300px;">{
|
||||||
|
"message": "Invalid or expired refresh token"
|
||||||
|
}</code>
|
||||||
|
</pre>
|
||||||
|
</span>
|
||||||
|
<span id="execution-results-POSTapi-auth-refresh" hidden>
|
||||||
|
<blockquote>Received response<span
|
||||||
|
id="execution-response-status-POSTapi-auth-refresh"></span>:
|
||||||
|
</blockquote>
|
||||||
|
<pre class="json"><code id="execution-response-content-POSTapi-auth-refresh"
|
||||||
|
data-empty-response-text="<Empty response>" style="max-height: 400px;"></code></pre>
|
||||||
|
</span>
|
||||||
|
<span id="execution-error-POSTapi-auth-refresh" hidden>
|
||||||
|
<blockquote>Request failed with error:</blockquote>
|
||||||
|
<pre><code id="execution-error-message-POSTapi-auth-refresh">
|
||||||
|
|
||||||
|
Tip: Check that you're properly connected to the network.
|
||||||
|
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
|
||||||
|
You can check the Dev Tools console for debugging information.</code></pre>
|
||||||
|
</span>
|
||||||
|
<form id="form-POSTapi-auth-refresh" data-method="POST"
|
||||||
|
data-path="api/auth/refresh"
|
||||||
|
data-authed="1"
|
||||||
|
data-hasfiles="0"
|
||||||
|
data-isarraybody="0"
|
||||||
|
autocomplete="off"
|
||||||
|
onsubmit="event.preventDefault(); executeTryOut('POSTapi-auth-refresh', this);">
|
||||||
|
<h3>
|
||||||
|
Request
|
||||||
|
<button type="button"
|
||||||
|
style="background-color: #8fbcd4; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-tryout-POSTapi-auth-refresh"
|
||||||
|
onclick="tryItOut('POSTapi-auth-refresh');">Try it out ⚡
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
style="background-color: #c97a7e; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-canceltryout-POSTapi-auth-refresh"
|
||||||
|
onclick="cancelTryOut('POSTapi-auth-refresh');" hidden>Cancel 🛑
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
style="background-color: #6ac174; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-executetryout-POSTapi-auth-refresh"
|
||||||
|
data-initial-text="Send Request 💥"
|
||||||
|
data-loading-text="⏱ Sending..."
|
||||||
|
hidden>Send Request 💥
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
<small class="badge badge-black">POST</small>
|
||||||
|
<b><code>api/auth/refresh</code></b>
|
||||||
|
</p>
|
||||||
|
<h4 class="fancy-heading-panel"><b>Headers</b></h4>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Authorization</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Authorization" class="auth-value" data-endpoint="POSTapi-auth-refresh"
|
||||||
|
value="Bearer Bearer {token}"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>Bearer Bearer {token}</code></p>
|
||||||
|
</div>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Content-Type</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Content-Type" data-endpoint="POSTapi-auth-refresh"
|
||||||
|
value="application/json"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>application/json</code></p>
|
||||||
|
</div>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Accept</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Accept" data-endpoint="POSTapi-auth-refresh"
|
||||||
|
value="application/json"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>application/json</code></p>
|
||||||
|
</div>
|
||||||
|
<h4 class="fancy-heading-panel"><b>Body Parameters</b></h4>
|
||||||
|
<div style=" padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>refresh_token</code></b>
|
||||||
|
<small>string</small>
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="refresh_token" data-endpoint="POSTapi-auth-refresh"
|
||||||
|
value="abc123def456"
|
||||||
|
data-component="body">
|
||||||
|
<br>
|
||||||
|
<p>Refresh token returned by login. Example: <code>abc123def456</code></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<h2 id="authentication-POSTapi-auth-logout">Logout current session</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<small class="badge badge-darkred">requires authentication</small>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Invalidate a refresh token and end the active authenticated session.</p>
|
||||||
|
|
||||||
|
<span id="example-requests-POSTapi-auth-logout">
|
||||||
|
<blockquote>Example request:</blockquote>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="bash-example">
|
||||||
|
<pre><code class="language-bash">curl --request POST \
|
||||||
|
"http://localhost/api/api/auth/logout" \
|
||||||
|
--header "Authorization: Bearer Bearer {token}" \
|
||||||
|
--header "Content-Type: application/json" \
|
||||||
|
--header "Accept: application/json" \
|
||||||
|
--data "{
|
||||||
|
\"refresh_token\": \"abc123def456\"
|
||||||
|
}"
|
||||||
|
</code></pre></div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="javascript-example">
|
||||||
|
<pre><code class="language-javascript">const url = new URL(
|
||||||
|
"http://localhost/api/api/auth/logout"
|
||||||
|
);
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
"Authorization": "Bearer Bearer {token}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
let body = {
|
||||||
|
"refresh_token": "abc123def456"
|
||||||
|
};
|
||||||
|
|
||||||
|
fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
}).then(response => response.json());</code></pre></div>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span id="example-responses-POSTapi-auth-logout">
|
||||||
|
<blockquote>
|
||||||
|
<p>Example response (200):</p>
|
||||||
|
</blockquote>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<code class="language-json" style="max-height: 300px;">{
|
||||||
|
"message": "Logged out successfully"
|
||||||
|
}</code>
|
||||||
|
</pre>
|
||||||
|
</span>
|
||||||
|
<span id="execution-results-POSTapi-auth-logout" hidden>
|
||||||
|
<blockquote>Received response<span
|
||||||
|
id="execution-response-status-POSTapi-auth-logout"></span>:
|
||||||
|
</blockquote>
|
||||||
|
<pre class="json"><code id="execution-response-content-POSTapi-auth-logout"
|
||||||
|
data-empty-response-text="<Empty response>" style="max-height: 400px;"></code></pre>
|
||||||
|
</span>
|
||||||
|
<span id="execution-error-POSTapi-auth-logout" hidden>
|
||||||
|
<blockquote>Request failed with error:</blockquote>
|
||||||
|
<pre><code id="execution-error-message-POSTapi-auth-logout">
|
||||||
|
|
||||||
|
Tip: Check that you're properly connected to the network.
|
||||||
|
If you're a maintainer of ths API, verify that your API is running and you've enabled CORS.
|
||||||
|
You can check the Dev Tools console for debugging information.</code></pre>
|
||||||
|
</span>
|
||||||
|
<form id="form-POSTapi-auth-logout" data-method="POST"
|
||||||
|
data-path="api/auth/logout"
|
||||||
|
data-authed="1"
|
||||||
|
data-hasfiles="0"
|
||||||
|
data-isarraybody="0"
|
||||||
|
autocomplete="off"
|
||||||
|
onsubmit="event.preventDefault(); executeTryOut('POSTapi-auth-logout', this);">
|
||||||
|
<h3>
|
||||||
|
Request
|
||||||
|
<button type="button"
|
||||||
|
style="background-color: #8fbcd4; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-tryout-POSTapi-auth-logout"
|
||||||
|
onclick="tryItOut('POSTapi-auth-logout');">Try it out ⚡
|
||||||
|
</button>
|
||||||
|
<button type="button"
|
||||||
|
style="background-color: #c97a7e; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-canceltryout-POSTapi-auth-logout"
|
||||||
|
onclick="cancelTryOut('POSTapi-auth-logout');" hidden>Cancel 🛑
|
||||||
|
</button>
|
||||||
|
<button type="submit"
|
||||||
|
style="background-color: #6ac174; padding: 5px 10px; border-radius: 5px; border-width: thin;"
|
||||||
|
id="btn-executetryout-POSTapi-auth-logout"
|
||||||
|
data-initial-text="Send Request 💥"
|
||||||
|
data-loading-text="⏱ Sending..."
|
||||||
|
hidden>Send Request 💥
|
||||||
|
</button>
|
||||||
|
</h3>
|
||||||
|
<p>
|
||||||
|
<small class="badge badge-black">POST</small>
|
||||||
|
<b><code>api/auth/logout</code></b>
|
||||||
|
</p>
|
||||||
|
<h4 class="fancy-heading-panel"><b>Headers</b></h4>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Authorization</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Authorization" class="auth-value" data-endpoint="POSTapi-auth-logout"
|
||||||
|
value="Bearer Bearer {token}"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>Bearer Bearer {token}</code></p>
|
||||||
|
</div>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Content-Type</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Content-Type" data-endpoint="POSTapi-auth-logout"
|
||||||
|
value="application/json"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>application/json</code></p>
|
||||||
|
</div>
|
||||||
|
<div style="padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>Accept</code></b>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="Accept" data-endpoint="POSTapi-auth-logout"
|
||||||
|
value="application/json"
|
||||||
|
data-component="header">
|
||||||
|
<br>
|
||||||
|
<p>Example: <code>application/json</code></p>
|
||||||
|
</div>
|
||||||
|
<h4 class="fancy-heading-panel"><b>Body Parameters</b></h4>
|
||||||
|
<div style=" padding-left: 28px; clear: unset;">
|
||||||
|
<b style="line-height: 2;"><code>refresh_token</code></b>
|
||||||
|
<small>string</small>
|
||||||
|
<i>optional</i>
|
||||||
|
|
||||||
|
<input type="text" style="display: none"
|
||||||
|
name="refresh_token" data-endpoint="POSTapi-auth-logout"
|
||||||
|
value="abc123def456"
|
||||||
|
data-component="body">
|
||||||
|
<br>
|
||||||
|
<p>Optional refresh token to invalidate immediately. Example: <code>abc123def456</code></p>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="dark-box">
|
||||||
|
<div class="lang-selector">
|
||||||
|
<button type="button" class="lang-button" data-language-name="bash">bash</button>
|
||||||
|
<button type="button" class="lang-button" data-language-name="javascript">javascript</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -93,14 +93,9 @@ public function index(Request $request): JsonResponse
|
|||||||
### Group Organization
|
### Group Organization
|
||||||
| Group | Controller | Endpoints |
|
| Group | Controller | Endpoints |
|
||||||
|----------------|-----------------------|-----------|
|
|----------------|-----------------------|-----------|
|
||||||
| Authentication | AuthController | 5 |
|
| Authentication | AuthController | 3 |
|
||||||
| Team Members | TeamMemberController | 5 |
|
|
||||||
| Projects | ProjectController | 8 |
|
Additional groups are intentionally deferred until corresponding controllers are added to this repository.
|
||||||
| Allocations | AllocationController | 6 |
|
|
||||||
| Actuals | ActualController | 5 |
|
|
||||||
| Capacity | CapacityController | 6 |
|
|
||||||
| Reports | ReportController | 5 |
|
|
||||||
| Master Data | MasterDataController | 9 |
|
|
||||||
|
|
||||||
### Authentication Documentation
|
### Authentication Documentation
|
||||||
Include a dedicated section explaining:
|
Include a dedicated section explaining:
|
||||||
@@ -113,15 +108,8 @@ Include a dedicated section explaining:
|
|||||||
|
|
||||||
1. Configure Scribe (`config/scribe.php`)
|
1. Configure Scribe (`config/scribe.php`)
|
||||||
2. Annotate AuthController (most critical, used by all)
|
2. Annotate AuthController (most critical, used by all)
|
||||||
3. Annotate TeamMemberController
|
3. Generate documentation (`php artisan scribe:generate`)
|
||||||
4. Annotate ProjectController
|
4. Verify SwaggerUI at `/api/documentation`
|
||||||
5. Annotate AllocationController
|
|
||||||
6. Annotate ActualController
|
|
||||||
7. Annotate CapacityController
|
|
||||||
8. Annotate ReportController
|
|
||||||
9. Annotate MasterDataController
|
|
||||||
10. Generate documentation (`php artisan scribe:generate`)
|
|
||||||
11. Verify SwaggerUI at `/api/documentation`
|
|
||||||
|
|
||||||
## File Changes
|
## File Changes
|
||||||
|
|
||||||
@@ -130,13 +118,7 @@ Include a dedicated section explaining:
|
|||||||
|
|
||||||
### Modified Files
|
### Modified Files
|
||||||
- `backend/app/Http/Controllers/Api/AuthController.php`
|
- `backend/app/Http/Controllers/Api/AuthController.php`
|
||||||
- `backend/app/Http/Controllers/Api/TeamMemberController.php`
|
- `backend/config/cors.php`
|
||||||
- `backend/app/Http/Controllers/Api/ProjectController.php`
|
|
||||||
- `backend/app/Http/Controllers/Api/AllocationController.php`
|
|
||||||
- `backend/app/Http/Controllers/Api/ActualController.php`
|
|
||||||
- `backend/app/Http/Controllers/Api/CapacityController.php`
|
|
||||||
- `backend/app/Http/Controllers/Api/ReportController.php`
|
|
||||||
- `backend/app/Http/Controllers/Api/MasterDataController.php`
|
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
- Run `php artisan scribe:generate` - must complete without errors
|
- Run `php artisan scribe:generate` - must complete without errors
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
Add comprehensive API documentation annotations to all Laravel controllers using Laravel Scribe. This enables auto-generated SwaggerUI documentation accessible at `/api/documentation`.
|
Add comprehensive API documentation annotations to all Laravel controllers using Laravel Scribe. This enables auto-generated SwaggerUI documentation accessible at `/api/documentation`.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
- Annotate ALL existing API controllers with Scribe annotations
|
- Annotate existing authentication API endpoints with Scribe annotations
|
||||||
- Generate browsable API documentation
|
- Generate browsable API documentation
|
||||||
- Ensure documentation stays in sync with implementation
|
- Ensure documentation stays in sync with implementation
|
||||||
- Enable frontend developers to reference accurate API specs
|
- Enable frontend developers to reference accurate API specs
|
||||||
@@ -21,13 +21,8 @@ Add comprehensive API documentation annotations to all Laravel controllers using
|
|||||||
|
|
||||||
### Controllers to Document
|
### Controllers to Document
|
||||||
1. **AuthController** - Login, logout, token refresh endpoints
|
1. **AuthController** - Login, logout, token refresh endpoints
|
||||||
2. **TeamMemberController** - CRUD for team members
|
|
||||||
3. **ProjectController** - CRUD, status transitions, estimates
|
Other controller groups in the broader roadmap are deferred until those controllers/routes are present in this repository.
|
||||||
4. **AllocationController** - CRUD, bulk operations, matrix view
|
|
||||||
5. **ActualController** - CRUD, logging hours
|
|
||||||
6. **CapacityController** - Capacity calculations, holidays, PTO
|
|
||||||
7. **ReportController** - Forecast, utilization, costs, variance reports
|
|
||||||
8. **MasterDataController** - Roles, statuses, types management
|
|
||||||
|
|
||||||
### Annotations Required
|
### Annotations Required
|
||||||
Each endpoint must have:
|
Each endpoint must have:
|
||||||
@@ -38,10 +33,10 @@ Each endpoint must have:
|
|||||||
- `@response 401|403|422` - Error responses
|
- `@response 401|403|422` - Error responses
|
||||||
|
|
||||||
## Success Criteria
|
## Success Criteria
|
||||||
- [ ] All controllers have Scribe annotations
|
- [ ] AuthController endpoints have Scribe annotations
|
||||||
- [ ] `php artisan scribe:generate` runs without errors
|
- [ ] `php artisan scribe:generate` runs without errors
|
||||||
- [ ] SwaggerUI accessible at `/api/documentation`
|
- [ ] SwaggerUI accessible at `/api/documentation`
|
||||||
- [ ] All endpoints documented with request/response examples
|
- [ ] Authentication endpoints documented with request/response examples
|
||||||
- [ ] Authentication section explains JWT flow
|
- [ ] Authentication section explains JWT flow
|
||||||
|
|
||||||
## Estimated Effort
|
## Estimated Effort
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Generate Authentication API Documentation
|
||||||
|
The system SHALL generate API documentation for authentication endpoints using Laravel Scribe.
|
||||||
|
|
||||||
|
#### Scenario: Generate docs successfully
|
||||||
|
- **WHEN** `php artisan scribe:generate` is run
|
||||||
|
- **THEN** the command completes without errors
|
||||||
|
- **AND** generated documentation includes `POST /api/auth/login`, `POST /api/auth/refresh`, and `POST /api/auth/logout`
|
||||||
|
|
||||||
|
### Requirement: Serve Documentation UI
|
||||||
|
The system SHALL serve documentation at `/api/documentation`.
|
||||||
|
|
||||||
|
#### Scenario: Access generated docs
|
||||||
|
- **WHEN** a client requests `GET /api/documentation`
|
||||||
|
- **THEN** the application responds with HTTP 200
|
||||||
|
- **AND** the page contains the configured API title and authentication guidance
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Tasks: API Documentation with Scribe
|
||||||
|
|
||||||
|
## Phase 1: Configure Scribe
|
||||||
|
|
||||||
|
- [x] 0.1 Install Scribe (if not already installed): `composer require knuckleswtf/scribe`
|
||||||
|
- [x] 0.2 Publish Scribe config: `php artisan vendor:publish --tag=scribe-config`
|
||||||
|
- [x] 0.3 Configure `config/scribe.php` with Headroom settings
|
||||||
|
- [x] 0.4 Add `/api/documentation` to CORS allowed paths
|
||||||
|
|
||||||
|
## Phase 2: Annotate Controllers
|
||||||
|
|
||||||
|
### AuthController
|
||||||
|
- [x] 0.5 Add `@group Authentication` to class
|
||||||
|
- [x] 0.6 Document `POST /api/auth/login` with @bodyParam, @response
|
||||||
|
- [x] 0.7 Document `POST /api/auth/refresh` with @authenticated, @response
|
||||||
|
- [x] 0.8 Document `POST /api/auth/logout` with @authenticated, @response
|
||||||
|
- [x] 0.9 Add authentication section to Scribe config
|
||||||
|
|
||||||
|
## Phase 3: Generate & Verify
|
||||||
|
|
||||||
|
- [x] 0.54 Run `php artisan scribe:generate`
|
||||||
|
- [x] 0.55 Verify no errors in generation
|
||||||
|
- [x] 0.56 Access `/api/documentation` in browser
|
||||||
|
- [x] 0.57 Verify all endpoints appear in documentation
|
||||||
|
- [x] 0.58 Test "Try it out" for login endpoint
|
||||||
|
- [x] 0.59 Verify authentication flow is documented
|
||||||
|
|
||||||
|
## Commits
|
||||||
|
|
||||||
|
1. `chore(docs): Configure Laravel Scribe for API documentation`
|
||||||
|
2. `docs(api): Add Scribe annotations to AuthController`
|
||||||
|
3. `docs(api): Generate and verify SwaggerUI documentation`
|
||||||
@@ -1,94 +0,0 @@
|
|||||||
# Tasks: API Documentation with Scribe
|
|
||||||
|
|
||||||
## Phase 1: Configure Scribe
|
|
||||||
|
|
||||||
- [x] 0.1 Install Scribe (if not already installed): `composer require knuckleswtf/scribe`
|
|
||||||
- [x] 0.2 Publish Scribe config: `php artisan vendor:publish --tag=scribe-config`
|
|
||||||
- [x] 0.3 Configure `config/scribe.php` with Headroom settings
|
|
||||||
- [x] 0.4 Add `/api/documentation` to CORS allowed paths
|
|
||||||
|
|
||||||
## Phase 2: Annotate Controllers
|
|
||||||
|
|
||||||
### AuthController
|
|
||||||
- [x] 0.5 Add `@group Authentication` to class
|
|
||||||
- [x] 0.6 Document `POST /api/auth/login` with @bodyParam, @response
|
|
||||||
- [x] 0.7 Document `POST /api/auth/refresh` with @authenticated, @response
|
|
||||||
- [x] 0.8 Document `POST /api/auth/logout` with @authenticated, @response
|
|
||||||
- [x] 0.9 Add authentication section to Scribe config
|
|
||||||
|
|
||||||
### TeamMemberController
|
|
||||||
- [ ] 0.10 Add `@group Team Members` to class
|
|
||||||
- [ ] 0.11 Document `GET /api/team-members` with @queryParam filters
|
|
||||||
- [ ] 0.12 Document `POST /api/team-members` with @bodyParam
|
|
||||||
- [ ] 0.13 Document `GET /api/team-members/{id}`
|
|
||||||
- [ ] 0.14 Document `PUT /api/team-members/{id}`
|
|
||||||
- [ ] 0.15 Document `DELETE /api/team-members/{id}`
|
|
||||||
|
|
||||||
### ProjectController
|
|
||||||
- [ ] 0.16 Add `@group Projects` to class
|
|
||||||
- [ ] 0.17 Document `GET /api/projects` with @queryParam filters
|
|
||||||
- [ ] 0.18 Document `POST /api/projects` with @bodyParam
|
|
||||||
- [ ] 0.19 Document `GET /api/projects/{id}`
|
|
||||||
- [ ] 0.20 Document `PUT /api/projects/{id}`
|
|
||||||
- [ ] 0.21 Document `PUT /api/projects/{id}/status`
|
|
||||||
- [ ] 0.22 Document `PUT /api/projects/{id}/estimate`
|
|
||||||
- [ ] 0.23 Document `PUT /api/projects/{id}/forecast`
|
|
||||||
|
|
||||||
### AllocationController
|
|
||||||
- [ ] 0.24 Add `@group Allocations` to class
|
|
||||||
- [ ] 0.25 Document `GET /api/allocations` (matrix view)
|
|
||||||
- [ ] 0.26 Document `POST /api/allocations`
|
|
||||||
- [ ] 0.27 Document `PUT /api/allocations/{id}`
|
|
||||||
- [ ] 0.28 Document `DELETE /api/allocations/{id}`
|
|
||||||
- [ ] 0.29 Document `POST /api/allocations/bulk`
|
|
||||||
|
|
||||||
### ActualController
|
|
||||||
- [ ] 0.30 Add `@group Actuals` to class
|
|
||||||
- [ ] 0.31 Document `GET /api/actuals`
|
|
||||||
- [ ] 0.32 Document `POST /api/actuals`
|
|
||||||
- [ ] 0.33 Document `PUT /api/actuals/{id}`
|
|
||||||
- [ ] 0.34 Document validation rules (future month rejection)
|
|
||||||
|
|
||||||
### CapacityController
|
|
||||||
- [ ] 0.35 Add `@group Capacity` to class
|
|
||||||
- [ ] 0.36 Document `GET /api/capacity`
|
|
||||||
- [ ] 0.37 Document `GET /api/capacity/team`
|
|
||||||
- [ ] 0.38 Document `GET /api/capacity/revenue`
|
|
||||||
- [ ] 0.39 Document `POST /api/holidays`
|
|
||||||
- [ ] 0.40 Document `POST /api/ptos`
|
|
||||||
|
|
||||||
### ReportController
|
|
||||||
- [ ] 0.41 Add `@group Reports` to class
|
|
||||||
- [ ] 0.42 Document `GET /api/reports/forecast`
|
|
||||||
- [ ] 0.43 Document `GET /api/reports/utilization`
|
|
||||||
- [ ] 0.44 Document `GET /api/reports/costs`
|
|
||||||
- [ ] 0.45 Document `GET /api/reports/variance`
|
|
||||||
- [ ] 0.46 Document `GET /api/reports/allocation`
|
|
||||||
|
|
||||||
### MasterDataController
|
|
||||||
- [ ] 0.47 Add `@group Master Data` to class
|
|
||||||
- [ ] 0.48 Document `GET /api/roles`
|
|
||||||
- [ ] 0.49 Document `POST /api/roles`
|
|
||||||
- [ ] 0.50 Document `PUT /api/roles/{id}`
|
|
||||||
- [ ] 0.51 Document `DELETE /api/roles/{id}`
|
|
||||||
- [ ] 0.52 Document project-statuses endpoints
|
|
||||||
- [ ] 0.53 Document project-types endpoints
|
|
||||||
|
|
||||||
## Phase 3: Generate & Verify
|
|
||||||
|
|
||||||
- [ ] 0.54 Run `php artisan scribe:generate`
|
|
||||||
- [ ] 0.55 Verify no errors in generation
|
|
||||||
- [ ] 0.56 Access `/api/documentation` in browser
|
|
||||||
- [ ] 0.57 Verify all endpoints appear in documentation
|
|
||||||
- [ ] 0.58 Test "Try it out" for login endpoint
|
|
||||||
- [ ] 0.59 Verify authentication flow is documented
|
|
||||||
|
|
||||||
## Commits
|
|
||||||
|
|
||||||
1. `chore(docs): Configure Laravel Scribe for API documentation`
|
|
||||||
2. `docs(api): Add Scribe annotations to AuthController`
|
|
||||||
3. `docs(api): Add Scribe annotations to TeamMemberController`
|
|
||||||
4. `docs(api): Add Scribe annotations to ProjectController`
|
|
||||||
5. `docs(api): Add Scribe annotations to AllocationController`
|
|
||||||
6. `docs(api): Add Scribe annotations to remaining controllers`
|
|
||||||
7. `docs(api): Generate and verify SwaggerUI documentation`
|
|
||||||
22
openspec/specs/api-documentation/spec.md
Normal file
22
openspec/specs/api-documentation/spec.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Capability: API Documentation
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
Provide generated, accessible API documentation for currently implemented authentication endpoints.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Requirement: Generate Authentication API Documentation
|
||||||
|
The system SHALL generate API documentation for authentication endpoints using Laravel Scribe.
|
||||||
|
|
||||||
|
#### Scenario: Generate docs successfully
|
||||||
|
- **WHEN** `php artisan scribe:generate` is run
|
||||||
|
- **THEN** the command completes without errors
|
||||||
|
- **AND** generated documentation includes `POST /api/auth/login`, `POST /api/auth/refresh`, and `POST /api/auth/logout`
|
||||||
|
|
||||||
|
### Requirement: Serve Documentation UI
|
||||||
|
The system SHALL serve documentation at `/api/documentation`.
|
||||||
|
|
||||||
|
#### Scenario: Access generated docs
|
||||||
|
- **WHEN** a client requests `GET /api/documentation`
|
||||||
|
- **THEN** the application responds with HTTP 200
|
||||||
|
- **AND** the page contains the configured API title and authentication guidance
|
||||||
Reference in New Issue
Block a user