diff --git a/backend/.scribe/.filehashes b/backend/.scribe/.filehashes index 9cf1036f..12844723 100644 --- a/backend/.scribe/.filehashes +++ b/backend/.scribe/.filehashes @@ -1,4 +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 \ No newline at end of file +.scribe/intro.md=4bf90470e636417926ae5d9227747d45 +.scribe/auth.md=9bee2b1ef8a238b2e58613fa636d5f39 \ No newline at end of file diff --git a/backend/.scribe/auth.md b/backend/.scribe/auth.md index bf1aa6a7..82903629 100644 --- a/backend/.scribe/auth.md +++ b/backend/.scribe/auth.md @@ -1,7 +1,3 @@ # 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. +This API is not authenticated. diff --git a/backend/.scribe/endpoints.cache/00.yaml b/backend/.scribe/endpoints.cache/00.yaml new file mode 100644 index 00000000..261eb5e4 --- /dev/null +++ b/backend/.scribe/endpoints.cache/00.yaml @@ -0,0 +1,214 @@ +## 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: false + deprecated: false + headers: + 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: [] + 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: + 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: [] + 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: + 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: [] + controller: null + method: null + route: null diff --git a/backend/.scribe/endpoints.cache/01.yaml b/backend/.scribe/endpoints.cache/01.yaml new file mode 100644 index 00000000..4eaae247 --- /dev/null +++ b/backend/.scribe/endpoints.cache/01.yaml @@ -0,0 +1,443 @@ +## Autogenerated by Scribe. DO NOT MODIFY. + +name: 'Team Members' +description: |- + + Endpoints for managing team members. +endpoints: + - + custom: [] + httpMethods: + - GET + uri: api/team-members + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'List all team members' + description: 'Get a list of all team members with optional filtering by active status.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: [] + cleanUrlParameters: [] + queryParameters: + active: + custom: [] + name: active + description: 'Filter by active status.' + required: false + example: true + type: boolean + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanQueryParameters: + active: true + bodyParameters: [] + cleanBodyParameters: [] + fileParameters: [] + responses: + - + custom: [] + status: 200 + content: |- + [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "John Doe", + "role_id": 1, + "role": { + "id": 1, + "name": "Backend Developer" + }, + "hourly_rate": "150.00", + "active": true, + "created_at": "2024-01-15T10:00:00.000000Z", + "updated_at": "2024-01-15T10:00:00.000000Z" + } + ] + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null + - + custom: [] + httpMethods: + - POST + uri: api/team-members + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'Create a new team member' + description: 'Create a new team member with name, role, and hourly rate.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: [] + cleanUrlParameters: [] + queryParameters: [] + cleanQueryParameters: [] + bodyParameters: + name: + custom: [] + name: name + description: 'Team member name.' + required: true + example: 'John Doe' + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + role_id: + custom: [] + name: role_id + description: 'Role ID.' + required: true + example: 1 + type: integer + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + hourly_rate: + custom: [] + name: hourly_rate + description: 'Hourly rate (must be > 0).' + required: true + example: '150.00' + type: numeric + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + active: + custom: [] + name: active + description: 'Active status (defaults to true).' + required: false + example: true + type: boolean + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanBodyParameters: + name: 'John Doe' + role_id: 1 + hourly_rate: '150.00' + active: true + fileParameters: [] + responses: + - + custom: [] + status: 201 + content: |- + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "John Doe", + "role_id": 1, + "role": { + "id": 1, + "name": "Backend Developer" + }, + "hourly_rate": "150.00", + "active": true, + "created_at": "2024-01-15T10:00:00.000000Z", + "updated_at": "2024-01-15T10:00:00.000000Z" + } + headers: [] + description: '' + - + custom: [] + status: 422 + content: '{"message":"Validation failed","errors":{"name":["The name field is required."],"hourly_rate":["Hourly rate must be greater than 0"]}}' + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null + - + custom: [] + httpMethods: + - GET + uri: 'api/team-members/{id}' + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'Get a single team member' + description: 'Get details of a specific team member by ID.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: + id: + custom: [] + name: id + description: 'Team member UUID.' + required: true + example: 550e8400-e29b-41d4-a716-446655440000 + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanUrlParameters: + id: 550e8400-e29b-41d4-a716-446655440000 + queryParameters: [] + cleanQueryParameters: [] + bodyParameters: [] + cleanBodyParameters: [] + fileParameters: [] + responses: + - + custom: [] + status: 200 + content: |- + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "John Doe", + "role_id": 1, + "role": { + "id": 1, + "name": "Backend Developer" + }, + "hourly_rate": "150.00", + "active": true, + "created_at": "2024-01-15T10:00:00.000000Z", + "updated_at": "2024-01-15T10:00:00.000000Z" + } + headers: [] + description: '' + - + custom: [] + status: 404 + content: '{"message":"Team member not found"}' + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null + - + custom: [] + httpMethods: + - PUT + - PATCH + uri: 'api/team-members/{id}' + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'Update a team member' + description: 'Update details of an existing team member.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: + id: + custom: [] + name: id + description: 'Team member UUID.' + required: true + example: 550e8400-e29b-41d4-a716-446655440000 + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanUrlParameters: + id: 550e8400-e29b-41d4-a716-446655440000 + queryParameters: [] + cleanQueryParameters: [] + bodyParameters: + name: + custom: [] + name: name + description: 'Team member name.' + required: false + example: 'John Doe' + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + role_id: + custom: [] + name: role_id + description: 'Role ID.' + required: false + example: 1 + type: integer + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + hourly_rate: + custom: [] + name: hourly_rate + description: 'Hourly rate (must be > 0).' + required: false + example: '175.00' + type: numeric + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + active: + custom: [] + name: active + description: 'Active status.' + required: false + example: false + type: boolean + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanBodyParameters: + name: 'John Doe' + role_id: 1 + hourly_rate: '175.00' + active: false + fileParameters: [] + responses: + - + custom: [] + status: 200 + content: |- + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "John Doe", + "role_id": 1, + "role": { + "id": 1, + "name": "Backend Developer" + }, + "hourly_rate": "175.00", + "active": false, + "created_at": "2024-01-15T10:00:00.000000Z", + "updated_at": "2024-01-15T11:00:00.000000Z" + } + headers: [] + description: '' + - + custom: [] + status: 404 + content: '{"message":"Team member not found"}' + headers: [] + description: '' + - + custom: [] + status: 422 + content: '{"message":"Validation failed","errors":{"hourly_rate":["Hourly rate must be greater than 0"]}}' + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null + - + custom: [] + httpMethods: + - DELETE + uri: 'api/team-members/{id}' + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'Delete a team member' + description: 'Delete a team member. Cannot delete if member has allocations or actuals.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: + id: + custom: [] + name: id + description: 'Team member UUID.' + required: true + example: 550e8400-e29b-41d4-a716-446655440000 + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanUrlParameters: + id: 550e8400-e29b-41d4-a716-446655440000 + queryParameters: [] + cleanQueryParameters: [] + bodyParameters: [] + cleanBodyParameters: [] + fileParameters: [] + responses: + - + custom: [] + status: 200 + content: '{"message":"Team member deleted successfully"}' + headers: [] + description: '' + - + custom: [] + status: 404 + content: '{"message":"Team member not found"}' + headers: [] + description: '' + - + custom: [] + status: 422 + content: '{"message":"Cannot delete team member with active allocations","suggestion":"Consider deactivating the team member instead"}' + headers: [] + description: '' + - + custom: [] + status: 422 + content: '{"message":"Cannot delete team member with historical data","suggestion":"Consider deactivating the team member instead"}' + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null diff --git a/backend/.scribe/endpoints/00.yaml b/backend/.scribe/endpoints/00.yaml new file mode 100644 index 00000000..f7772fb6 --- /dev/null +++ b/backend/.scribe/endpoints/00.yaml @@ -0,0 +1,212 @@ +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: false + deprecated: false + headers: + 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: [] + 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: + 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: [] + 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: + 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: [] + controller: null + method: null + route: null diff --git a/backend/.scribe/endpoints/01.yaml b/backend/.scribe/endpoints/01.yaml new file mode 100644 index 00000000..0d3fb6b3 --- /dev/null +++ b/backend/.scribe/endpoints/01.yaml @@ -0,0 +1,441 @@ +name: 'Team Members' +description: |- + + Endpoints for managing team members. +endpoints: + - + custom: [] + httpMethods: + - GET + uri: api/team-members + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'List all team members' + description: 'Get a list of all team members with optional filtering by active status.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: [] + cleanUrlParameters: [] + queryParameters: + active: + custom: [] + name: active + description: 'Filter by active status.' + required: false + example: true + type: boolean + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanQueryParameters: + active: true + bodyParameters: [] + cleanBodyParameters: [] + fileParameters: [] + responses: + - + custom: [] + status: 200 + content: |- + [ + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "John Doe", + "role_id": 1, + "role": { + "id": 1, + "name": "Backend Developer" + }, + "hourly_rate": "150.00", + "active": true, + "created_at": "2024-01-15T10:00:00.000000Z", + "updated_at": "2024-01-15T10:00:00.000000Z" + } + ] + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null + - + custom: [] + httpMethods: + - POST + uri: api/team-members + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'Create a new team member' + description: 'Create a new team member with name, role, and hourly rate.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: [] + cleanUrlParameters: [] + queryParameters: [] + cleanQueryParameters: [] + bodyParameters: + name: + custom: [] + name: name + description: 'Team member name.' + required: true + example: 'John Doe' + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + role_id: + custom: [] + name: role_id + description: 'Role ID.' + required: true + example: 1 + type: integer + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + hourly_rate: + custom: [] + name: hourly_rate + description: 'Hourly rate (must be > 0).' + required: true + example: '150.00' + type: numeric + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + active: + custom: [] + name: active + description: 'Active status (defaults to true).' + required: false + example: true + type: boolean + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanBodyParameters: + name: 'John Doe' + role_id: 1 + hourly_rate: '150.00' + active: true + fileParameters: [] + responses: + - + custom: [] + status: 201 + content: |- + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "John Doe", + "role_id": 1, + "role": { + "id": 1, + "name": "Backend Developer" + }, + "hourly_rate": "150.00", + "active": true, + "created_at": "2024-01-15T10:00:00.000000Z", + "updated_at": "2024-01-15T10:00:00.000000Z" + } + headers: [] + description: '' + - + custom: [] + status: 422 + content: '{"message":"Validation failed","errors":{"name":["The name field is required."],"hourly_rate":["Hourly rate must be greater than 0"]}}' + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null + - + custom: [] + httpMethods: + - GET + uri: 'api/team-members/{id}' + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'Get a single team member' + description: 'Get details of a specific team member by ID.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: + id: + custom: [] + name: id + description: 'Team member UUID.' + required: true + example: 550e8400-e29b-41d4-a716-446655440000 + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanUrlParameters: + id: 550e8400-e29b-41d4-a716-446655440000 + queryParameters: [] + cleanQueryParameters: [] + bodyParameters: [] + cleanBodyParameters: [] + fileParameters: [] + responses: + - + custom: [] + status: 200 + content: |- + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "John Doe", + "role_id": 1, + "role": { + "id": 1, + "name": "Backend Developer" + }, + "hourly_rate": "150.00", + "active": true, + "created_at": "2024-01-15T10:00:00.000000Z", + "updated_at": "2024-01-15T10:00:00.000000Z" + } + headers: [] + description: '' + - + custom: [] + status: 404 + content: '{"message":"Team member not found"}' + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null + - + custom: [] + httpMethods: + - PUT + - PATCH + uri: 'api/team-members/{id}' + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'Update a team member' + description: 'Update details of an existing team member.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: + id: + custom: [] + name: id + description: 'Team member UUID.' + required: true + example: 550e8400-e29b-41d4-a716-446655440000 + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanUrlParameters: + id: 550e8400-e29b-41d4-a716-446655440000 + queryParameters: [] + cleanQueryParameters: [] + bodyParameters: + name: + custom: [] + name: name + description: 'Team member name.' + required: false + example: 'John Doe' + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + role_id: + custom: [] + name: role_id + description: 'Role ID.' + required: false + example: 1 + type: integer + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + hourly_rate: + custom: [] + name: hourly_rate + description: 'Hourly rate (must be > 0).' + required: false + example: '175.00' + type: numeric + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + active: + custom: [] + name: active + description: 'Active status.' + required: false + example: false + type: boolean + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanBodyParameters: + name: 'John Doe' + role_id: 1 + hourly_rate: '175.00' + active: false + fileParameters: [] + responses: + - + custom: [] + status: 200 + content: |- + { + "id": "550e8400-e29b-41d4-a716-446655440000", + "name": "John Doe", + "role_id": 1, + "role": { + "id": 1, + "name": "Backend Developer" + }, + "hourly_rate": "175.00", + "active": false, + "created_at": "2024-01-15T10:00:00.000000Z", + "updated_at": "2024-01-15T11:00:00.000000Z" + } + headers: [] + description: '' + - + custom: [] + status: 404 + content: '{"message":"Team member not found"}' + headers: [] + description: '' + - + custom: [] + status: 422 + content: '{"message":"Validation failed","errors":{"hourly_rate":["Hourly rate must be greater than 0"]}}' + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null + - + custom: [] + httpMethods: + - DELETE + uri: 'api/team-members/{id}' + metadata: + custom: [] + groupName: 'Team Members' + groupDescription: |- + + Endpoints for managing team members. + subgroup: '' + subgroupDescription: '' + title: 'Delete a team member' + description: 'Delete a team member. Cannot delete if member has allocations or actuals.' + authenticated: true + deprecated: false + headers: + Content-Type: application/json + Accept: application/json + urlParameters: + id: + custom: [] + name: id + description: 'Team member UUID.' + required: true + example: 550e8400-e29b-41d4-a716-446655440000 + type: string + enumValues: [] + exampleWasSpecified: true + nullable: false + deprecated: false + cleanUrlParameters: + id: 550e8400-e29b-41d4-a716-446655440000 + queryParameters: [] + cleanQueryParameters: [] + bodyParameters: [] + cleanBodyParameters: [] + fileParameters: [] + responses: + - + custom: [] + status: 200 + content: '{"message":"Team member deleted successfully"}' + headers: [] + description: '' + - + custom: [] + status: 404 + content: '{"message":"Team member not found"}' + headers: [] + description: '' + - + custom: [] + status: 422 + content: '{"message":"Cannot delete team member with active allocations","suggestion":"Consider deactivating the team member instead"}' + headers: [] + description: '' + - + custom: [] + status: 422 + content: '{"message":"Cannot delete team member with historical data","suggestion":"Consider deactivating the team member instead"}' + headers: [] + description: '' + responseFields: [] + auth: [] + controller: null + method: null + route: null diff --git a/backend/.scribe/intro.md b/backend/.scribe/intro.md index bc297e8a..95f6bdff 100644 --- a/backend/.scribe/intro.md +++ b/backend/.scribe/intro.md @@ -1,12 +1,13 @@ # Introduction -Resource planning and capacity management API + - Authenticate by sending `Authorization: Bearer {access_token}` on protected endpoints. + This documentation aims to provide all the information you need to work with our API. - 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. + diff --git a/backend/config/scribe.php b/backend/config/scribe.php index f747ed20..8fdbe8a8 100644 --- a/backend/config/scribe.php +++ b/backend/config/scribe.php @@ -1,24 +1,32 @@ for the generated documentation. - 'title' => 'Headroom API', + 'title' => config('app.name').' API Documentation', // A short description of your API. Will be included in the docs webpage, Postman collection and OpenAPI spec. - 'description' => 'Resource planning and capacity management API', + 'description' => '', // Text to place in the "Introduction" section, right after the `description`. Markdown and HTML are supported. 'intro_text' => <<<'INTRO' - Authenticate by sending `Authorization: Bearer {access_token}` on protected endpoints. + This documentation aims to provide all the information you need to work with our API. - 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. + INTRO, // The base URL displayed in the docs. // If you're using `laravel` type, you can set this to a dynamic string, like '{{ config("app.tenant_url") }}' to get a dynamic base URL. - 'base_url' => rtrim(config('app.url'), '/').'/api', + 'base_url' => config('app.url'), // Routes to include in the docs 'routes' => [ @@ -38,7 +46,7 @@ return [ // Exclude these routes even if they matched the rules above. 'exclude' => [ - 'api/user', + // 'GET /health', 'admin.*' ], ], ], @@ -64,7 +72,7 @@ return [ // URL path to use for the docs endpoint (if `add_routes` is true). // By default, `/docs` opens the HTML page, `/docs.postman` opens the Postman collection, and `/docs.openapi` the OpenAPI spec. - 'docs_url' => '/api/documentation', + 'docs_url' => '/docs', // Directory within `public` in which to store CSS and JS assets. // By default, assets are stored in `public/vendor/scribe`. @@ -97,28 +105,28 @@ return [ // How is your API authenticated? This information will be used in the displayed docs, generated examples and response calls. 'auth' => [ // Set this to true if ANY endpoints in your API use authentication. - 'enabled' => true, + 'enabled' => false, // Set this to true if your API should be authenticated by default. If so, you must also set `enabled` (above) to true. // You can then use @unauthenticated or @authenticated on individual endpoints to change their status from the default. - 'default' => true, + 'default' => false, // Where is the auth value meant to be sent in a request? - 'in' => 'bearer', + 'in' => AuthIn::BEARER->value, // The name of the auth parameter (e.g. token, key, apiKey) or header (e.g. Authorization, Api-Key). - 'name' => 'Authorization', + 'name' => 'key', // The value of the parameter to be used by Scribe to authenticate response calls. // This will NOT be included in the generated documentation. If empty, Scribe will use a random value. - 'use_value' => 'Bearer {token}', + 'use_value' => env('SCRIBE_AUTH_KEY'), // Placeholder your users will see for the auth parameter in the example requests. // Set this to null if you want Scribe to use a random value as placeholder instead. - 'placeholder' => 'Bearer {token}', + 'placeholder' => '{YOUR_AUTH_KEY}', // Any extra authentication-related info for your users. Markdown and HTML are supported. - 'extra_info' => 'Get tokens from `POST /api/auth/login`, send access token as `Bearer {token}`, and renew with `POST /api/auth/refresh` before access token expiry.', + 'extra_info' => 'You can retrieve your token by visiting your dashboard and clicking Generate API token.', ], // Example requests for each endpoint will be shown in each of these languages. @@ -205,18 +213,38 @@ return [ // Use configureStrategy() to specify settings for a strategy in the list. // Use removeStrategies() to remove an included strategy. 'strategies' => [ - 'metadata' => [], + 'metadata' => [ + ...Defaults::METADATA_STRATEGIES, + ], 'headers' => [ - [ + ...Defaults::HEADERS_STRATEGIES, + Strategies\StaticData::withSettings(data: [ 'Content-Type' => 'application/json', 'Accept' => 'application/json', - ], + ]), + ], + 'urlParameters' => [ + ...Defaults::URL_PARAMETERS_STRATEGIES, + ], + 'queryParameters' => [ + ...Defaults::QUERY_PARAMETERS_STRATEGIES, + ], + 'bodyParameters' => [ + ...Defaults::BODY_PARAMETERS_STRATEGIES, + ], + 'responses' => configureStrategy( + Defaults::RESPONSES_STRATEGIES, + Strategies\Responses\ResponseCalls::withSettings( + only: ['GET *'], + // Recommended: disable debug mode in response calls to avoid error stack traces in responses + config: [ + 'app.debug' => false, + ] + ) + ), + 'responseFields' => [ + ...Defaults::RESPONSE_FIELDS_STRATEGIES, ], - 'urlParameters' => [], - 'queryParameters' => [], - 'bodyParameters' => [], - 'responses' => [], - 'responseFields' => [], ], // For response calls, API resource responses and transformer responses, diff --git a/backend/resources/views/scribe/index.blade.php b/backend/resources/views/scribe/index.blade.php index 49c5df9a..a95f5128 100644 --- a/backend/resources/views/scribe/index.blade.php +++ b/backend/resources/views/scribe/index.blade.php @@ -4,7 +4,7 @@ - Headroom API + Laravel API Documentation @@ -26,7 +26,7 @@ @@ -66,6 +66,44 @@ Authenticating requests + +