├── .github
├── dependabot.yml
├── release-drafter.yml
└── workflows
│ ├── release-notes.yml
│ └── tests.yml
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── composer.json
├── config
└── compass.php
├── docs
├── authenticator.md
├── configuration.md
├── documenter.md
├── installation.md
└── introduction.md
├── package.json
├── public
├── app.css
├── app.js
├── favicon.ico
├── manifest.js
├── mix-manifest.json
├── vendor.js
└── vendor.js.LICENSE.txt
├── resources
├── js
│ ├── app.js
│ ├── axios-instance.js
│ ├── base.js
│ ├── components
│ │ ├── Alert.vue
│ │ ├── CodeEditor.vue
│ │ ├── ContentSpace.vue
│ │ ├── DataTable.vue
│ │ ├── Dropdown.vue
│ │ ├── FilesInput.vue
│ │ ├── HeaderFields.vue
│ │ ├── HttpMethods.vue
│ │ ├── HttpResponseSize.vue
│ │ ├── HttpResponseTime.vue
│ │ ├── HttpStatus.vue
│ │ ├── Omnibox.vue
│ │ ├── RequestTabs.vue
│ │ ├── ResponseTabs.vue
│ │ ├── SidebarMenu.vue
│ │ ├── SiteHeader.vue
│ │ ├── Spotlight.vue
│ │ ├── request
│ │ │ ├── AuthTab.vue
│ │ │ ├── BodyTab.vue
│ │ │ ├── DocsTab.vue
│ │ │ ├── HeadersTab.vue
│ │ │ ├── ParamsTab.vue
│ │ │ ├── RouteTab.vue
│ │ │ └── auth
│ │ │ │ ├── Bearer.vue
│ │ │ │ └── None.vue
│ │ └── response
│ │ │ ├── BodyTab.vue
│ │ │ └── HeadersTab.vue
│ ├── constants.js
│ ├── pages
│ │ ├── 404.vue
│ │ ├── cortex.vue
│ │ ├── example.vue
│ │ └── welcome.vue
│ └── routes.js
├── sass
│ ├── _base.scss
│ ├── _codemirror.scss
│ ├── _modal.scss
│ ├── _multiselect.scss
│ ├── _spotlight.scss
│ └── app.scss
└── views
│ ├── documenter
│ └── documentarian
│ │ ├── layout.blade.php
│ │ └── partials
│ │ ├── example-requests
│ │ └── bash.blade.php
│ │ ├── frontmatter.blade.php
│ │ ├── info.blade.php
│ │ └── route.blade.php
│ └── layout.blade.php
├── src
├── Authenticator.php
├── Authenticators
│ ├── CredentialResult.php
│ ├── SanctumAuth.php
│ ├── TokenAuth.php
│ └── UserProvider.php
├── Compass.php
├── CompassServiceProvider.php
├── Console
│ ├── BuildCommand.php
│ ├── InstallCommand.php
│ ├── PublishCommand.php
│ └── RebuildCommand.php
├── Contracts
│ ├── AuthenticatorRepository.php
│ ├── DocumenterRepository.php
│ ├── RequestRepository.php
│ └── ResponseRepository.php
├── Documenter
│ └── DocumentarianProvider.php
├── Http
│ ├── Controllers
│ │ ├── CredentialController.php
│ │ ├── HomeController.php
│ │ ├── RequestController.php
│ │ └── ResponseController.php
│ └── routes.php
├── RouteResult.php
└── Storage
│ ├── DatabaseRequestRepository.php
│ ├── DatabaseResponseRepository.php
│ ├── RouteModel.php
│ ├── factories
│ └── RouteModelFactory.php
│ └── migrations
│ └── 2019_08_08_100000_create_compass_routeables_table.php
├── tailwind.config.js
├── webpack.mix.js
└── yarn.lock
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 |
4 | # Maintain dependencies for npm.
5 | - package-ecosystem: npm
6 | directory: "/"
7 | schedule:
8 | interval: weekly
9 | labels:
10 | - dependencies
11 | versioning-strategy: increase
12 | rebase-strategy: disabled
13 |
14 | # Maintain dependencies for Composer.
15 | - package-ecosystem: composer
16 | directory: "/"
17 | schedule:
18 | interval: monthly
19 | labels:
20 | - dependencies
21 | versioning-strategy: increase-if-necessary
22 | rebase-strategy: disabled
23 |
--------------------------------------------------------------------------------
/.github/release-drafter.yml:
--------------------------------------------------------------------------------
1 | name-template: 'v$NEXT_MINOR_VERSION'
2 | tag-template: 'v$NEXT_MINOR_VERSION'
3 | exclude-labels:
4 | - 'skip-changelog'
5 | categories:
6 | - title: '🚀 Features'
7 | labels:
8 | - 'new-feature'
9 | - 'feature'
10 | - 'enhancement'
11 | - title: '🐛 Bug fixes'
12 | labels:
13 | - 'fix'
14 | - 'bugfix'
15 | - 'bug'
16 | - title: '📦 Dependencies'
17 | labels:
18 | - 'dependencies'
19 | change-template: '- $TITLE (#$NUMBER)'
20 | template: |
21 | ## Changes
22 |
23 | $CHANGES
24 |
--------------------------------------------------------------------------------
/.github/workflows/release-notes.yml:
--------------------------------------------------------------------------------
1 | name: Release notes
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | update_release_draft:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: release-drafter/release-drafter@v5
13 | env:
14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
15 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: tests
2 |
3 | on:
4 | push:
5 | pull_request:
6 | schedule:
7 | - cron: "0 0 * * *"
8 |
9 | jobs:
10 | tests:
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | fail-fast: true
15 | matrix:
16 | php: [7.3, 7.4, 8.0]
17 | laravel: [^6.0, ^7.0, ^8.0]
18 |
19 | name: P${{ matrix.php }} - L${{ matrix.laravel }}
20 |
21 | steps:
22 | - name: Checkout code
23 | uses: actions/checkout@v2
24 |
25 | - name: Cache dependencies
26 | uses: actions/cache@v2
27 | with:
28 | path: ~/.composer/cache/files
29 | key: dependencies-laravel-${{ matrix.laravel }}-php-${{ matrix.php }}-composer-${{ hashFiles('composer.json') }}
30 |
31 | - name: Setup PHP
32 | uses: shivammathur/setup-php@v2
33 | with:
34 | php-version: ${{ matrix.php }}
35 | extensions: dom, curl, libxml, mbstring, zip
36 | tools: composer:v2
37 | coverage: none
38 |
39 | - name: Install dependencies
40 | run: |
41 | composer require "illuminate/contracts=${{ matrix.laravel }}" --no-update
42 | composer update --prefer-dist --no-interaction --no-suggest
43 |
44 | - name: Execute tests
45 | run: vendor/bin/phpunit --verbose
46 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [Unreleased](https://github.com/davidhsianturi/laravel-compass/compare/v1.2.1...HEAD)
4 |
5 | ## [v1.2.1 (2021-06-13)](https://github.com/davidhsianturi/laravel-compass/compare/v1.2.0...v1.2.1)
6 |
7 | ## Changes
8 |
9 | - Allow host as a fallback domain when using variable domain ([#162](https://github.com/davidhsianturi/laravel-compass/pull/162))
10 |
11 | ## [v1.2.0 (2021-01-12)](https://github.com/davidhsianturi/laravel-compass/compare/v1.1.1...v1.2.0)
12 |
13 | ## Changes
14 |
15 | - Make the example response body editable ([#123](https://github.com/davidhsianturi/laravel-compass/pull/123))
16 |
17 | ## [v1.1.1 (2020-12-12)](https://github.com/davidhsianturi/laravel-compass/compare/v1.1.0...v1.1.1)
18 |
19 | ## Changes
20 |
21 | - PHP 8 support (#123)
22 |
23 | ## [v1.1.0 (2020-10-05)](https://github.com/davidhsianturi/laravel-compass/compare/v1.0.1...v1.1.0)
24 |
25 | ## Changes
26 |
27 | - Add Laravel 8 support ([#103](https://github.com/davidhsianturi/laravel-compass/pull/103))
28 | - Update Tailwind config file ([#100](https://github.com/davidhsianturi/laravel-compass/pull/100))
29 | - Adding release drafter action ([#84](https://github.com/davidhsianturi/laravel-compass/pull/84))
30 |
31 | ## [v1.0.1 (2020-06-13)](https://github.com/davidhsianturi/laravel-compass/compare/v1.0.0...v1.0.1)
32 |
33 | ### Fixed
34 | - Testing fixes ([@4fb6ecc ](https://github.com/davidhsianturi/laravel-compass/commit/dd16971cb407500c3b65fdd58b04168b34f4f2a5))
35 |
36 | ## [v1.0.0 (2020-04-03)](https://github.com/davidhsianturi/laravel-compass/compare/v0.6.0...v1.0.0)
37 |
38 | ### Added
39 | - Authenticator for auth requests ([#76](https://github.com/davidhsianturi/laravel-compass/pull/76))
40 | - Laravel 7 support ([#71](https://github.com/davidhsianturi/laravel-compass/pull/71))
41 |
42 | ### Changed
43 | - Revamped the UI ([#73](https://github.com/davidhsianturi/laravel-compass/pull/73))
44 | - Change `ApiDocsRepository` to `DocumenterRepository` ([#79](https://github.com/davidhsianturi/laravel-compass/pull/79))
45 |
46 | ### Removed
47 | - Dropped support for Laravel 5.8
48 |
49 | ## [v0.6.0 (2020-03-08)](https://github.com/davidhsianturi/laravel-compass/compare/v0.5.1...v0.6.0)
50 |
51 | ### Added
52 | - New request tab to manage query parameters ([#64 ](https://github.com/davidhsianturi/laravel-compass/pull/64))
53 | - Add spotlight search to quickly find the endpoints ([#66 ](https://github.com/davidhsianturi/laravel-compass/pull/66))
54 |
55 | ## [v0.5.0 (2020-02-22)](https://github.com/davidhsianturi/laravel-compass/compare/v0.4.1...v0.5.0)
56 |
57 | ### Added
58 | - Add response preview ([#57](https://github.com/davidhsianturi/laravel-compass/pull/57))
59 | - Add auth tab components ([#59](https://github.com/davidhsianturi/laravel-compass/pull/59))
60 | - Add default header ([@cadbac8](https://github.com/davidhsianturi/laravel-compass/commit/cadbac825efe8008ce212b1deefb4643b939383c))
61 |
62 | ### Changed
63 | - Tidy up the front-end ([#60](https://github.com/davidhsianturi/laravel-compass/pull/60))
64 |
65 | ## [v0.4.1 (2020-01-11)](https://github.com/davidhsianturi/laravel-compass/compare/v0.4.0...v0.4.1)
66 |
67 | ### Fixed
68 | - Fixed data-table for request header ([@a6bd01a](https://github.com/davidhsianturi/laravel-compass/commit/a6bd01ac27a31575f1130c5a3dfbcd4beb8a3d4a))
69 |
70 | ## [v0.4.0 (2019-12-13)](https://github.com/davidhsianturi/laravel-compass/compare/v0.3.0...v0.4.0)
71 |
72 | ### Added
73 | - Add support for sub-domain routing ([#83d2541](https://github.com/davidhsianturi/laravel-compass/pull/53))
74 | - New header select options component ([@8d6d5e8](https://github.com/davidhsianturi/laravel-compass/commit/7806673eb6108218524418b6c09cdc6757ba4f9e))
75 |
76 | ### Changed
77 | - Removed forbidden header name from the suggestion list ([@c358263](https://github.com/davidhsianturi/laravel-compass/commit/8d6d5e86b4a2a8e796f3c87d3a20887bdffe684f))
78 | - The selected method now saved to storage ([@33fbcce](https://github.com/davidhsianturi/laravel-compass/commit/6afadd081403e0127d49a9da7bf56ffb0c695c18))
79 | - Use a selected method to the request list ([@0a322cd](https://github.com/davidhsianturi/laravel-compass/commit/ae5f2066ca92f9681390ff93d5d7e6afe6c76449))
80 | - Dropped support for Laravel 5.7 ([@eb732c5](https://github.com/davidhsianturi/laravel-compass/commit/347a3bd7122ca44471523b80e6fa7570f9c061ba))
81 |
82 | ### Fixed
83 | - Fix request body key type dropdown in Firefox ([@7806673](https://github.com/davidhsianturi/laravel-compass/commit/b80509753431ae38037778660dfa9b9fc81d4434))
84 |
85 | ## [v0.3.0 (2019-11-25)](https://github.com/davidhsianturi/laravel-compass/compare/v0.2.0...v0.3.0)
86 |
87 | ### Added
88 | - Added more options to exclude the routes ([#40](https://github.com/davidhsianturi/laravel-compass/pull/40))
89 | - Added form urlencoded & raw request body options ([#39](https://github.com/davidhsianturi/laravel-compass/pull/39))
90 |
91 | ### Fixed
92 | - Fixed render HTTP response time and size components in `ResponseTabs.vue` ([71b7d38](https://github.com/davidhsianturi/laravel-compass/commit/71b7d3887f624e238043e22543cab21859bd4cfe))
93 |
94 | ## [v0.2.0 (2019-11-08)](https://github.com/davidhsianturi/laravel-compass/compare/v0.1.1...v0.2.0)
95 |
96 | ### Added
97 | - Added HTTP methods component ([#26](https://github.com/davidhsianturi/laravel-compass/pull/26))
98 | - Added HTTP status component ([#32](https://github.com/davidhsianturi/laravel-compass/pull/32))
99 | - Added HTTP response size and timing component ([#35](https://github.com/davidhsianturi/laravel-compass/pull/35))
100 |
101 | ## [v0.1.1 (2019-10-31)](https://github.com/davidhsianturi/laravel-compass/compare/v0.1.0...v0.1.1)
102 |
103 | ### Fixed
104 | - Fixed redirection route methods ([#12](https://github.com/davidhsianturi/laravel-compass/pull/12))
105 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | Contributions are **welcome** and will be fully **credited**.
4 |
5 | Please read and understand the contribution guide before creating an issue or pull request.
6 |
7 | ## Etiquette
8 |
9 | This project is open source, and as such, the maintainers give their free time to build and maintain the source code
10 | held within. They make the code freely available in the hope that it will be of use to other developers. It would be
11 | extremely unfair for them to suffer abuse or anger for their hard work.
12 |
13 | Please be considerate towards maintainers when raising issues or presenting pull requests. Let's show the
14 | world that developers are civilized and selfless people.
15 |
16 | It's the duty of the maintainer to ensure that all submissions to the project are of sufficient
17 | quality to benefit the project. Many developers have different skillsets, strengths, and weaknesses. Respect the maintainer's decision, and do not be upset or abusive if your submission is not used.
18 |
19 | ## Viability
20 |
21 | When requesting or submitting new features, first consider whether it might be useful to others. Open
22 | source projects are used by many developers, who may have entirely different needs to your own. Think about
23 | whether or not your feature is likely to be used by other users of the project.
24 |
25 | ## Procedure
26 |
27 | Before filing an issue:
28 |
29 | - Attempt to replicate the problem, to ensure that it wasn't a coincidental incident.
30 | - Check to make sure your feature suggestion isn't already present within the project.
31 | - Check the pull requests tab to ensure that the bug doesn't have a fix in progress.
32 | - Check the pull requests tab to ensure that the feature isn't already in progress.
33 |
34 | Before submitting a pull request:
35 |
36 | - Check the codebase to ensure that your feature doesn't already exist.
37 | - Check the pull requests to ensure that another person hasn't already submitted the feature or fix.
38 |
39 | ## Requirements
40 |
41 | If the project maintainer has any additional requirements, you will find them listed here.
42 |
43 | - **[PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md)** - The easiest way to apply the conventions is to install [PHP Code Sniffer](https://pear.php.net/package/PHP_CodeSniffer).
44 |
45 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests.
46 |
47 | - **Document any change in behaviour** - Make sure the `README.md` and any other relevant documentation are kept up-to-date.
48 |
49 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](https://semver.org/). Randomly breaking public APIs is not an option.
50 |
51 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests.
52 |
53 | - **Send coherent history** - Make sure each individual commit in your pull request is meaningful. If you had to make multiple intermediate commits while developing, please [squash them](https://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting.
54 |
55 | **Happy coding**!
56 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) David H. Sianturi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | ## Introduction
17 |
18 | Laravel Compass is an elegant REST assistant for the Laravel framework that you can use to test API calls and create API documentation. it provides automatically endpoints for GET, POST, PUT/PATCH, DELETE, various auth mechanisms, and other utility endpoints based on Laravel routes in your project.
19 |
20 | ## Installation and usage
21 |
22 | This package requires PHP 7.2 and Laravel 6.0 or higher.
23 | You'll find installation instructions and full documentation on https://davidhsianturi.com/laravel-compass.
24 |
25 | ## Changelog
26 |
27 | Please see [CHANGELOG](CHANGELOG.md) for more information what has changed recently.
28 |
29 | ## Contributing
30 |
31 | Please see [CONTRIBUTING](CONTRIBUTING.md) for details.
32 |
33 | ## License
34 |
35 | Laravel Compass is open-sourced software licensed under the [MIT license](https://opensource.org/licenses/MIT).
36 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "davidhsianturi/laravel-compass",
3 | "description": "An elegant REST assistent for the Laravel framework.",
4 | "keywords": [
5 | "laravel",
6 | "API",
7 | "testing",
8 | "documentation"
9 | ],
10 | "homepage": "https://github.com/davidhsianturi/laravel-compass",
11 | "license": "MIT",
12 | "authors": [{
13 | "name": "David H. Sianturi",
14 | "email": "davidhsianturi@gmail.com"
15 | }],
16 | "require": {
17 | "php": "^7.3|^8.0",
18 | "illuminate/console": "^6.0|^7.0|^8.0",
19 | "illuminate/routing": "^6.0|^7.0|^8.0",
20 | "illuminate/support": "^6.0|^7.0|^8.0",
21 | "mpociot/documentarian": "dev-master"
22 | },
23 | "require-dev": {
24 | "laravel/legacy-factories": "^1.0.5",
25 | "laravel/sanctum": "^2.0",
26 | "orchestra/testbench": "^4.0|^5.0|^6.0"
27 | },
28 | "autoload": {
29 | "psr-4": {
30 | "Davidhsianturi\\Compass\\": "src/"
31 | }
32 | },
33 | "autoload-dev": {
34 | "psr-4": {
35 | "Davidhsianturi\\Compass\\Tests\\": "tests/"
36 | }
37 | },
38 | "extra": {
39 | "branch-alias": {
40 | "dev-master": "1.x-dev"
41 | },
42 | "laravel": {
43 | "providers": [
44 | "Davidhsianturi\\Compass\\CompassServiceProvider"
45 | ]
46 | }
47 | },
48 | "config": {
49 | "sort-packages": true
50 | },
51 | "minimum-stability": "dev",
52 | "prefer-stable": true
53 | }
54 |
--------------------------------------------------------------------------------
/config/compass.php:
--------------------------------------------------------------------------------
1 | env('COMPASS_PATH', 'compass'),
16 |
17 | /*
18 | |--------------------------------------------------------------------------
19 | | Laravel Routes
20 | |--------------------------------------------------------------------------
21 | |
22 | | This is the routes rules that will be filtered for the requests list.
23 | | use "*" as a wildcard to match any characters. note that the following
24 | | array list "exclude" can be referenced by the route name or route URI.
25 | | "base_uri" is a string value as a comparison for grouping the routes.
26 | |
27 | */
28 |
29 | 'routes' => [
30 | 'domains' => [
31 | '*',
32 | ],
33 |
34 | 'prefixes' => [
35 | '*',
36 | ],
37 |
38 | 'exclude' => [
39 | 'compass.*',
40 | 'debugbar.*',
41 | '_ignition/*',
42 | 'telescope/*',
43 | ],
44 |
45 | 'base_uri' => '*',
46 | ],
47 |
48 | /*
49 | |--------------------------------------------------------------------------
50 | | Compass Storage Driver
51 | |--------------------------------------------------------------------------
52 | |
53 | | This configuration options determines the storage driver that will
54 | | be used to store your API calls and routes. In addition, you may set any
55 | | custom options as needed by the particular driver you choose.
56 | |
57 | */
58 |
59 | 'driver' => env('COMPASS_DRIVER', 'database'),
60 |
61 | 'storage' => [
62 | 'database' => [
63 | 'connection' => env('DB_CONNECTION', 'mysql'),
64 | ],
65 | ],
66 |
67 | /*
68 | |--------------------------------------------------------------------------
69 | | Compass Authenticator
70 | |--------------------------------------------------------------------------
71 | |
72 | | This options allow you to get all the "credentials" of users that you can
73 | | use to perform auth requests through the UI. when "enabled" set to "true"
74 | | you should adjust the authentication guard driver for your application to
75 | | support "token" or "sanctum".
76 | |
77 | */
78 |
79 | 'authenticator' => [
80 | 'enabled' => false,
81 | 'guard' => 'api',
82 | 'identifier' => 'email',
83 | ],
84 |
85 | /*
86 | |--------------------------------------------------------------------------
87 | | Compass Documenter Provider
88 | |--------------------------------------------------------------------------
89 | |
90 | | This configuration option determines the documenter provider that will be
91 | | used to create a beautiful API documentation. In addition, you may set
92 | | any custom options as needed by the particular provider you choose.
93 | |
94 | */
95 |
96 | 'documenter' => 'documentarian',
97 |
98 | 'provider' => [
99 | 'documentarian' => [
100 | 'output' => 'public/docs',
101 | 'example_requests' => [
102 | 'bash',
103 | ],
104 | ],
105 | ],
106 | ];
107 |
--------------------------------------------------------------------------------
/docs/authenticator.md:
--------------------------------------------------------------------------------
1 | # Authenticator
2 |
3 | Compass authenticator using [Laravel authentication](https://laravel.com/docs/7.x/authentication) guard driver to gather all the `credentials` of users automatically that can be used to perform auth requests through the UI.
4 |
5 | Currently, Compass ships with a simple based [Token](https://laravel.com/docs/6.x/api-authentication) guard driver and [Laravel Sanctum](https://laravel.com/docs/7.x/sanctum) guard driver; however, writing custom driver is simple and you are free to extend Compass Authenticator with your own guard implementations.
6 |
7 | You should enable Compass authenticator using the `enabled` configuration option:
8 |
9 | ```php
10 | 'authenticator' => [
11 | 'enabled' => true,
12 | 'guard' => 'sanctum',
13 | ...
14 | ],
15 | ```
16 |
17 | and you may adjust the authentication guard driver for your application to support `token` or `sanctum`.
18 |
19 | ### Custom Driver
20 |
21 | if one of the built-in Compass Authenticator guard driver doesn't fit your needs, you may write your own custom driver and register it with Compass.
22 |
23 | #### Writing The Driver
24 |
25 | Your driver should implements the `Davidhsianturi\Compass\Contracts\AuthenticatorRepository` interface class. This interface class contains only one method your custom driver must implement:
26 |
27 | ```php
28 | use Davidhsianturi\Compass\Contracts\AuthenticatorRepository;
29 |
30 | class JwtAuthenticator implements AuthenticatorRepository
31 | {
32 | /**
33 | * Return a valid credential of users.
34 | *
35 | * @return \Illuminate\Support\Collection|\Davidhsianturi\Compass\Authenticators\CredentialResult[]
36 | */
37 | public function credentials()
38 | {
39 | ...
40 | }
41 | }
42 | ```
43 |
44 | You may find it helpful to review the implementations of `credentials` method on the `Davidhsianturi\Compass\Authenticators\SanctumAuth` class. This class will provide you with a good starting point for learning how to implement the method in your own driver.
45 |
46 |
47 | #### Registering The Driver
48 |
49 | Once you have written your custom driver, you may register it with Compass using the `extend` method of the Compass authenticator. You should call the `extend` method from the `register` method of your `AppServiceProvider` or any other service provider used by your application. For example, if you have written a `JwtAuthenticator`, you may register it like so:
50 |
51 | ```php
52 | use Davidhsianturi\Compass\Authenticator;
53 |
54 | /**
55 | * Register any application services.
56 | *
57 | * @return void
58 | */
59 | public function register()
60 | {
61 | if ($this->app->environment('local')) {
62 | $authenticator = $this->app->make(Authenticator::class);
63 | $authenticator->extend('jwt', function () use ($authenticator) {
64 | return new JwtAuthenticator($authenticator->getConfig());
65 | });
66 | }
67 | }
68 | ```
69 |
70 | Once your driver has been registered, you may specify it as your default Compass authenticator guard in your `config/compass.php` configuration file:
71 |
72 | ```php
73 | 'authenticator' => [
74 | 'enabled' => true,
75 | 'guard' => 'jwt',
76 | ...
77 | ],
78 | ```
79 |
--------------------------------------------------------------------------------
/docs/configuration.md:
--------------------------------------------------------------------------------
1 | # Configuration
2 |
3 | After publishing Compass's assets, its primary configuration file will be located at `config/compass.php` below is the default content of the config file:
4 |
5 | ```php
6 | return [
7 | /*
8 | |--------------------------------------------------------------------------
9 | | Compass Path
10 | |--------------------------------------------------------------------------
11 | |
12 | | This is the URI path where Compass will be accessible from. Feel free
13 | | to change this path to anything you like.
14 | |
15 | */
16 |
17 | 'path' => env('COMPASS_PATH', 'compass'),
18 |
19 | /*
20 | |--------------------------------------------------------------------------
21 | | Laravel Routes
22 | |--------------------------------------------------------------------------
23 | |
24 | | This is the routes rules that will be filtered for the requests list. use
25 | | * as a wildcard to match any characters. note that the following array
26 | | list "exclude" must be referenced by the route name.
27 | | "base_uri" is a string value as a comparison for grouping the routes.
28 | |
29 | */
30 |
31 | 'routes' => [
32 | 'domains' => [
33 | '*',
34 | ],
35 |
36 | 'prefixes' => [
37 | '*',
38 | ],
39 |
40 | 'exclude' => [
41 | 'compass.*',
42 | 'debugbar.*',
43 | ],
44 |
45 | 'base_uri' => '*',
46 | ],
47 |
48 | /*
49 | |--------------------------------------------------------------------------
50 | | Compass Storage Driver
51 | |--------------------------------------------------------------------------
52 | |
53 | | This configuration options determines the storage driver that will
54 | | be used to store your API calls and routes. In addition, you may set any
55 | | custom options as needed by the particular driver you choose.
56 | |
57 | */
58 |
59 | 'driver' => env('COMPASS_DRIVER', 'database'),
60 |
61 | 'storage' => [
62 | 'database' => [
63 | 'connection' => env('DB_CONNECTION', 'mysql'),
64 | ],
65 | ],
66 |
67 | /*
68 | |--------------------------------------------------------------------------
69 | | Compass Authenticator
70 | |--------------------------------------------------------------------------
71 | |
72 | | This options allow you to get all the "credentials" of users that you can
73 | | use to perform auth requests through the UI. when "enabled" set to "true"
74 | | you should adjust the authentication guard driver for your application to
75 | | support "token" or "sanctum".
76 | |
77 | */
78 |
79 | 'authenticator' => [
80 | 'enabled' => false,
81 | 'guard' => 'api',
82 | 'identifier' => 'email',
83 | ],
84 |
85 | /*
86 | |--------------------------------------------------------------------------
87 | | Compass Documenter Provider
88 | |--------------------------------------------------------------------------
89 | |
90 | | This configuration option determines the documenter provider that will be
91 | | used to create a beautiful API documentation. In addition, you may set
92 | | any custom options as needed by the particular provider you choose.
93 | |
94 | */
95 |
96 | 'documenter' => 'documentarian',
97 |
98 | 'provider' => [
99 | 'documentarian' => [
100 | 'output' => 'public/docs',
101 | 'example_requests' => [
102 | 'bash',
103 | ],
104 | ],
105 | ],
106 | ];
107 | ```
108 |
109 | ## Migration Customization
110 |
111 | If you are not going to use Compass's default migrations, you should call the `Compass::ignoreMigrations` method in the `register` method of your `AppServiceProvider`.
112 | You may export the default migrations using the `php artisan vendor:publish --tag=compass-migrations` command.
113 |
--------------------------------------------------------------------------------
/docs/documenter.md:
--------------------------------------------------------------------------------
1 | # Documenter
2 |
3 | Compass documenter ships with [Documentarian](https://github.com/mpociot/documentarian) package by [Marcel Pociot](https://github.com/mpociot) that will be used to simply write beautiful API documentation. below is the example of how it looks like:
4 |
5 | 
6 |
7 | Yes, Documentarian is a PHP port of the popular [Slate](https://github.com/slatedocs/slate) API documentation tool.
8 |
9 | ### Build Documentation
10 |
11 | When building documentation, you should run the `build` command:
12 |
13 | ```bash
14 | php artisan compass:build
15 | ```
16 |
17 | Next, you should be able to visit the results at `yourproject.test/docs/index.html` in your browser.
18 |
19 | ### Rebuild Documentation
20 |
21 | You may rebuild documentation from existing markdown file, you should run the `rebuild` command:
22 |
23 | ```bash
24 | php artisan compass:rebuild
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 | # Installation
2 |
3 | Laravel Compass require **PHP 7.2 or higher** and **Laravel 6.0 or higher**.
4 | You may use Composer to install Compass into your Laravel project:
5 |
6 | ```bash
7 | composer require davidhsianturi/laravel-compass --dev
8 | ```
9 |
10 | Once Composer is done, publish its assets using the `compass:install` Artisan command. After installing Compass, you should also run the `migrate` command:
11 |
12 | ```bash
13 | php artisan compass:install
14 |
15 | php artisan migrate
16 | ```
17 |
18 | That's it! Next, you should be able to visit the Compass UI at `yourproject.test/compass` in your browser.
19 |
20 | ### Updating Compass
21 |
22 | When updating Compass, you should re-publish the assets:
23 |
24 | ```bash
25 | php artisan compass:publish
26 | ```
27 |
--------------------------------------------------------------------------------
/docs/introduction.md:
--------------------------------------------------------------------------------
1 | # Introduction
2 |
3 | Laravel Compass is an elegant REST assistant for the Laravel framework that you can use to test API calls and create API documentation. it provides automatically endpoints for GET, POST, PUT/PATCH, DELETE, various auth mechanisms, and other utility endpoints based on Laravel routes in your project.
4 |
5 | 
6 |
7 |
23 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "npm run development -- --watch",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "axios": "^0.24",
14 | "cross-env": "^7.0",
15 | "laravel-mix": "^5.0.9",
16 | "resolve-url-loader": "^4.0.0",
17 | "sass-loader": "^12.3.0",
18 | "sass": "^1.43.5",
19 | "tailwindcss": "^1.9.4",
20 | "vue": "^2.6.14",
21 | "vue-codemirror": "^4.0.6",
22 | "vue-multiselect": "^2.1.6",
23 | "vue-router": "^3.5.3",
24 | "vue-template-compiler": "^2.6.14"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidhsianturi/laravel-compass/296f33c40e960b223b5cafc0bf48afb3fd4b2bee/public/favicon.ico
--------------------------------------------------------------------------------
/public/manifest.js:
--------------------------------------------------------------------------------
1 | !function(e){function r(r){for(var n,l,f=r[0],i=r[1],a=r[2],c=0,s=[];c {
8 | config.timing = {
9 | start: performance.now(),
10 | end: null,
11 | duration: 0
12 | };
13 |
14 | return config;
15 | })
16 |
17 | instance.interceptors.response.use((response) => {
18 | response.config.timing.end = performance.now();
19 | response.config.timing.duration = response.config.timing.end - response.config.timing.start;
20 |
21 | return response;
22 | })
23 |
24 | export default instance;
25 |
--------------------------------------------------------------------------------
/resources/js/base.js:
--------------------------------------------------------------------------------
1 | import qs from 'querystring';
2 | import { REQUEST_BODY_OPTIONS } from './constants';
3 |
4 | export default {
5 | data() {
6 | return {
7 | elementId: null,
8 | waitingTime: null,
9 | };
10 | },
11 |
12 | computed: {
13 | Compass() {
14 | return Compass;
15 | }
16 | },
17 |
18 | methods: {
19 | /**
20 | * Show a success message.
21 | */
22 | alertSuccess(message, autoClose) {
23 | this.$root.alert.mode = 'toast';
24 | this.$root.alert.type = 'success';
25 | this.$root.alert.message = message;
26 | this.$root.alert.autoClose = autoClose;
27 | },
28 |
29 | /**
30 | * Show a confirmation dialog.
31 | */
32 | alertConfirm(message, success, failure) {
33 | this.$root.alert.mode = 'dialog';
34 | this.$root.alert.type = 'confirmation';
35 | this.$root.alert.message = message;
36 | this.$root.alert.autoClose = false;
37 | this.$root.alert.confirmationCancel = failure;
38 | this.$root.alert.confirmationProceed = success;
39 | },
40 |
41 | /**
42 | * Show an error message.
43 | */
44 | alertError(message) {
45 | this.$root.alert.mode = 'dialog';
46 | this.$root.alert.type = 'error';
47 | this.$root.alert.message = message;
48 | this.$root.alert.autoClose = false;
49 | },
50 |
51 | /**
52 | * The default entries for form request body.
53 | */
54 | newFormRequests() {
55 | return [
56 | {
57 | included: false,
58 | key: null,
59 | value: null,
60 | description: null,
61 | new: true,
62 | type: 'text',
63 | },
64 | ];
65 | },
66 |
67 | /**
68 | * Filter form request body key/value pair.
69 | */
70 | filterFormRequests(entries) {
71 | let arr = entries.filter(data => data.included == true);
72 |
73 | return arr.reduce((obj, item) => (obj[item.key] = item.value, obj), {});
74 | },
75 |
76 | /**
77 | * Append entries value and key to FormData object.
78 | */
79 | toFormData(entries) {
80 | let data = this.filterFormRequests(entries);
81 | let formData = new FormData();
82 |
83 | for (let pair in data) {
84 | formData.append(pair, data[pair]);
85 | }
86 |
87 | return formData;
88 | },
89 |
90 | /**
91 | * Convert entries value and key to Form URL encoded string.
92 | *
93 | * @param {Array} entries
94 | * @param {String}
95 | */
96 | toFormUrlEncoded(entries) {
97 | let data = this.filterFormRequests(entries);
98 | return qs.stringify(data);
99 | },
100 |
101 | /**
102 | * Convert entries value and key to request data based on 'Content-Type'.
103 | *
104 | * @param {Array|String} entries
105 | * @param {String} contentType
106 | * @return {FormData|String}
107 | */
108 | toRequestData(entries, contentType) {
109 | if (contentType === 'multipart/form-data') return this.toFormData(entries)
110 | if (contentType === 'application/x-www-form-urlencoded') return this.toFormUrlEncoded(entries)
111 | return entries
112 | },
113 |
114 | /**
115 | * Normalize headers with the given auth.
116 | *
117 | * @param {Array} entries
118 | * @param {Object} auth
119 | * @return {Object}
120 | */
121 | toRequestHeaders(entries, auth) {
122 | const authInStorage = localStorage.getItem(auth.key);
123 | const token = authInStorage ? JSON.parse(authInStorage).token : '';
124 | const authHeader = { Authorization: `${auth.type} ${token}` };
125 | const headers = this.filterFormRequests(entries);
126 | return auth.type === 'Bearer' ? { ...headers, ...authHeader } : headers;
127 | },
128 |
129 | /**
130 | * The mouseOver/mouseOut event target in element.
131 | */
132 | activateElmnt(val) {
133 | window.clearTimeout(this.waitingTime);
134 |
135 | this.waitingTime = window.setTimeout(() => {
136 | this.elementId = val;
137 | }, 100);
138 | },
139 |
140 | /**
141 | * Normalize header 'Content-Type' into selected request body option.
142 | *
143 | * @param {String} contentType
144 | * @return {Object}
145 | */
146 | normalizeContentType(contentType) {
147 | let bodyOption = { value: 'none', rawOption: 'text' }
148 | if (!contentType) return bodyOption
149 |
150 | let option = REQUEST_BODY_OPTIONS.find(opt => opt.value === contentType)
151 | bodyOption.value = option ? option.key : 'raw'
152 |
153 | if (bodyOption.value === 'raw') {
154 | option = REQUEST_BODY_OPTIONS.find(opt => opt.key === 'raw')
155 | const rawOption = option.options.find(opt => opt.value === contentType)
156 | bodyOption.rawOption = rawOption ? rawOption.key : 'text'
157 | }
158 |
159 | return bodyOption
160 | },
161 |
162 | /**
163 | * Encode parameter entries to query string.
164 | *
165 | * @param {Array} entries
166 | * @param {String} uri
167 | */
168 | encodeParams(entries, uri) {
169 | let query = this.toFormUrlEncoded(entries);
170 | let url = new URL(Compass.app.base_url.concat('/', uri));
171 | url.search = query;
172 | let newUri = url.pathname + url.search;
173 |
174 | return decodeURI(newUri).slice(1);
175 | },
176 |
177 | /**
178 | * Decode query string to parameter entries.
179 | *
180 | * @param {String} query
181 | */
182 | decodeParams(query) {
183 | let newEntry = this.newFormRequests();
184 | let url = new URL(Compass.app.base_url.concat('/', query));
185 | let entries = [...url.searchParams.entries()].map(item => (
186 | { ...newEntry[0], included: true, new: false, key: item[0], value: item[1] }
187 | ));
188 |
189 | return [...entries, ...newEntry];
190 | }
191 | }
192 | };
193 |
--------------------------------------------------------------------------------
/resources/js/components/Alert.vue:
--------------------------------------------------------------------------------
1 |
42 |
43 |
44 |
45 |
46 |
47 |
{{message}}
48 |
49 | Close
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | {{message}}
58 |
59 |
60 |
61 | Never mind
66 | Yes
71 | Got it!
76 |
77 |
78 |
79 |
80 |
81 |
82 |
90 |
--------------------------------------------------------------------------------
/resources/js/components/CodeEditor.vue:
--------------------------------------------------------------------------------
1 |
65 |
66 |
67 |
72 |
73 |
--------------------------------------------------------------------------------
/resources/js/components/ContentSpace.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{ description }}
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/resources/js/components/DataTable.vue:
--------------------------------------------------------------------------------
1 |
64 |
65 |
66 |
140 |
141 |
--------------------------------------------------------------------------------
/resources/js/components/Dropdown.vue:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
29 |
30 |
--------------------------------------------------------------------------------
/resources/js/components/FilesInput.vue:
--------------------------------------------------------------------------------
1 |
22 |
23 |
24 |
25 |
26 |
27 | {{fileName}}
28 |
29 |
30 |
31 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/resources/js/components/HeaderFields.vue:
--------------------------------------------------------------------------------
1 |
46 |
47 |
48 |
49 |
62 |
63 |
64 |
--------------------------------------------------------------------------------
/resources/js/components/HttpMethods.vue:
--------------------------------------------------------------------------------
1 |
15 |
16 |
17 |
18 | {{ method.name }}
19 |
20 |
21 |
22 |
27 |
--------------------------------------------------------------------------------
/resources/js/components/HttpResponseSize.vue:
--------------------------------------------------------------------------------
1 |
17 |
18 |
19 |
20 | Size:
21 | {{size}}
22 |
23 |
24 |
--------------------------------------------------------------------------------
/resources/js/components/HttpResponseTime.vue:
--------------------------------------------------------------------------------
1 |
28 |
29 |
30 |
31 | Time:
32 | {{time}}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/resources/js/components/HttpStatus.vue:
--------------------------------------------------------------------------------
1 |
41 |
42 |
43 |
44 |
45 | Status:
46 | {{status}}
47 |
48 |
49 |
50 |
51 |
{{status}}
52 |
{{description}}
53 |
54 |
55 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/resources/js/components/Omnibox.vue:
--------------------------------------------------------------------------------
1 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
59 | {{method}}
60 |
61 |
62 |
67 |
68 |
75 |
76 |
77 |
SEND
82 |
83 |
84 |
--------------------------------------------------------------------------------
/resources/js/components/RequestTabs.vue:
--------------------------------------------------------------------------------
1 |
56 |
57 |
58 |
59 |
60 |
68 |
69 |
70 |
Save request
74 |
|
75 |
76 |
77 |
78 |
Examples ({{ $attrs.examples.length }})
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
No examples added
88 |
Save responses and associated requests as Examples.
89 |
90 |
91 |
92 |
93 | {{ example.title }}
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/resources/js/components/ResponseTabs.vue:
--------------------------------------------------------------------------------
1 |
38 |
39 |
40 |
41 |
42 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | |
57 |
58 | Save response as example
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/resources/js/components/SidebarMenu.vue:
--------------------------------------------------------------------------------
1 |
53 |
54 |
55 |
56 |
57 |
58 | List
63 | Group
68 |
69 |
76 |
77 |
78 |
79 |
80 | ...
81 | No data were found
82 |
83 |
84 |
85 |
86 |
90 |
91 |
92 | {{ request.title }}
93 |
94 |
95 |
96 |
97 |
98 |
99 | {{name}}
100 |
101 |
102 |
103 |
104 |
105 | {{ request.title }}
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
126 |
--------------------------------------------------------------------------------
/resources/js/components/SiteHeader.vue:
--------------------------------------------------------------------------------
1 |
2 |
35 |
36 |
--------------------------------------------------------------------------------
/resources/js/components/Spotlight.vue:
--------------------------------------------------------------------------------
1 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
163 |
164 | No results
165 |
166 |
167 |
168 |
169 |
179 |
180 |
181 |
182 |
183 | {{ endpoint.method }}
184 |
185 |
186 | — {{ endpoint.title }}
187 |
188 |
189 |
190 |
191 | {{ endpoint.description || 'No description available' }}
192 |
193 |
194 |
195 |
200 |
201 |
202 |
203 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
--------------------------------------------------------------------------------
/resources/js/components/request/AuthTab.vue:
--------------------------------------------------------------------------------
1 |
27 |
28 |
29 |
44 |
45 |
--------------------------------------------------------------------------------
/resources/js/components/request/BodyTab.vue:
--------------------------------------------------------------------------------
1 |
108 |
109 |
110 |
111 |
137 |
138 |
139 |
143 |
146 |
150 |
151 |
152 | This request does not have a body
153 |
154 |
155 |
156 |
157 |
--------------------------------------------------------------------------------
/resources/js/components/request/DocsTab.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
14 |
15 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/resources/js/components/request/HeadersTab.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/resources/js/components/request/ParamsTab.vue:
--------------------------------------------------------------------------------
1 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/resources/js/components/request/RouteTab.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Key
13 | Value
14 |
15 |
16 |
17 |
18 |
19 | {{key}}
20 |
21 | {{key === 'methods' ? value.join(" | ") : value || '...'}}
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/resources/js/components/request/auth/Bearer.vue:
--------------------------------------------------------------------------------
1 |
125 |
126 |
127 |
174 |
175 |
--------------------------------------------------------------------------------
/resources/js/components/request/auth/None.vue:
--------------------------------------------------------------------------------
1 |
11 |
12 |
13 |
14 |
15 | No authorization was set for this request
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/resources/js/components/response/BodyTab.vue:
--------------------------------------------------------------------------------
1 |
38 |
39 |
40 |
41 |
42 | Pretty
47 | Preview
52 |
53 |
54 |
59 |
60 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/resources/js/components/response/HeadersTab.vue:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Key
13 | Value
14 |
15 |
16 |
17 |
18 |
19 | {{key}}
20 | {{value}}
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/resources/js/constants.js:
--------------------------------------------------------------------------------
1 |
2 | const REQUEST_BODY_RAW_OPTIONS = [
3 | { key: 'text', value: 'text/plain', text: 'Text', },
4 | { key: 'json', value: 'application/json', text: 'JSON', },
5 | // TODO: support body raw options for XML, YAML, and EDN
6 | ];
7 |
8 | export const REQUEST_BODY_KEYS = {
9 | FORM_DATA: 'form-data',
10 | FORM_URL_ENCODED: 'form-urlencoded',
11 | RAW: 'raw'
12 | };
13 |
14 | export const REQUEST_BODY_OPTIONS = [{
15 | key: 'none',
16 | value: null,
17 | text: 'none',
18 | options: []
19 | }, {
20 | key: REQUEST_BODY_KEYS.FORM_DATA,
21 | value: 'multipart/form-data',
22 | text: 'multipart form',
23 | options: []
24 | }, {
25 | key: REQUEST_BODY_KEYS.FORM_URL_ENCODED,
26 | value: 'application/x-www-form-urlencoded',
27 | text: 'form URL encoded',
28 | options: []
29 | }, {
30 | key: REQUEST_BODY_KEYS.RAW,
31 | value: null,
32 | text: 'raw',
33 | options: REQUEST_BODY_RAW_OPTIONS
34 | }];
35 |
36 | export const HTTP_REQUEST_METHODS = [{
37 | name: 'GET',
38 | color: 'text-green-700'
39 | },
40 | {
41 | name: 'POST',
42 | color: 'text-blue-700'
43 | },
44 | {
45 | name: 'DELETE',
46 | color: 'text-red-700'
47 | },
48 | {
49 | name: 'PUT',
50 | color: 'text-purple-700'
51 | },
52 | {
53 | name: 'PATCH',
54 | color: 'text-blue-700'
55 | }
56 | ];
57 |
58 | // forbidden header name is not included to the lists.
59 | // sourced from:
60 | // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers
61 | // https://developer.mozilla.org/en-US/docs/Glossary/Forbidden_header_name
62 | export const HTTP_HEADER_FIELDS = {
63 | KEYS: [
64 | 'Accept',
65 | 'Authorization',
66 | 'Accept-Language',
67 | 'Content-MD5',
68 | 'Cache-Control',
69 | 'Content-Transfer-Encoding',
70 | 'Cookie',
71 | 'Content-Type',
72 | 'From',
73 | 'if-Match',
74 | 'if-Modified-Since',
75 | 'If-None-Match',
76 | 'If-Range',
77 | 'If-Unmodified-Since',
78 | 'Max-Forwards',
79 | 'Pragma',
80 | 'Range',
81 | 'User-Agent',
82 | 'Warning',
83 | 'X-Do-Not-Track',
84 | 'X-Requested-With',
85 | 'x-api-key'
86 | ],
87 | VALUES: [
88 | "application/atom+xml",
89 | "application/ecmascript",
90 | "application/json",
91 | "application/javascript",
92 | "application/octet-stream",
93 | "application/ogg",
94 | "application/pdf",
95 | "application/postscript",
96 | "application/rdf+xml",
97 | "application/rss+xml",
98 | "application/font-woff",
99 | "application/x-yaml",
100 | "application/xhtml+xml",
101 | "application/xop+xml",
102 | "application/xml",
103 | "application/xop+xml",
104 | "application/zip",
105 | "application/gzip",
106 | "application/x-www-form-urlencoded",
107 | "audio/basic",
108 | "audio/L24",
109 | "audio/mp4",
110 | "audio/mpeg",
111 | "audio/ogg",
112 | "audio/vorbis",
113 | "audio/vnd.rn-realaudio",
114 | "audio/vnd.wave",
115 | "audio/webm",
116 | "image/gif",
117 | "image/jpg",
118 | "image/jpeg",
119 | "image/pjpeg",
120 | "image/png",
121 | "image/svg+xml",
122 | "image/tiff",
123 | "message/http",
124 | "message/imdn+xml",
125 | "message/partial",
126 | "message/rfc822",
127 | "multipart/mixed",
128 | "multipart/alternative",
129 | "multipart/related",
130 | "multipart/form-data",
131 | "multipart/signed",
132 | "multipart/encrypted",
133 | "text/cmd",
134 | "text/css",
135 | "text/csv",
136 | "text/html",
137 | "text/plain",
138 | "text/vcard",
139 | "text/xml"
140 | ]
141 | };
142 |
143 | // Sourced from https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
144 | export const RESPONSE_CODE_DESCRIPTIONS = {
145 | // 100s
146 | 100: 'This interim response indicates that everything so far is OK and that the client should continue with the request or ignore it if it is already finished.',
147 | 101: 'This code is sent in response to an Upgrade: request header by the client and indicates the protocol the server is switching to. It was introduced to allow migration to an incompatible protocol version, and it is not in common use.',
148 |
149 | // 200s
150 | 200: 'The request has succeeded.',
151 | 201: 'The request has succeeded and a new resource has been created as a result of it. This is typically the response sent after a PUT request.',
152 | 202: 'The request has been received but not yet acted upon. It is non-committal, meaning that there is no way in HTTP to later send an asynchronous response indicating the outcome of processing the request. It is intended for cases where another process or server handles the request, or for batch processing.',
153 | 203: 'This response code means returned meta-information set is not exact set as available from the origin server, but collected from a local or a third party copy. Except this condition, 200 OK response should be preferred instead of this response.',
154 | 204: 'There is no content to send for this request, but the headers may be useful. The user-agent may update its cached headers for this resource with the new ones.',
155 | 205: 'This response code is sent after accomplishing request to tell user agent reset document view which sent this request.',
156 | 206: 'This response code is used because of range header sent by the client to separate download into multiple streams.',
157 | 207: 'A Multi-Status response conveys information about multiple resources in situations where multiple status codes might be appropriate.',
158 | 208: 'Used inside a DAV: propstat response element to avoid enumerating the internal members of multiple bindings to the same collection repeatedly.',
159 | 226: 'The server has fulfilled a GET request for the resource, and the response is a representation of the result of one or more instance-manipulations applied to the current instance.',
160 |
161 | // 300s
162 | 300: 'The request has more than one possible responses. User-agent or user should choose one of them. There is no standardized way to choose one of the responses.',
163 | 301: 'This response code means that URI of requested resource has been changed. Probably, new URI would be given in the response.',
164 | 302: 'This response code means that URI of requested resource has been changed temporarily. New changes in the URI might be made in the future. Therefore, this same URI should be used by the client in future requests.',
165 | 303: 'Server sent this response to directing client to get requested resource to another URI with an GET request.',
166 | 304: 'This is used for caching purposes. It is telling to client that response has not been modified. So, client can continue to use same cached version of response.',
167 | 305: 'This means requested response must be accessed by a proxy. This response code is not largely supported because of security reasons.',
168 | 306: 'This response code is no longer used and is just reserved currently. It was used in a previous version of the HTTP 1.1 specification.',
169 | 307: 'Server sent this response to directing client to get requested resource to another URI with same method that used prior request. This has the same semantic than the 302 Found HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.',
170 | 308: 'This means that the resource is now permanently located at another URI, specified by the Location: HTTP Response header. This has the same semantics as the 301 Moved Permanently HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.',
171 |
172 | // 400s
173 | 400: 'This response means that the server could not understand the request due to invalid syntax.',
174 | 401: 'Authentication is needed to get the requested response. This is similar to 403, but is different in that authentication is possible.',
175 | 402: 'This response code is reserved for future use. Initial aim for creating this code was using it for digital payment systems, but it is not used currently.',
176 | 403: 'Client does not have access rights to the content, so the server is rejecting to give proper response.',
177 | 404: 'Server cannot find requested resource. This response code is probably the most famous one due to how frequently it occurs on the web.',
178 | 405: 'The request method is known by the server but has been disabled and cannot be used.',
179 | 406: "This response is sent when the web server, after performing server-driven content negotiation, doesn't find any content following the criteria given by the user agent.",
180 | 407: 'This is similar to 401 but authentication is needed to be done by a proxy.',
181 | 408: 'This response is sent on an idle connection by some servers, even without any previous request by the client. It means that the server would like to shut down this unused connection. This response is used much more since some browsers, like Chrome or IE9, use HTTP pre-connection mechanisms to speed up surfing (see bug 881804, which tracks the future implementation of such a mechanism in Firefox). Also, note that some servers merely shut down the connection without sending this message.',
182 | 409: 'This response is sent when a request conflicts with the current state of the server.',
183 | 410: 'This response is sent when the requested content has been deleted from the server.',
184 | 411: 'Server rejected the request because the Content-Length header field is not defined and the server requires it.',
185 | 412: 'The client has indicated preconditions in its headers which the server does not meet.',
186 | 413: 'Request entity is larger than limits defined by the server; the server might close the connection or return a Retry-After header field.',
187 | 414: 'The URI requested by the client is longer than the server is willing to interpret.',
188 | 415: 'The media format of the requested data is not supported by the server, so the server is rejecting the request.',
189 | 416: "The range specified by the Range header field in the request can't be fulfilled; it's possible that the range is outside the size of the target URI's data.",
190 | 417: "This response code means the expectation indicated by the Expect request header field can't be met by the server.",
191 | 418: 'Any attempt to brew coffee with a teapot should result in the error code "418 I\'m a teapot". The resulting entity body MAY be short and stout.',
192 | 421: 'The request was directed at a server that is not able to produce a response. This can be sent by a server that is not configured to produce responses for the combination of scheme and authority that are included in the request URI.',
193 | 422: 'The request was well-formed but was unable to be followed due to semantic errors.',
194 | 423: 'The resource that is being accessed is locked.',
195 | 424: 'The request failed due to failure of a previous request.',
196 | 426: 'The server refuses to perform the request using the current protocol but might be willing to do so after the client upgrades to a different protocol. The server MUST send an Upgrade header field in a 426 response to indicate the required protocol(s) (Section 6.7 of [RFC7230]).',
197 | 428: "The origin server requires the request to be conditional. Intended to prevent \"the 'lost update' problem, where a client GETs a resource's state, modifies it, and PUTs it back to the server, when meanwhile a third party has modified the state on the server, leading to a conflict.\"",
198 | 429: 'The user has sent too many requests in a given amount of time ("rate limiting").',
199 | 431: 'The server is unwilling to process the request because its header fields are too large. The request MAY be resubmitted after reducing the size of the request header fields.',
200 | 451: 'The user requests an illegal resource, such as a web page censored by a government.',
201 |
202 | // 500s
203 | 500: "The server has encountered a situation it doesn't know how to handle.",
204 | 501: 'The request method is not supported by the server and cannot be handled. The only methods that servers are required to support (and therefore that must not return this code) are GET and HEAD.',
205 | 502: 'This error response means that the server, while working as a gateway to get a response needed to handle the request, got an invalid response.',
206 | 503: 'The server is not ready to handle the request. Common causes are a server that is down for maintenance or that is overloaded. Note that together with this response, a user-friendly page explaining the problem should be sent. This responses should be used for temporary conditions and the Retry-After: HTTP header should, if possible, contain the estimated time before the recovery of the service. The webmaster must also take care about the caching-related headers that are sent along with this response, as these temporary condition responses should usually not be cached.',
207 | 504: 'This error response is given when the server is acting as a gateway and cannot get a response in time.',
208 | 505: 'The HTTP version used in the request is not supported by the server.',
209 | 506: 'The server has an internal configuration error: transparent content negotiation for the request results in a circular reference.',
210 | 507: 'The server has an internal configuration error: the chosen variant resource is configured to engage in transparent content negotiation itself, and is therefore not a proper end point in the negotiation process.',
211 | 508: 'The server detected an infinite loop while processing the request.',
212 | 510: 'Further extensions to the request are required for the server to fulfill it.',
213 | 511: 'The 511 status code indicates that the client needs to authenticate to gain network access.',
214 | };
215 |
--------------------------------------------------------------------------------
/resources/js/pages/404.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/resources/js/pages/cortex.vue:
--------------------------------------------------------------------------------
1 |
173 |
174 |
175 |
176 |
184 |
185 |
188 |
189 |
190 |
191 |
192 |
193 |
196 |
197 |
198 |
--------------------------------------------------------------------------------
/resources/js/pages/example.vue:
--------------------------------------------------------------------------------
1 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Title
68 |
73 |
74 |
75 |
76 | {{ exampleData.content.request.title }} →
77 |
78 |
79 |
80 | Update
81 |
82 |
83 | Delete
84 |
85 |
86 |
87 |
88 |
89 |
90 | Example request
91 |
92 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | Example response
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
--------------------------------------------------------------------------------
/resources/js/pages/welcome.vue:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
11 |
12 | Press
13 |
14 | ctrl
15 |
16 | +
17 |
18 | space
19 |
20 | to find an endpoint
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/resources/js/routes.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/',
4 | component: require('./pages/welcome').default,
5 | },
6 | {
7 | path: '/:id',
8 | name: 'cortex',
9 | component: require('./pages/cortex').default,
10 | },
11 | {
12 | path: '/eg/:id',
13 | name: 'example',
14 | component: require('./pages/example').default,
15 | },
16 | {
17 | path: '*',
18 | name: 'catch-all',
19 | component: require('./pages/404').default,
20 | },
21 | ];
22 |
--------------------------------------------------------------------------------
/resources/sass/_base.scss:
--------------------------------------------------------------------------------
1 | kbd {
2 | @apply font-mono pointer-events-none inline-block align-middle border border-gray-200 rounded;
3 | padding: 3px 5px;
4 | }
5 |
--------------------------------------------------------------------------------
/resources/sass/_codemirror.scss:
--------------------------------------------------------------------------------
1 | @import 'node_modules/codemirror/lib/codemirror';
2 | @import 'node_modules/codemirror/addon/fold/foldgutter';
3 |
4 | .CodeMirror {
5 | @apply h-screen;
6 | }
7 |
8 | .CodeMirror-gutters {
9 | @apply border-r-0 bg-white;
10 | }
11 |
12 | .CodeMirror-activeline-background {
13 | background: #fff7f1;
14 | }
15 |
16 | .CodeMirror-foldmarker {
17 | color: #f75858;
18 | text-shadow: #f37f79 1px 1px 2px, #fbe8e7 -1px -1px 2px, #f37f79 1px -1px 2px, #fbe8e7 -1px 1px 2px;
19 | font-family: arial;
20 | line-height: 0.3;
21 | cursor: pointer;
22 | }
23 |
--------------------------------------------------------------------------------
/resources/sass/_modal.scss:
--------------------------------------------------------------------------------
1 | .modal-mask {
2 | background: rgba(128,82,82,.1);
3 | transition: opacity .3s ease;
4 | }
5 |
6 | .modal-container {
7 | transition: all .3s ease;
8 | }
9 |
10 | .modal-contents {
11 | @apply flex flex-col rounded-lg shadow;
12 | }
13 |
14 | .modal-items {
15 | @apply w-full overflow-x-auto overflow-y-scroll z-10 bg-white border-t;
16 | max-height: 400px;
17 | }
18 |
19 | .modal-enter {
20 | opacity: 0;
21 | }
22 |
23 | .modal-leave-active {
24 | opacity: 0;
25 | }
26 |
27 | .modal-enter .modal-container,
28 | .modal-leave-active .modal-container {
29 | transform: scale(1.1);
30 | }
31 |
32 | .fade-enter-active {
33 | transition: opacity .5s;
34 | }
35 |
36 | .fade-leave-active {
37 | transition: opacity 0s;
38 | }
39 |
40 | .fade-enter,
41 | .fade-leave-to {
42 | opacity: 0;
43 | }
44 |
--------------------------------------------------------------------------------
/resources/sass/_multiselect.scss:
--------------------------------------------------------------------------------
1 | @import 'node_modules/vue-multiselect/dist/vue-multiselect.min';
2 |
3 | .multiselect {
4 | min-height: 35px;
5 | }
6 |
7 | .multiselect__spinner:after,
8 | .multiselect__spinner:before {
9 | border-top-color: #e79334;
10 | }
11 |
12 | .multiselect,
13 | .multiselect__input,
14 | .multiselect__single {
15 | @apply text-xs;
16 | }
17 |
18 | .multiselect__single {
19 | @apply pl-0 mb-0 truncate;
20 | }
21 |
22 | .multiselect__tags {
23 | @apply border-0 bg-transparent text-xs;
24 | min-height: 35px;
25 | }
26 |
27 | .multiselect__tag {
28 | @apply bg-primary-light;
29 | }
30 |
31 | .multiselect__select {
32 | height: 33px;
33 | }
34 |
35 | .multiselect__placeholder {
36 | @apply text-gray-500 text-xs mb-0 pt-0;
37 | }
38 |
39 | .multiselect__content-wrapper {
40 | @apply border border-gray-200;
41 | }
42 |
43 | // .multiselect__option {
44 | // @apply capitalize;
45 | // }
46 |
47 | .multiselect__option--highlight {
48 | @apply bg-primary-light text-primary;
49 | }
50 |
51 | .multiselect__option--highlight:after {
52 | @apply bg-primary-light text-primary;
53 | }
54 |
55 | .multiselect__option--selected.multiselect__option--highlight {
56 | @apply bg-primary
57 | }
58 |
59 | .multiselect__option--selected.multiselect__option--highlight:after {
60 | @apply bg-primary
61 | }
62 |
63 | .hide-arrow-icon .multiselect__select {
64 | @apply hidden;
65 | }
66 |
--------------------------------------------------------------------------------
/resources/sass/_spotlight.scss:
--------------------------------------------------------------------------------
1 | .spotlight-search-container {
2 | height: calc(90vh - 80px);
3 | width: 500px;
4 | max-width: calc(100vw - 32px);
5 | }
6 |
7 | .spotlight-search-bar {
8 | @apply flex-grow-0 flex-shrink-0;
9 | caret-color: #f75858;
10 | }
11 |
12 | .spotlight-search-input {
13 | @apply outline-none block w-full appearance-none leading-normal text-sm text-gray-600;
14 | }
15 |
16 | .spotlight-search-contents {
17 | @apply flex flex-col rounded-lg shadow;
18 | }
19 |
20 | .spotlight-search-results {
21 | @apply w-full overflow-x-auto overflow-y-scroll z-10 bg-white border-t;
22 | max-height: 400px;
23 | }
24 |
25 | .spotlight-search-results-empty {
26 | @apply w-full leading-5 text-sm p-4 bg-white font-medium text-gray-600;
27 | }
28 |
29 | .spotlight-search-footer {
30 | @apply flex flex-grow-0 flex-shrink-0 justify-between items-center h-8 px-4 rounded-b-lg bg-secondary text-gray-500 text-xs;
31 | }
32 |
--------------------------------------------------------------------------------
/resources/sass/app.scss:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 |
4 | @import "base";
5 | @import "modal";
6 | @import "spotlight";
7 | @import "codemirror";
8 | @import "multiselect";
9 |
10 | @tailwind utilities;
11 |
--------------------------------------------------------------------------------
/resources/views/documenter/documentarian/layout.blade.php:
--------------------------------------------------------------------------------
1 | ---
2 | {!! $frontmatter !!}
3 | ---
4 |
5 | {!! $infoText !!}
6 |
7 | {!! $prependMd !!}
8 | @foreach($parsedRoutes as $groupName => $routes)
9 | #{!! $groupName !!}
10 |
11 |
12 | @foreach($routes as $route)
13 | {!! $route->content['output'] !!}
14 | @endforeach
15 | @endforeach{!! $appendMd !!}
16 |
--------------------------------------------------------------------------------
/resources/views/documenter/documentarian/partials/example-requests/bash.blade.php:
--------------------------------------------------------------------------------
1 | ```bash
2 | curl -X {{$example->content->request->content->selectedMethod}} {{$example->content->request->content->selectedMethod == 'GET' ? '-G ' : ''}}"{{ rtrim($baseUrl, '/')}}/{{ ltrim($example->content->request->content->url, '/') }}" \
3 | @if(is_object($example->content->request->content) || count($example->content->request->content->headers))
4 | @foreach($example->content->request->content->headers as $header)
5 | @if ($header->included)
6 | -H "{{$header->key}}: {{$header->value}}" \
7 | @endif
8 | @endforeach
9 | @endif
10 |
11 | ```
--------------------------------------------------------------------------------
/resources/views/documenter/documentarian/partials/frontmatter.blade.php:
--------------------------------------------------------------------------------
1 | title: API Reference
2 |
3 | language_tabs:
4 | @foreach($settings['languages'] as $language)
5 | - {{ $language }}
6 | @endforeach
7 |
8 | includes:
9 |
10 | search: true
11 |
12 | toc_footers:
13 | - Documentation Powered by Documentarian
--------------------------------------------------------------------------------
/resources/views/documenter/documentarian/partials/info.blade.php:
--------------------------------------------------------------------------------
1 | # Info
2 |
3 | Welcome to the generated API reference.
--------------------------------------------------------------------------------
/resources/views/documenter/documentarian/partials/route.blade.php:
--------------------------------------------------------------------------------
1 |
2 | ## {{$route->title}}
3 | @if ($route->description)
4 |
5 | {{$route->description}}
6 | @endif
7 |
8 | @if (count($route->examples))
9 | @foreach ($route->examples as $example)
10 | > {{$example->title}} :
11 |
12 | @foreach($settings['languages'] as $language)
13 | @include("compass::documenter.documentarian.partials.example-requests.$language")
14 |
15 | @endforeach
16 |
17 |
18 | > Example response ({{$example->content->response->status}}) :
19 |
20 | ```json
21 | {!! json_encode($example->content->response->data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) !!}
22 | ```
23 | @endforeach
24 | @else
25 |
26 | > No example provided.
27 | @endif
28 |
29 | ### HTTP Request
30 | @foreach ($route->info['methods'] as $method)
31 | `{{$method}} {{$route->info['uri']}}`
32 | @endforeach
33 |
34 | @if (array_key_exists('params', $route->content))
35 | @foreach ($route->content['params'] as $param)
36 | @if ($param['included'])
37 | #### Query Parameters
38 |
39 | Key | Description
40 | --------- | -----------
41 | {{$param['key']}} | {{$param['description']}}
42 | @endif
43 | @endforeach
44 | @endif
45 |
46 |
47 |
--------------------------------------------------------------------------------
/resources/views/layout.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Compass - {{ config('app.name') }}
10 |
11 |
12 |
13 |
14 |
15 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | @if (! $assetsAreCurrent)
32 |
33 |
34 |
39 |
40 |
The published Compass assets are not up-to-date with the installed version.
41 |
To update, run: php artisan compass:publish
42 |
43 |
44 |
45 | @endif
46 |
47 |
48 |
49 |
50 |
51 |
52 | {{-- Global Laravel Compass Object --}}
53 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/src/Authenticator.php:
--------------------------------------------------------------------------------
1 | getConfig();
19 |
20 | return new TokenAuth($config);
21 | }
22 |
23 | /**
24 | * Create a Sanctum auth instance.
25 | *
26 | * @return \Davidhsianturi\Compass\Authenticators\SanctumAuth
27 | */
28 | public function createSanctumDriver()
29 | {
30 | $config = $this->getConfig();
31 |
32 | return new SanctumAuth($config);
33 | }
34 |
35 | /**
36 | * Get the default Authenticator driver name.
37 | *
38 | * @return string
39 | */
40 | public function getDefaultDriver()
41 | {
42 | $guard = $this->getConfig()->only('guard')->first();
43 |
44 | return $guard['driver'];
45 | }
46 |
47 | /**
48 | * Get the default Authenticator configuration.
49 | *
50 | * @return \Illuminate\Support\Collection
51 | */
52 | public function getConfig()
53 | {
54 | $config = $this->container['config']['compass.authenticator'];
55 | $guard = $this->container['config']['auth.guards.'.$config['guard']];
56 |
57 | return collect([
58 | 'guard' => $guard,
59 | 'provider' => $this->container['config']['auth.providers.'.$guard['provider']],
60 | 'identifier' => $config['identifier'],
61 | ]);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Authenticators/CredentialResult.php:
--------------------------------------------------------------------------------
1 | name = $name;
32 | $this->token = $token;
33 | }
34 |
35 | /**
36 | * Get the array representation of the credential.
37 | *
38 | * @return array
39 | */
40 | public function jsonSerialize()
41 | {
42 | return [
43 | 'name' => $this->name,
44 | 'token' => $this->token,
45 | ];
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Authenticators/SanctumAuth.php:
--------------------------------------------------------------------------------
1 | getUsers()
20 | ->reject(function ($user) {
21 | return $user->tokens()->get()->isEmpty();
22 | })->map(function ($user) {
23 | $plainTextToken = Str::random(80);
24 | // override the latest access token.
25 | $token = tap($user->tokens()->latest()->first(), function ($token) use ($plainTextToken) {
26 | $token->update(['token' => hash('sha256', $plainTextToken)]);
27 | });
28 |
29 | return new CredentialResult($token->name, $plainTextToken);
30 | })->values();
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/Authenticators/TokenAuth.php:
--------------------------------------------------------------------------------
1 | identifier;
20 | $hash = $this->config['guard']['hash'] ?? false;
21 |
22 | if ($hash) {
23 | return $this->overrideTokens($identifier);
24 | }
25 |
26 | return $this->retrieveTokens($identifier);
27 | }
28 |
29 | /**
30 | * Get all the API tokens from storage.
31 | *
32 | * @param string $identifier
33 | * @return \Illuminate\Support\Collection|\Davidhsianturi\Compass\Authenticators\CredentialResult[]
34 | */
35 | protected function retrieveTokens(string $identifier)
36 | {
37 | $storageKey = $this->getStorageKey();
38 |
39 | return $this->getUsers()
40 | ->map(function ($user) use ($storageKey, $identifier) {
41 | $identifier = $user->$identifier ?? 'anonymous';
42 |
43 | return new CredentialResult($identifier, $user->$storageKey);
44 | })->values();
45 | }
46 |
47 | /**
48 | * Override all the API tokens from storage.
49 | *
50 | * @param string $identifier
51 | * @return \Illuminate\Support\Collection|\Davidhsianturi\Compass\Authenticators\CredentialResult[]
52 | */
53 | protected function overrideTokens(string $identifier)
54 | {
55 | $token = $this->getStorageKey();
56 |
57 | return $this->getUsers()
58 | ->map(function ($user) use ($token, $identifier) {
59 | $identifier = $user->$identifier ?? 'anonymous';
60 |
61 | $user->forceFill([
62 | $token => hash('sha256', $plainTextToken = Str::random(80)),
63 | ])->save();
64 |
65 | return new CredentialResult($identifier, $plainTextToken);
66 | })->values();
67 | }
68 |
69 | /**
70 | * Get the storage key of the API token.
71 | *
72 | * @return string
73 | */
74 | protected function getStorageKey()
75 | {
76 | return $this->config['guard']['storage_key'] ?? 'api_token';
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/Authenticators/UserProvider.php:
--------------------------------------------------------------------------------
1 | config = $config;
30 | $this->identifier = $config['identifier'];
31 | }
32 |
33 | /**
34 | * Get a user provider instance.
35 | *
36 | * @return \Illuminate\Database\Eloquent\Collection
37 | */
38 | protected function getUsers()
39 | {
40 | return $this->createModel()->newQuery()->get();
41 | }
42 |
43 | /**
44 | * Create a new instance of the model.
45 | *
46 | * @return \Illuminate\Database\Eloquent\Model
47 | */
48 | protected function createModel()
49 | {
50 | $class = '\\'.ltrim($this->config['provider']['model'], '\\');
51 |
52 | return new $class;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/Compass.php:
--------------------------------------------------------------------------------
1 | map(function ($route) {
30 | return static::getRouteInformation($route);
31 | })->filter();
32 | }
33 |
34 | /**
35 | * Get the route information for a given route.
36 | *
37 | * @param \Illuminate\Routing\Route $route
38 | * @return array
39 | */
40 | protected static function getRouteInformation(Route $route)
41 | {
42 | $methods = array_values(array_diff($route->methods(), ['HEAD']));
43 | $baseUri = config('compass.routes.base_uri');
44 |
45 | return static::filterRoute([
46 | 'uuid' => null,
47 | 'title' => Str::after($route->uri(), $baseUri),
48 | 'description' => null,
49 | 'content' => [],
50 | 'example' => false,
51 | 'route_hash' => md5($route->uri().':'.implode($methods)),
52 | 'domain' => $route->domain(),
53 | 'methods' => $methods,
54 | 'uri' => $route->uri(),
55 | 'name' => $route->getName(),
56 | 'action' => ltrim($route->getActionName(), '\\'),
57 | 'created_at' => null,
58 | 'updated_at' => null,
59 | ]);
60 | }
61 |
62 | /**
63 | * Filter the route by the config rules.
64 | *
65 | * @param array $route
66 | * @return array|null
67 | */
68 | protected static function filterRoute(array $route)
69 | {
70 | $routeRules = config('compass.routes');
71 |
72 | if ((Str::is($routeRules['exclude'], $route['name'])) ||
73 | Str::is($routeRules['exclude'], $route['uri']) ||
74 | ! Str::is($routeRules['domains'], $route['domain']) ||
75 | ! Str::is($routeRules['prefixes'], $route['uri'])) {
76 | return;
77 | }
78 |
79 | return $route;
80 | }
81 |
82 | /**
83 | * Sync route from storage with app routes.
84 | *
85 | * @param array $routeInStorage
86 | * @return \Illuminate\Support\Collection
87 | */
88 | public static function syncRoute(array $routeInStorage)
89 | {
90 | return static::getAppRoutes()->map(function ($appRoute) use ($routeInStorage) {
91 | $route = collect($routeInStorage)
92 | ->where('route_hash', $appRoute['route_hash'])
93 | ->collapse()
94 | ->toArray();
95 |
96 | return array_merge($appRoute, $route);
97 | })->values();
98 | }
99 |
100 | /**
101 | * Group the routes with a base URI.
102 | *
103 | * @param \Illuminate\Support\Collection|\Davidhsianturi\Compass\RouteResult[] $routes
104 | * @return \Illuminate\Support\Collection|\Davidhsianturi\Compass\RouteResult[]
105 | */
106 | public static function groupingRoutes(Collection $routes)
107 | {
108 | $baseUri = config('compass.routes.base_uri');
109 |
110 | return $routes->groupBy(function ($route) use ($baseUri) {
111 | if (is_object($route)) {
112 | return strtok(Str::after($route->info['uri'], $baseUri), '/');
113 | }
114 |
115 | return strtok(Str::after($route['uri'], $baseUri), '/');
116 | });
117 | }
118 |
119 | /**
120 | * Get the default JavaScript variables for Compass.
121 | *
122 | * @return array
123 | */
124 | public static function scriptVariables()
125 | {
126 | return [
127 | 'path' => config('compass.path'),
128 | 'app' => [
129 | 'name' => config('app.name'),
130 | 'base_url' => config('app.url'),
131 | 'env' => App::environment(),
132 | ],
133 | 'authenticator' => config('compass.authenticator.enabled'),
134 | ];
135 | }
136 |
137 | /**
138 | * Configure Compass to not register its migrations.
139 | *
140 | * @return static
141 | */
142 | public static function ignoreMigrations()
143 | {
144 | static::$runsMigrations = false;
145 |
146 | return new static;
147 | }
148 |
149 | /**
150 | * Check if assets are up-to-date.
151 | *
152 | * src: https://github.com/laravel/telescope/pull/729
153 | *
154 | * @return bool
155 | *
156 | * @throws \RuntimeException
157 | */
158 | public static function assetsAreCurrent()
159 | {
160 | $publishedPath = public_path('vendor/compass/mix-manifest.json');
161 |
162 | if (! File::exists($publishedPath)) {
163 | throw new RuntimeException('The Compass assets are not published. Please run: php artisan compass:publish');
164 | }
165 |
166 | return File::get($publishedPath) === File::get(__DIR__.'/../public/mix-manifest.json');
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/src/CompassServiceProvider.php:
--------------------------------------------------------------------------------
1 | registerRoutes();
25 | $this->registerMigrations();
26 | $this->registerPublishing();
27 |
28 | $this->loadViewsFrom(
29 | __DIR__.'/../resources/views', 'compass'
30 | );
31 | }
32 |
33 | /**
34 | * Register the package routes.
35 | *
36 | * @return void
37 | */
38 | private function registerRoutes()
39 | {
40 | Route::namespace('Davidhsianturi\Compass\Http\Controllers')
41 | ->as('compass.')
42 | ->prefix(config('compass.path'))
43 | ->group(function () {
44 | $this->loadRoutesFrom(__DIR__.'/Http/routes.php');
45 | });
46 | }
47 |
48 | /**
49 | * Register the package's migrations.
50 | *
51 | * @return void
52 | */
53 | private function registerMigrations()
54 | {
55 | if ($this->app->runningInConsole() && $this->shouldMigrate()) {
56 | $this->loadMigrationsFrom(__DIR__.'/Storage/migrations');
57 | }
58 | }
59 |
60 | /**
61 | * Determine if we should register the migrations.
62 | *
63 | * @return void
64 | */
65 | protected function shouldMigrate()
66 | {
67 | return Compass::$runsMigrations && config('compass.driver') === 'database';
68 | }
69 |
70 | /**
71 | * Register the package's publishable resources.
72 | *
73 | * @return void
74 | */
75 | private function registerPublishing()
76 | {
77 | if ($this->app->runningInConsole()) {
78 | $this->publishes([
79 | __DIR__.'/Storage/migrations' => database_path('migrations'),
80 | ], 'compass-migrations');
81 |
82 | $this->publishes([
83 | __DIR__.'/../public' => public_path('vendor/compass'),
84 | ], 'compass-assets');
85 |
86 | $this->publishes([
87 | __DIR__.'/../resources/views/documenter' => resource_path('views/vendor/compass/documenter'),
88 | ], 'compass-documenter');
89 |
90 | $this->publishes([
91 | __DIR__.'/../config/compass.php' => config_path('compass.php'),
92 | ], 'compass-config');
93 | }
94 | }
95 |
96 | /**
97 | * Register any package services.
98 | *
99 | * @return void
100 | */
101 | public function register()
102 | {
103 | $this->mergeConfigFrom(
104 | __DIR__.'/../config/compass.php', 'compass'
105 | );
106 |
107 | $this->registerStorageDriver();
108 | $this->registerAuthenticator();
109 | $this->registerDocumenterProvider();
110 |
111 | $this->commands([
112 | Console\InstallCommand::class,
113 | Console\PublishCommand::class,
114 | Console\BuildCommand::class,
115 | Console\RebuildCommand::class,
116 | ]);
117 | }
118 |
119 | /**
120 | * Register the package storage driver.
121 | *
122 | * @return void
123 | */
124 | protected function registerStorageDriver()
125 | {
126 | $driver = config('compass.driver');
127 |
128 | if (method_exists($this, $method = 'register'.ucfirst($driver).'Driver')) {
129 | return $this->$method();
130 | }
131 | }
132 |
133 | /**
134 | * Register the package database storage driver.
135 | *
136 | * @return void
137 | */
138 | protected function registerDatabaseDriver()
139 | {
140 | $this->app->singleton(
141 | RequestRepository::class, DatabaseRequestRepository::class
142 | );
143 |
144 | $this->app->singleton(
145 | ResponseRepository::class, DatabaseResponseRepository::class
146 | );
147 |
148 | $this->app->when(DatabaseRequestRepository::class)
149 | ->needs('$connection')
150 | ->give(config('compass.storage.database.connection'));
151 | }
152 |
153 | /**
154 | * Register the package's documenter provider.
155 | *
156 | * @return void
157 | */
158 | protected function registerDocumenterProvider()
159 | {
160 | $documenter = config('compass.documenter');
161 |
162 | if (method_exists($this, $method = 'register'.ucfirst($documenter).'Provider')) {
163 | return $this->$method();
164 | }
165 | }
166 |
167 | /**
168 | * Register the package documentarian provider.
169 | *
170 | * @return void
171 | */
172 | protected function registerDocumentarianProvider()
173 | {
174 | $this->app->singleton(
175 | DocumenterRepository::class, DocumentarianProvider::class
176 | );
177 | }
178 |
179 | /**
180 | * Register the package authenticator.
181 | *
182 | * @return void
183 | */
184 | protected function registerAuthenticator()
185 | {
186 | if (! app()->environment('self-testing') && ! config('compass.authenticator.enabled')) {
187 | return;
188 | }
189 |
190 | $this->app->singleton(Authenticator::class, function ($app) {
191 | return new Authenticator($app);
192 | });
193 |
194 | $this->app->singleton('compass.authenticator', function ($app) {
195 | return $app[Authenticator::class]->driver();
196 | });
197 |
198 | $this->app->alias('compass.authenticator', AuthenticatorRepository::class);
199 | }
200 | }
201 |
--------------------------------------------------------------------------------
/src/Console/BuildCommand.php:
--------------------------------------------------------------------------------
1 | build();
32 |
33 | $this->info('Building complete.');
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Console/InstallCommand.php:
--------------------------------------------------------------------------------
1 | comment('Publishing Compass Assets...');
31 | $this->callSilent('vendor:publish', ['--tag' => 'compass-assets']);
32 |
33 | $this->comment('Publishing Compass Configuration...');
34 | $this->callSilent('vendor:publish', ['--tag' => 'compass-config']);
35 |
36 | $this->info('Compass scaffolding installed successfully.');
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/Console/PublishCommand.php:
--------------------------------------------------------------------------------
1 | call('vendor:publish', [
31 | '--tag' => 'compass-config',
32 | '--force' => $this->option('force'),
33 | ]);
34 |
35 | $this->call('vendor:publish', [
36 | '--tag' => 'compass-assets',
37 | '--force' => true,
38 | ]);
39 |
40 | $this->call('vendor:publish', [
41 | '--tag' => 'compass-documenter',
42 | '--force' => $this->option('force'),
43 | ]);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Console/RebuildCommand.php:
--------------------------------------------------------------------------------
1 | rebuild() === false) {
32 | $this->error('There is no existing markdown files to rebuild, Try to run compass:build first.');
33 |
34 | return;
35 | }
36 |
37 | $this->info('Rebuilding complete.');
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/Contracts/AuthenticatorRepository.php:
--------------------------------------------------------------------------------
1 | generator = $generator;
32 | $this->resources = $resources;
33 | }
34 |
35 | /**
36 | * Build documentation resources from storage.
37 | *
38 | * @return false|null
39 | */
40 | public function build()
41 | {
42 | $contents = $this->viewContents();
43 | $settings = $this->markdownConfig();
44 |
45 | // Build routes output.
46 | $routesOutput = Compass::getAppRoutes()->map(function ($route) use ($settings, $contents) {
47 | $route = $this->resources->find($route['route_hash']);
48 |
49 | collect($route->examples)->each(function ($example) {
50 | $example->content = json_decode($example->content);
51 | });
52 |
53 | $route->content['output'] = (string) view($contents['route'])
54 | ->with('route', $route)
55 | ->with('settings', $settings)
56 | ->with('baseUrl', config('app.url'))
57 | ->render();
58 |
59 | return $route;
60 | })->values();
61 |
62 | $parsedRoutesOutput = Compass::groupingRoutes($routesOutput);
63 | $infoText = view($contents['info']);
64 | $frontmatter = view($contents['frontmatter'])->with('settings', $settings);
65 |
66 | $markdownFiles = $this->markdownFiles();
67 | $prependFileContents = file_exists($markdownFiles['prepend']) ? file_get_contents($markdownFiles['prepend'])."\n" : '';
68 | $appendFileContents = file_exists($markdownFiles['append']) ? "\n".file_get_contents($markdownFiles['append']) : '';
69 |
70 | // @todo should we check if the documentation was modified and skip the modified parts of the routes ?
71 |
72 | $this->writeContents($appendFileContents, $prependFileContents, $parsedRoutesOutput, $frontmatter, $infoText);
73 |
74 | return $this->generator->generate($markdownFiles['path']);
75 | }
76 |
77 | /**
78 | * Rebuild documentation resources from existing markdown files.
79 | *
80 | * @return false|null
81 | */
82 | public function rebuild()
83 | {
84 | $markdownFiles = $this->markdownFiles();
85 |
86 | if (! is_dir($markdownFiles['path'])) {
87 | // throw an exception?
88 | return false;
89 | }
90 |
91 | return $this->generator->generate($markdownFiles['path']);
92 | }
93 |
94 | /**
95 | * Write contents to markdown files.
96 | *
97 | * @param string $appendFileContents
98 | * @param string $prependFileContents
99 | * @param \Illuminate\Support\Collection $parsedRoutesOutput
100 | * @param \Illuminate\View\View|\Illuminate\Contracts\View\Factory|string $frontmatter
101 | * @param \Illuminate\View\View|\Illuminate\Contracts\View\Factory $infoText
102 | * @return int|false
103 | */
104 | protected function writeContents($appendFileContents, $prependFileContents, $parsedRoutesOutput, $frontmatter, $infoText)
105 | {
106 | $markdownFiles = $this->markdownFiles();
107 |
108 | if (! is_dir($markdownFiles['path'])) {
109 | $this->generator->create($markdownFiles['path']);
110 | }
111 |
112 | $contents = view('compass::documenter.documentarian.layout')
113 | ->with('appendMd', $appendFileContents)
114 | ->with('prependMd', $prependFileContents)
115 | ->with('parsedRoutes', $parsedRoutesOutput)
116 | ->with('frontmatter', $frontmatter)
117 | ->with('infoText', $infoText)
118 | ->with('outputPath', $markdownFiles['path']);
119 |
120 | file_put_contents($markdownFiles['index'], $contents);
121 | }
122 |
123 | /**
124 | * The markdown files collection.
125 | *
126 | * @return array
127 | */
128 | protected function markdownFiles()
129 | {
130 | $outputPath = config('compass.provider.documentarian.output');
131 |
132 | return [
133 | 'path' => $outputPath,
134 | 'index' => $outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'index.md',
135 | 'append' => $outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'append.md',
136 | 'compare' => $outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'compare.md',
137 | 'prepend' => $outputPath.DIRECTORY_SEPARATOR.'source'.DIRECTORY_SEPARATOR.'prepend.md',
138 | ];
139 | }
140 |
141 | /**
142 | * The markdown files configuration.
143 | *
144 | * @return array
145 | */
146 | protected function markdownConfig()
147 | {
148 | return [
149 | 'languages' => config('compass.provider.documentarian.example_requests'),
150 | ];
151 | }
152 |
153 | /**
154 | * Get the evaluated contents for view.
155 | *
156 | * @return array
157 | */
158 | protected function viewContents()
159 | {
160 | $documentarian = 'compass::documenter.documentarian.';
161 |
162 | return [
163 | 'layout' => $documentarian.'layout',
164 | 'info' => $documentarian.'partials.info',
165 | 'route' => $documentarian.'partials.route',
166 | 'frontmatter' => $documentarian.'partials.frontmatter',
167 | ];
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/Http/Controllers/CredentialController.php:
--------------------------------------------------------------------------------
1 | json([
18 | 'data' => $auth->credentials(),
19 | ]);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Http/Controllers/HomeController.php:
--------------------------------------------------------------------------------
1 | Compass::scriptVariables(),
18 | 'assetsAreCurrent' => Compass::assetsAreCurrent(),
19 | ]);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Http/Controllers/RequestController.php:
--------------------------------------------------------------------------------
1 | request = $request;
25 | }
26 |
27 | /**
28 | * List the route requests.
29 | *
30 | * @return \Illuminate\Http\JsonResponse
31 | */
32 | public function index()
33 | {
34 | $requests = $this->request->get();
35 |
36 | return response()->json([
37 | 'data' => [
38 | 'list' => $requests,
39 | 'group' => Compass::groupingRoutes($requests),
40 | ],
41 | ]);
42 | }
43 |
44 | /**
45 | * Get route request with the given ID.
46 | *
47 | * @param string $id
48 | * @return \Illuminate\Http\JsonResponse
49 | */
50 | public function show($id)
51 | {
52 | return response()->json($this->request->find($id));
53 | }
54 |
55 | /**
56 | * Store the route request.
57 | *
58 | * @return \Illuminate\Http\JsonResponse
59 | */
60 | public function store()
61 | {
62 | return response()->json($this->request->save($this->validateRequest()));
63 | }
64 |
65 | /**
66 | * Validate the request attributes.
67 | *
68 | * @return array
69 | */
70 | protected function validateRequest()
71 | {
72 | return request()->validate([
73 | 'id' => 'string|required',
74 | 'storageId' => 'nullable',
75 | 'title' => 'string|required',
76 | 'description' => 'nullable',
77 | 'content' => 'nullable|array',
78 | ]);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Http/Controllers/ResponseController.php:
--------------------------------------------------------------------------------
1 | examples = $examples;
24 | }
25 |
26 | /**
27 | * Store the response as example.
28 | *
29 | * @return \Illuminate\Http\JsonResponse
30 | */
31 | public function store()
32 | {
33 | return response()->json(
34 | $this->examples->save($this->validateRequest())
35 | );
36 | }
37 |
38 | /**
39 | * Show the example of a response by given UUID.
40 | *
41 | * @param mixed $uuid
42 | * @return \Illuminate\Http\JsonResponse
43 | */
44 | public function show($uuid)
45 | {
46 | return response()->json(
47 | $this->examples->find($uuid)
48 | );
49 | }
50 |
51 | /**
52 | * Delete the example of a response by given UUID.
53 | *
54 | * @param mixed $uuid
55 | * @return \Illuminate\Http\JsonResponse
56 | */
57 | public function destroy($uuid)
58 | {
59 | return response()->json(
60 | $this->examples->delete($uuid),
61 | 204
62 | );
63 | }
64 |
65 | /**
66 | * Validate the request attributes.
67 | *
68 | * @return array
69 | */
70 | protected function validateRequest()
71 | {
72 | return request()->validate([
73 | 'uuid' => 'sometimes|nullable',
74 | 'route_hash' => 'required|string',
75 | 'title' => 'required|string',
76 | 'description' => 'nullable',
77 | 'content' => 'required|array',
78 | ]);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Http/routes.php:
--------------------------------------------------------------------------------
1 | name('request');
5 | Route::post('/request', 'RequestController@store')->name('request.store');
6 | Route::get('/request/{id}', 'RequestController@show')->name('request.show');
7 |
8 | // Route response.
9 | Route::post('/response', 'ResponseController@store')->name('response.store');
10 | Route::get('/response/{uuid}', 'ResponseController@show')->name('response.show');
11 | Route::delete('/response/{uuid}', 'ResponseController@destroy')->name('response.destroy');
12 |
13 | Route::get('/credentials', 'CredentialController')->name('credentials');
14 |
15 | // Catch-all Route.
16 | Route::get('/{view?}', 'HomeController')->where('view', '(.*)')->name('home');
17 |
--------------------------------------------------------------------------------
/src/RouteResult.php:
--------------------------------------------------------------------------------
1 | id = $id;
96 | $this->storageId = $storageId;
97 | $this->title = $title;
98 | $this->description = $description;
99 | $this->content = $content;
100 | $this->info = $info;
101 | $this->isExample = $isExample;
102 | $this->examples = $examples;
103 | $this->createdAt = $createdAt;
104 | $this->updatedAt = $updatedAt;
105 | }
106 |
107 | /**
108 | * Get the array representation of the route.
109 | *
110 | * @return array
111 | */
112 | public function jsonSerialize()
113 | {
114 | return [
115 | 'id' => $this->id,
116 | 'storageId' => $this->storageId,
117 | 'title' => $this->title,
118 | 'description' => $this->description,
119 | 'content' => $this->content,
120 | 'info' => $this->info,
121 | 'isExample' => $this->isExample,
122 | 'examples' => $this->examples,
123 | 'createdAt' => $this->createdAt->toDateTimeString(),
124 | 'updatedAt' => $this->updatedAt->toDateTimeString(),
125 | ];
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Storage/DatabaseRequestRepository.php:
--------------------------------------------------------------------------------
1 | connection = $connection;
30 | }
31 |
32 | /**
33 | * Return all the route requests.
34 | *
35 | * @return \Illuminate\Support\Collection|Davidhsianturi\Compass\RouteResult[]
36 | */
37 | public function get()
38 | {
39 | return Compass::syncRoute($this->routesInStorage())->map(function ($route) {
40 | return $this->routeResult($route, []);
41 | });
42 | }
43 |
44 | /**
45 | * find the route with the given ID.
46 | *
47 | * @param string $id
48 | * @return \Davidhsianturi\Compass\RouteResult
49 | */
50 | public function find(string $id): RouteResult
51 | {
52 | $route = Compass::syncRoute($this->routesInStorage())
53 | ->whereStrict('route_hash', $id)
54 | ->first();
55 |
56 | $responses = $this->table('compass_routeables')
57 | ->whereExample(true)
58 | ->where('route_hash', $id)
59 | ->get()
60 | ->toArray();
61 |
62 | return $this->routeResult($route, $responses);
63 | }
64 |
65 | /**
66 | * Update or create the given route.
67 | *
68 | * @param array $route
69 | * @return \Davidhsianturi\Compass\RouteResult
70 | */
71 | public function save(array $route)
72 | {
73 | $storageId = $route['storageId'] ?? (string) Str::uuid();
74 |
75 | $store = RouteModel::on($this->connection)->updateOrCreate(
76 | ['route_hash' => $route['id'], 'uuid' => $storageId],
77 | [
78 | 'title' => $route['title'],
79 | 'description' => $route['description'],
80 | 'content' => $route['content'],
81 | ]
82 | );
83 |
84 | $syncedRoute = Compass::syncRoute($store->get()->toArray())
85 | ->whereStrict('uuid', $store->uuid)
86 | ->first();
87 |
88 | return $this->routeResult($syncedRoute, []);
89 | }
90 |
91 | /**
92 | * The route result.
93 | *
94 | * @param array|null $route
95 | * @param array|null $responses
96 | * @return \Davidhsianturi\Compass\RouteResult
97 | */
98 | protected function routeResult(?array $route, ?array $responses)
99 | {
100 | if (blank($route)) {
101 | return;
102 | }
103 |
104 | return new RouteResult(
105 | $route['route_hash'],
106 | $route['uuid'],
107 | $route['title'],
108 | $route['description'],
109 | $route['content'],
110 | [
111 | 'domain' => $this->hasVariableFragment($route['domain'])
112 | ? request()->getHost()
113 | : $route['domain'],
114 | 'methods' => $route['methods'],
115 | 'uri' => $route['uri'],
116 | 'name' => $route['name'],
117 | 'action' => $route['action'],
118 | ],
119 | Carbon::parse($route['created_at']),
120 | Carbon::parse($route['updated_at']),
121 | $route['example'],
122 | $responses
123 | );
124 | }
125 |
126 | /**
127 | * Get routes from storage.
128 | *
129 | * @return array
130 | */
131 | protected function routesInStorage()
132 | {
133 | return RouteModel::on($this->connection)
134 | ->whereExample(false)
135 | ->get()
136 | ->toArray();
137 | }
138 |
139 | /**
140 | * Get a query builder instance for the given table.
141 | *
142 | * @param string $table
143 | * @return \Illuminate\Database\Query\Builder
144 | */
145 | protected function table($table)
146 | {
147 | return DB::connection($this->connection)->table($table);
148 | }
149 |
150 | /**
151 | * Determines if a given domain has any dynamic fragment.
152 | *
153 | * @param string|null $domain
154 | * @return bool
155 | */
156 | private function hasVariableFragment(?string $domain)
157 | {
158 | return collect(explode('.', $domain))->filter(function ($fragment) {
159 | return Str::startsWith($fragment, '{') && Str::endsWith($fragment, '}');
160 | })->count() > 0;
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/Storage/DatabaseResponseRepository.php:
--------------------------------------------------------------------------------
1 | $responseId, 'route_hash' => $response['route_hash']],
22 | [
23 | 'title' => $response['title'],
24 | 'description' => $response['description'],
25 | 'content' => $response['content'],
26 | 'example' => true,
27 | ]
28 | );
29 | }
30 |
31 | /**
32 | * Find the route response by given UUID.
33 | *
34 | * @param mixed $uuid
35 | * @return \Illuminate\Database\Eloquent\ModelNotFoundException|\Davidhsianturi\Compass\Storage\RouteModel
36 | */
37 | public function find($uuid)
38 | {
39 | return RouteModel::whereExample(true)->whereUuid($uuid)->firstOrFail();
40 | }
41 |
42 | /**
43 | * Delete the route response by given UUID.
44 | *
45 | * @param mixed $uuid
46 | * @return bool
47 | */
48 | public function delete($uuid)
49 | {
50 | return RouteModel::whereExample(true)->whereUuid($uuid)->delete();
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/Storage/RouteModel.php:
--------------------------------------------------------------------------------
1 | 'array',
51 | 'example' => 'boolean',
52 | ];
53 |
54 | /**
55 | * Get the current connection name for the model.
56 | *
57 | * @return string
58 | */
59 | public function getConnectionName()
60 | {
61 | return config('compass.storage.database.connection');
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Storage/factories/RouteModelFactory.php:
--------------------------------------------------------------------------------
1 | define(RouteModel::class, function (Faker\Generator $faker) {
7 | return [
8 | 'uuid' => $faker->uuid,
9 | 'route_hash' => $faker->md5,
10 | 'title' => $faker->word,
11 | 'description' => $faker->paragraph,
12 | 'content' => [$faker->word => $faker->word],
13 | ];
14 | });
15 |
--------------------------------------------------------------------------------
/src/Storage/migrations/2019_08_08_100000_create_compass_routeables_table.php:
--------------------------------------------------------------------------------
1 | schema = Schema::connection($this->getConnection());
24 | }
25 |
26 | /**
27 | * Run the migrations.
28 | *
29 | * @return void
30 | */
31 | public function up()
32 | {
33 | $this->schema->create('compass_routeables', function (Blueprint $table) {
34 | $table->uuid('uuid')->unique();
35 | $table->string('route_hash');
36 | $table->string('title');
37 | $table->text('description')->nullable();
38 | $table->longText('content')->nullable();
39 | $table->boolean('example')->default(false);
40 | $table->timestamps();
41 |
42 | $table->index(['route_hash', 'title']);
43 | });
44 | }
45 |
46 | /**
47 | * Reverse the migrations.
48 | *
49 | * @return void
50 | */
51 | public function down()
52 | {
53 | $this->schema->dropIfExists('compass_routeables');
54 | }
55 |
56 | /**
57 | * Get the migration connection name.
58 | *
59 | * @return string|null
60 | */
61 | public function getConnection()
62 | {
63 | return config('compass.storage.database.connection');
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | purge: {
3 | content: [
4 | './resources/**/*.blade.php',
5 | './resources/**/*.vue',
6 | './resources/**/*.js'
7 | ],
8 | options: {
9 | whitelistPatterns: [/-active$/, /-enter$/, /-leave-to$/, /show$/]
10 | }
11 | },
12 | theme: {
13 | boxShadow: {
14 | default: '0 15px 35px 0 rgba(94, 59, 59, .1)'
15 | },
16 | extend: {
17 | colors: {
18 | primary: {
19 | default: '#f75858',
20 | light: '#fbe8e7',
21 | dark: '#f64949'
22 | },
23 | secondary: {
24 | default: '#f5f5fa'
25 | },
26 | light: '#fff7f1'
27 | },
28 | spacing: {
29 | '72': '18rem',
30 | '80': '20rem'
31 | },
32 | padding: {
33 | '5/6': '83.3333333%'
34 | }
35 | }
36 | },
37 | variants: {
38 | tableLayout: ['responsive', 'hover', 'focus'],
39 | borderColor: ['responsive', 'hover', 'focus', 'focus-within']
40 | },
41 | plugins: [],
42 | future: {
43 | removeDeprecatedGapUtilities: true,
44 | purgeLayersByDefault: true
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require('laravel-mix');
2 | const tailwindcss = require('tailwindcss');
3 |
4 | mix
5 | .options({
6 | terser: {
7 | terserOptions: {
8 | compress: {
9 | drop_console: true,
10 | }
11 | }
12 | },
13 | processCssUrls: false,
14 | postCss: [tailwindcss('./tailwind.config.js')]
15 | })
16 | .setPublicPath('public')
17 | .js('resources/js/app.js', 'public')
18 | .sass('resources/sass/app.scss', 'public')
19 | .extract()
20 | .version()
21 | .copy('public', '../compasstest/public/vendor/compass');
22 |
--------------------------------------------------------------------------------