diff --git a/frameworks-laravel/.gitignore b/frameworks-laravel/.gitignore index 3113dfe..95c2342 100644 --- a/frameworks-laravel/.gitignore +++ b/frameworks-laravel/.gitignore @@ -8,7 +8,6 @@ /.fleet /.idea /.nova -/.scribe /.phpunit.cache /.vscode /.zed @@ -23,3 +22,6 @@ Homestead.json Homestead.yaml Thumbs.db + +# Scribe +.scribe diff --git a/frameworks-laravel/.scribe/.filehashes b/frameworks-laravel/.scribe/.filehashes deleted file mode 100644 index 51da126..0000000 --- a/frameworks-laravel/.scribe/.filehashes +++ /dev/null @@ -1,4 +0,0 @@ -# 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=35310953ddf7f9e68771f7ed7b6189f9 -.scribe/auth.md=9bee2b1ef8a238b2e58613fa636d5f39 \ No newline at end of file diff --git a/frameworks-laravel/.scribe/auth.md b/frameworks-laravel/.scribe/auth.md deleted file mode 100644 index 8290362..0000000 --- a/frameworks-laravel/.scribe/auth.md +++ /dev/null @@ -1,3 +0,0 @@ -# Authenticating requests - -This API is not authenticated. diff --git a/frameworks-laravel/.scribe/endpoints.cache/00.yaml b/frameworks-laravel/.scribe/endpoints.cache/00.yaml deleted file mode 100644 index 151bc32..0000000 --- a/frameworks-laravel/.scribe/endpoints.cache/00.yaml +++ /dev/null @@ -1,321 +0,0 @@ -## Autogenerated by Scribe. DO NOT MODIFY. - -name: Endpoints -description: '' -endpoints: - - - custom: [] - httpMethods: - - GET - uri: api/health - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: '' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: [] - cleanUrlParameters: [] - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"status":"healthy","version":"unversioned","timestamp":"2025-11-03T10:55:55+00:00"}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: api/drivers - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display a listing of the resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: [] - cleanUrlParameters: [] - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":[{"id":1,"name":"Max Verstappen","code":"VER","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":2,"name":"Sergio Perez","code":"PER","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":3,"name":"Lewis Hamilton","code":"HAM","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":4,"name":"George Russell","code":"RUS","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":5,"name":"Charles Leclerc","code":"LEC","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":6,"name":"Carlos Sainz","code":"SAI","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":7,"name":"Lando Norris","code":"NOR","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":8,"name":"Oscar Piastri","code":"PIA","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":9,"name":"Fernando Alonso","code":"ALO","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":10,"name":"Lance Stroll","code":"STR","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"}],"meta":{"count":10}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: 'api/drivers/{id}' - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display the specified resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: - id: - custom: [] - name: id - description: 'The ID of the driver.' - required: true - example: 1 - type: integer - enumValues: [] - exampleWasSpecified: false - nullable: false - deprecated: false - cleanUrlParameters: - id: 1 - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":{"id":1,"name":"Max Verstappen","code":"VER","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: api/circuits - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display a listing of the resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: [] - cleanUrlParameters: [] - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":[{"id":1,"name":"Monaco Grand Prix","location":"Monte Carlo, Monaco","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":2,"name":"British Grand Prix","location":"Silverstone, United Kingdom","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":3,"name":"Italian Grand Prix","location":"Monza, Italy","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":4,"name":"Belgian Grand Prix","location":"Spa-Francorchamps, Belgium","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":5,"name":"Japanese Grand Prix","location":"Suzuka, Japan","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":6,"name":"Singapore Grand Prix","location":"Marina Bay, Singapore","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":7,"name":"Abu Dhabi Grand Prix","location":"Yas Marina, UAE","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":8,"name":"Brazilian Grand Prix","location":"Interlagos, Brazil","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":9,"name":"Australian Grand Prix","location":"Melbourne, Australia","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":10,"name":"Spanish Grand Prix","location":"Barcelona, Spain","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"}],"meta":{"count":10}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: 'api/circuits/{id}' - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display the specified resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: - id: - custom: [] - name: id - description: 'The ID of the circuit.' - required: true - example: 1 - type: integer - enumValues: [] - exampleWasSpecified: false - nullable: false - deprecated: false - cleanUrlParameters: - id: 1 - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":{"id":1,"name":"Monaco Grand Prix","location":"Monte Carlo, Monaco","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: api/races - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display a listing of the resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: [] - cleanUrlParameters: [] - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":[{"id":1,"name":"2024 Monaco Grand Prix","race_date":"2024-05-26","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/1","circuit":"http:\/\/localhost\/api\/circuits\/1","drivers":"http:\/\/localhost\/api\/drivers?race=1"}},{"id":2,"name":"2024 British Grand Prix","race_date":"2024-07-07","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/2","circuit":"http:\/\/localhost\/api\/circuits\/2","drivers":"http:\/\/localhost\/api\/drivers?race=2"}},{"id":3,"name":"2024 Italian Grand Prix","race_date":"2024-09-01","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/3","circuit":"http:\/\/localhost\/api\/circuits\/3","drivers":"http:\/\/localhost\/api\/drivers?race=3"}},{"id":4,"name":"2024 Belgian Grand Prix","race_date":"2024-07-28","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/4","circuit":"http:\/\/localhost\/api\/circuits\/4","drivers":"http:\/\/localhost\/api\/drivers?race=4"}},{"id":5,"name":"2024 Japanese Grand Prix","race_date":"2024-04-07","season":"2024","created_at":"2025-10-29T17:21:40+00:00","updated_at":"2025-10-29T17:21:40+00:00","links":{"self":"http:\/\/localhost\/api\/races\/5","circuit":"http:\/\/localhost\/api\/circuits\/5","drivers":"http:\/\/localhost\/api\/drivers?race=5"}}],"meta":{"count":5}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: 'api/races/{id}' - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display the specified resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: - id: - custom: [] - name: id - description: 'The ID of the race.' - required: true - example: 1 - type: integer - enumValues: [] - exampleWasSpecified: false - nullable: false - deprecated: false - cleanUrlParameters: - id: 1 - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":{"id":1,"name":"2024 Monaco Grand Prix","race_date":"2024-05-26","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/1","circuit":"http:\/\/localhost\/api\/circuits\/1","drivers":"http:\/\/localhost\/api\/drivers?race=1"}}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null diff --git a/frameworks-laravel/.scribe/endpoints/00.yaml b/frameworks-laravel/.scribe/endpoints/00.yaml deleted file mode 100644 index b7775ac..0000000 --- a/frameworks-laravel/.scribe/endpoints/00.yaml +++ /dev/null @@ -1,319 +0,0 @@ -name: Endpoints -description: '' -endpoints: - - - custom: [] - httpMethods: - - GET - uri: api/health - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: '' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: [] - cleanUrlParameters: [] - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"status":"healthy","version":"unversioned","timestamp":"2025-11-03T10:55:55+00:00"}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: api/drivers - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display a listing of the resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: [] - cleanUrlParameters: [] - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":[{"id":1,"name":"Max Verstappen","code":"VER","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":2,"name":"Sergio Perez","code":"PER","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":3,"name":"Lewis Hamilton","code":"HAM","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":4,"name":"George Russell","code":"RUS","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":5,"name":"Charles Leclerc","code":"LEC","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":6,"name":"Carlos Sainz","code":"SAI","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":7,"name":"Lando Norris","code":"NOR","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":8,"name":"Oscar Piastri","code":"PIA","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":9,"name":"Fernando Alonso","code":"ALO","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":10,"name":"Lance Stroll","code":"STR","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"}],"meta":{"count":10}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: 'api/drivers/{id}' - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display the specified resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: - id: - custom: [] - name: id - description: 'The ID of the driver.' - required: true - example: 1 - type: integer - enumValues: [] - exampleWasSpecified: false - nullable: false - deprecated: false - cleanUrlParameters: - id: 1 - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":{"id":1,"name":"Max Verstappen","code":"VER","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: api/circuits - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display a listing of the resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: [] - cleanUrlParameters: [] - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":[{"id":1,"name":"Monaco Grand Prix","location":"Monte Carlo, Monaco","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":2,"name":"British Grand Prix","location":"Silverstone, United Kingdom","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":3,"name":"Italian Grand Prix","location":"Monza, Italy","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":4,"name":"Belgian Grand Prix","location":"Spa-Francorchamps, Belgium","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":5,"name":"Japanese Grand Prix","location":"Suzuka, Japan","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":6,"name":"Singapore Grand Prix","location":"Marina Bay, Singapore","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":7,"name":"Abu Dhabi Grand Prix","location":"Yas Marina, UAE","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":8,"name":"Brazilian Grand Prix","location":"Interlagos, Brazil","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":9,"name":"Australian Grand Prix","location":"Melbourne, Australia","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"},{"id":10,"name":"Spanish Grand Prix","location":"Barcelona, Spain","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"}],"meta":{"count":10}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: 'api/circuits/{id}' - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display the specified resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: - id: - custom: [] - name: id - description: 'The ID of the circuit.' - required: true - example: 1 - type: integer - enumValues: [] - exampleWasSpecified: false - nullable: false - deprecated: false - cleanUrlParameters: - id: 1 - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":{"id":1,"name":"Monaco Grand Prix","location":"Monte Carlo, Monaco","created_at":"2025-10-29T17:21:39.000000Z","updated_at":"2025-10-29T17:21:39.000000Z"}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: api/races - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display a listing of the resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: [] - cleanUrlParameters: [] - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":[{"id":1,"name":"2024 Monaco Grand Prix","race_date":"2024-05-26","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/1","circuit":"http:\/\/localhost\/api\/circuits\/1","drivers":"http:\/\/localhost\/api\/drivers?race=1"}},{"id":2,"name":"2024 British Grand Prix","race_date":"2024-07-07","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/2","circuit":"http:\/\/localhost\/api\/circuits\/2","drivers":"http:\/\/localhost\/api\/drivers?race=2"}},{"id":3,"name":"2024 Italian Grand Prix","race_date":"2024-09-01","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/3","circuit":"http:\/\/localhost\/api\/circuits\/3","drivers":"http:\/\/localhost\/api\/drivers?race=3"}},{"id":4,"name":"2024 Belgian Grand Prix","race_date":"2024-07-28","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/4","circuit":"http:\/\/localhost\/api\/circuits\/4","drivers":"http:\/\/localhost\/api\/drivers?race=4"}},{"id":5,"name":"2024 Japanese Grand Prix","race_date":"2024-04-07","season":"2024","created_at":"2025-10-29T17:21:40+00:00","updated_at":"2025-10-29T17:21:40+00:00","links":{"self":"http:\/\/localhost\/api\/races\/5","circuit":"http:\/\/localhost\/api\/circuits\/5","drivers":"http:\/\/localhost\/api\/drivers?race=5"}}],"meta":{"count":5}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null - - - custom: [] - httpMethods: - - GET - uri: 'api/races/{id}' - metadata: - custom: [] - groupName: Endpoints - groupDescription: '' - subgroup: '' - subgroupDescription: '' - title: 'Display the specified resource.' - description: '' - authenticated: false - deprecated: false - headers: - Content-Type: application/json - Accept: application/json - urlParameters: - id: - custom: [] - name: id - description: 'The ID of the race.' - required: true - example: 1 - type: integer - enumValues: [] - exampleWasSpecified: false - nullable: false - deprecated: false - cleanUrlParameters: - id: 1 - queryParameters: [] - cleanQueryParameters: [] - bodyParameters: [] - cleanBodyParameters: [] - fileParameters: [] - responses: - - - custom: [] - status: 200 - content: '{"data":{"id":1,"name":"2024 Monaco Grand Prix","race_date":"2024-05-26","season":"2024","created_at":"2025-10-29T17:21:39+00:00","updated_at":"2025-10-29T17:21:39+00:00","links":{"self":"http:\/\/localhost\/api\/races\/1","circuit":"http:\/\/localhost\/api\/circuits\/1","drivers":"http:\/\/localhost\/api\/drivers?race=1"}}}' - headers: - cache-control: 'no-cache, private' - content-type: application/json - access-control-allow-origin: '*' - description: null - responseFields: [] - auth: [] - controller: null - method: null - route: null diff --git a/frameworks-laravel/.scribe/endpoints/custom.0.yaml b/frameworks-laravel/.scribe/endpoints/custom.0.yaml deleted file mode 100644 index 4b02352..0000000 --- a/frameworks-laravel/.scribe/endpoints/custom.0.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# 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 diff --git a/frameworks-laravel/.scribe/intro.md b/frameworks-laravel/.scribe/intro.md deleted file mode 100644 index e89564d..0000000 --- a/frameworks-laravel/.scribe/intro.md +++ /dev/null @@ -1,11 +0,0 @@ -# Introduction - - API for managing Formula 1 races, drivers, and circuits - - - - Welcome to the F1 Race API documentation! - This documentation aims to provide all the information you need to work with our API. - diff --git a/frameworks-laravel/README.md b/frameworks-laravel/README.md index 57888ee..629437b 100644 --- a/frameworks-laravel/README.md +++ b/frameworks-laravel/README.md @@ -93,7 +93,7 @@ php artisan scribe:generate ## OpenAPI -The API description is exported by [Scribe](https://scribe.knuckles.wtf/laravel/) and can be found at `frameworks-laravel/storage/app/private/scribe/openapi.yaml`. +The API description is exported by [Scribe](https://scribe.knuckles.wtf/laravel/) and can be found in `./storage/app/private/scribe/openapi.yaml`. ## License diff --git a/frameworks-laravel/app/Http/Controllers/Api/HealthController.php b/frameworks-laravel/app/Http/Controllers/Api/HealthController.php index 7e55f26..9559e6e 100644 --- a/frameworks-laravel/app/Http/Controllers/Api/HealthController.php +++ b/frameworks-laravel/app/Http/Controllers/Api/HealthController.php @@ -7,6 +7,13 @@ class HealthController extends Controller { + /** + * Healthcheck + * + * Check that the service is up. If everything is okay, you'll get a 200 OK response. + * + * Otherwise, the request will fail with a 400 error, and a response listing the failed services. + */ public function show(): JsonResponse { return response()->json([ diff --git a/frameworks-laravel/app/Http/Controllers/Api/RaceController.php b/frameworks-laravel/app/Http/Controllers/Api/RaceController.php index 759c690..ebcbe08 100644 --- a/frameworks-laravel/app/Http/Controllers/Api/RaceController.php +++ b/frameworks-laravel/app/Http/Controllers/Api/RaceController.php @@ -7,12 +7,19 @@ use App\Http\Resources\RaceCollection; use App\Models\Race; use Illuminate\Http\Request; +use Knuckles\Scribe\Attributes\{Authenticated, Group, BodyParam, QueryParam}; +#[Group(name: 'Races', description: 'A series of endpoints that allow programmatic access to managing F1 races.', authenticated: true)] class RaceController extends Controller { /** - * Display a listing of the resource. + * Get races + * + * A collection of race resources, newest first, optionally filtered by circuit or season query parameters. */ + #[Authenticated] + #[QueryParam(name: 'season', type: 'string', description: 'Filter the results by season year', required: false, example: '2024')] + #[QueryParam(name: 'circuit', type: 'string', description: 'Filter the results by circuit name', required: false, example: 'Monaco')] public function index(Request $request): RaceCollection { $query = Race::query(); @@ -29,8 +36,16 @@ public function index(Request $request): RaceCollection } /** - * Store a newly created resource in storage. + * Create a race + * + * Allows authenticated users to submit a new Race resource to the system. */ + #[Authenticated] + #[BodyParam(name: 'name', type: 'string', description: 'The name of the race.', required: true, example: 'Monaco Grand Prix')] + #[BodyParam(name: 'race_date', type: 'string', description: 'The date and time the race takes place, RFC 3339 in local timezone.', required: true, example: '2024-05-26T14:53:59')] + #[BodyParam(name: 'circuit_id', type: 'string', description: 'The Unique Identifier for the circuit where the race will be held.', required: true, example: '1234-1234-1234-1234')] + #[BodyParam(name: 'season', type: 'string', description: 'The season year for this race.', required: true, example: '2024')] + #[BodyParam(name: 'driver_ids', type: 'array', description: 'An array of Unique Identifiers for drivers participating in the race.', required: false, example: [ "5678-5678-5678-5678", "6789-6789-6789-6789" ])] public function store(Request $request): RaceResource { $validated = $request->validate([ diff --git a/frameworks-laravel/resources/views/scribe/index.blade.php b/frameworks-laravel/resources/views/scribe/index.blade.php index 7d47a05..6240ef5 100644 --- a/frameworks-laravel/resources/views/scribe/index.blade.php +++ b/frameworks-laravel/resources/views/scribe/index.blade.php @@ -72,7 +72,7 @@ @@ -103,7 +106,7 @@ @@ -125,12 +128,13 @@ -

GET api/health

+

Healthcheck

- +

Check that the service is up. If everything is okay, you'll get a 200 OK response.

+

Otherwise, the request will fail with a 400 error, and a response listing the failed services.

Example request:
@@ -176,7 +180,7 @@ { "status": "healthy", "version": "unversioned", - "timestamp": "2025-11-03T10:55:55+00:00" + "timestamp": "2025-11-16T15:08:22+00:00" }
@@ -943,12 +947,13 @@ -

Display a listing of the resource.

+

Get races

+requires authentication

- +

A collection of race resources, newest first, optionally filtered by circuit or season query parameters.

Example request:
@@ -956,7 +961,7 @@
curl --request GET \
-    --get "http://localhost/api/races" \
+    --get "http://localhost/api/races?season=2024&circuit=Monaco" \
     --header "Content-Type: application/json" \
     --header "Accept: application/json"
@@ -966,6 +971,13 @@ "http://localhost/api/races" ); +const params = { + "season": "2024", + "circuit": "Monaco", +}; +Object.keys(params) + .forEach(key => url.searchParams.append(key, params[key])); + const headers = { "Content-Type": "application/json", "Accept": "application/json", @@ -992,75 +1004,9 @@
 
 {
-    "data": [
-        {
-            "id": 1,
-            "name": "2024 Monaco Grand Prix",
-            "race_date": "2024-05-26",
-            "season": "2024",
-            "created_at": "2025-10-29T17:21:39+00:00",
-            "updated_at": "2025-10-29T17:21:39+00:00",
-            "links": {
-                "self": "http://localhost/api/races/1",
-                "circuit": "http://localhost/api/circuits/1",
-                "drivers": "http://localhost/api/drivers?race=1"
-            }
-        },
-        {
-            "id": 2,
-            "name": "2024 British Grand Prix",
-            "race_date": "2024-07-07",
-            "season": "2024",
-            "created_at": "2025-10-29T17:21:39+00:00",
-            "updated_at": "2025-10-29T17:21:39+00:00",
-            "links": {
-                "self": "http://localhost/api/races/2",
-                "circuit": "http://localhost/api/circuits/2",
-                "drivers": "http://localhost/api/drivers?race=2"
-            }
-        },
-        {
-            "id": 3,
-            "name": "2024 Italian Grand Prix",
-            "race_date": "2024-09-01",
-            "season": "2024",
-            "created_at": "2025-10-29T17:21:39+00:00",
-            "updated_at": "2025-10-29T17:21:39+00:00",
-            "links": {
-                "self": "http://localhost/api/races/3",
-                "circuit": "http://localhost/api/circuits/3",
-                "drivers": "http://localhost/api/drivers?race=3"
-            }
-        },
-        {
-            "id": 4,
-            "name": "2024 Belgian Grand Prix",
-            "race_date": "2024-07-28",
-            "season": "2024",
-            "created_at": "2025-10-29T17:21:39+00:00",
-            "updated_at": "2025-10-29T17:21:39+00:00",
-            "links": {
-                "self": "http://localhost/api/races/4",
-                "circuit": "http://localhost/api/circuits/4",
-                "drivers": "http://localhost/api/drivers?race=4"
-            }
-        },
-        {
-            "id": 5,
-            "name": "2024 Japanese Grand Prix",
-            "race_date": "2024-04-07",
-            "season": "2024",
-            "created_at": "2025-10-29T17:21:40+00:00",
-            "updated_at": "2025-10-29T17:21:40+00:00",
-            "links": {
-                "self": "http://localhost/api/races/5",
-                "circuit": "http://localhost/api/circuits/5",
-                "drivers": "http://localhost/api/drivers?race=5"
-            }
-        }
-    ],
+    "data": [],
     "meta": {
-        "count": 5
+        "count": 0
     }
 }
  
@@ -1082,7 +1028,7 @@

Example: application/json

-
+

Query Parameters

+
+ season   +string  +optional   +   + +
+

Filter the results by season year Example: 2024

+
+
+ circuit   +string  +optional   +   + +
+

Filter the results by circuit name Example: Monaco

+
+

Display the specified resource.

@@ -1289,6 +1260,202 @@ +

Create a race

+ +

+

+ +

Allows authenticated users to submit a new Race resource to the system.

+ + +
Example request:
+ + +
+
curl --request POST \
+    "http://localhost/api/races" \
+    --header "Content-Type: application/json" \
+    --header "Accept: application/json" \
+    --data "{
+    \"name\": \"Monaco Grand Prix\",
+    \"circuit_id\": \"1234-1234-1234-1234\",
+    \"race_date\": \"2024-05-26T14:53:59\",
+    \"season\": \"2024\",
+    \"driver_ids\": [
+        \"5678-5678-5678-5678\",
+        \"6789-6789-6789-6789\"
+    ]
+}"
+
+ + +
+
const url = new URL(
+    "http://localhost/api/races"
+);
+
+const headers = {
+    "Content-Type": "application/json",
+    "Accept": "application/json",
+};
+
+let body = {
+    "name": "Monaco Grand Prix",
+    "circuit_id": "1234-1234-1234-1234",
+    "race_date": "2024-05-26T14:53:59",
+    "season": "2024",
+    "driver_ids": [
+        "5678-5678-5678-5678",
+        "6789-6789-6789-6789"
+    ]
+};
+
+fetch(url, {
+    method: "POST",
+    headers,
+    body: JSON.stringify(body),
+}).then(response => response.json());
+ +
+ + + + + +
+

+ Request    + +    + +

+

+ POST + api/races +

+

Headers

+
+ Content-Type   +  +   +   + +
+

Example: application/json

+
+
+ Accept   +  +   +   + +
+

Example: application/json

+
+

Body Parameters

+
+ name   +string  +   +   + +
+

The name of the race. Example: Monaco Grand Prix

+
+
+ circuit_id   +string  +   +   + +
+

The Unique Identifier for the circuit where the race will be held. Example: 1234-1234-1234-1234

+
+
+ race_date   +string  +   +   + +
+

The date and time the race takes place, RFC 3339 in local timezone. Example: 2024-05-26T14:53:59

+
+
+ season   +string  +   +   + +
+

The season year for this race. Example: 2024

+
+
+ driver_ids   +string[]  +optional   +   + + +
+

An array of Unique Identifiers for drivers participating in the race.

+
+
+ diff --git a/frameworks-laravel/routes/api.php b/frameworks-laravel/routes/api.php index 6738487..2958424 100644 --- a/frameworks-laravel/routes/api.php +++ b/frameworks-laravel/routes/api.php @@ -4,25 +4,22 @@ use App\Http\Controllers\Api\DriverController; use App\Http\Controllers\Api\HealthController; use App\Http\Controllers\Api\RaceController; -use Illuminate\Http\Request; use Illuminate\Support\Facades\Route; // API routes (no version prefix, base /api is automatic) + // Health check Route::get('/health', [HealthController::class, 'show']); // Drivers Route::get('/drivers', [DriverController::class, 'index']); Route::get('/drivers/{id}', [DriverController::class, 'show']); -// GET-only demo: removed create/update/delete routes // Circuits Route::get('/circuits', [CircuitController::class, 'index']); Route::get('/circuits/{id}', [CircuitController::class, 'show']); -// GET-only demo: removed create/update/delete routes // Races Route::get('/races', [RaceController::class, 'index']); Route::get('/races/{id}', [RaceController::class, 'show']); -// GET-only demo: removed create/update/delete routes -// Removed granular driver attach/detach endpoints to simplify API +Route::post('/races', [RaceController::class, 'store']); diff --git a/frameworks-laravel/tests/Feature/CircuitsTest.php b/frameworks-laravel/tests/Feature/CircuitsTest.php new file mode 100644 index 0000000..ec5c9cb --- /dev/null +++ b/frameworks-laravel/tests/Feature/CircuitsTest.php @@ -0,0 +1,66 @@ +seed(); + }); + + test('returns list of all circuits', function () { + $response = $this->getJson('/api/circuits'); + + $response->assertStatus(200) + ->assertJsonStructure(['data', 'meta']); + + $data = $response->json('data'); + expect($data)->toBeArray()->not->toBeEmpty(); + }); + + test('returns correct data structure', function () { + $response = $this->getJson('/api/circuits'); + + $data = $response->json('data'); + expect($data)->toBeArray(); + + if (count($data) > 0) { + $circuit = $data[0]; + expect($circuit)->toHaveKeys(['id', 'name', 'location']); + } + }); +}); + +describe('GET /api/circuits/{id}', function () { + + beforeEach(function () { + $this->seed(); + }); + + test('returns a specific circuit by id', function () { + $response = $this->getJson('/api/circuits'); + $circuits = $response->json('data'); + expect($circuits)->toBeArray()->not->toBeEmpty(); + + $firstCircuit = $circuits[0]; + + $response = $this->getJson("/api/circuits/{$firstCircuit['id']}"); + + $response->assertStatus(200) + ->assertJsonStructure(['data' => ['id', 'name', 'location']]) + ->assertJson([ + 'data' => [ + 'id' => $firstCircuit['id'], + 'name' => $firstCircuit['name'], + 'location' => $firstCircuit['location'], + ], + ]); + }); + + test('returns 404 for non-existent circuit', function () { + $response = $this->getJson('/api/circuits/99999'); + $response->assertStatus(404); + }); +}); diff --git a/frameworks-laravel/tests/Feature/DriversTest.php b/frameworks-laravel/tests/Feature/DriversTest.php new file mode 100644 index 0000000..41939a9 --- /dev/null +++ b/frameworks-laravel/tests/Feature/DriversTest.php @@ -0,0 +1,66 @@ +seed(); + }); + + test('returns list of all drivers', function () { + $response = $this->getJson('/api/drivers'); + + $response->assertStatus(200) + ->assertJsonStructure(['data', 'meta']); + + $data = $response->json('data'); + expect($data)->toBeArray()->not->toBeEmpty(); + }); + + test('returns correct data structure', function () { + $response = $this->getJson('/api/drivers'); + + $data = $response->json('data'); + expect($data)->toBeArray(); + + if (count($data) > 0) { + $driver = $data[0]; + expect($driver)->toHaveKeys(['id', 'name', 'code']); + } + }); +}); + +describe('GET /api/drivers/{id}', function () { + + beforeEach(function () { + $this->seed(); + }); + + test('returns a specific driver by id', function () { + $response = $this->getJson('/api/drivers'); + $drivers = $response->json('data'); + expect($drivers)->toBeArray()->not->toBeEmpty(); + + $firstDriver = $drivers[0]; + + $response = $this->getJson("/api/drivers/{$firstDriver['id']}"); + + $response->assertStatus(200) + ->assertJsonStructure(['data' => ['id', 'name', 'code']]) + ->assertJson([ + 'data' => [ + 'id' => $firstDriver['id'], + 'name' => $firstDriver['name'], + 'code' => $firstDriver['code'], + ], + ]); + }); + + test('returns 404 for non-existent driver', function () { + $response = $this->getJson('/api/drivers/99999'); + $response->assertStatus(404); + }); +}); diff --git a/frameworks-laravel/tests/Feature/HealthTest.php b/frameworks-laravel/tests/Feature/HealthTest.php new file mode 100644 index 0000000..9634638 --- /dev/null +++ b/frameworks-laravel/tests/Feature/HealthTest.php @@ -0,0 +1,19 @@ +seed(); + }); + + test('returns success status', function () { + $response = $this->getJson('/api/health'); + + $response->assertStatus(200) + ->assertJsonStructure(['status']); + }); +}); diff --git a/frameworks-laravel/tests/Feature/RacesTest.php b/frameworks-laravel/tests/Feature/RacesTest.php new file mode 100644 index 0000000..27b2b6d --- /dev/null +++ b/frameworks-laravel/tests/Feature/RacesTest.php @@ -0,0 +1,59 @@ +seed(); + }); + + test('returns list of all races', function () { + $response = $this->getJson('/api/races'); + + $response->assertStatus(200) + ->assertJsonStructure(['data', 'meta']); + + $data = $response->json('data'); + expect($data)->toBeArray()->not->toBeEmpty(); + }); + + test('returns correct data structure', function () { + $response = $this->getJson('/api/races'); + + $data = $response->json('data'); + expect($data)->toBeArray(); + + if (count($data) > 0) { + $race = $data[0]; + expect($race)->toHaveKeys(['id', 'name', 'season']); + } + }); +}); + +describe('GET /api/races/{id}', function () { + + beforeEach(function () { + $this->seed(); + }); + + test('returns a specific race by id', function () { + $response = $this->getJson('/api/races'); + $races = $response->json('data'); + expect($races)->toBeArray()->not->toBeEmpty(); + + $firstRace = $races[0]; + + $response = $this->getJson("/api/races/{$firstRace['id']}"); + + $response->assertStatus(200) + ->assertJsonStructure(['data' => ['id', 'name', 'season', 'race_date', 'links']]); + }); + + test('returns 404 for non-existent race', function () { + $response = $this->getJson('/api/races/99999'); + $response->assertStatus(404); + }); +}); diff --git a/frameworks-laravel/tests/Pest.php b/frameworks-laravel/tests/Pest.php new file mode 100644 index 0000000..a7b28ca --- /dev/null +++ b/frameworks-laravel/tests/Pest.php @@ -0,0 +1,3 @@ +in('Feature');