├── .gitignore ├── .styleci.yml ├── changelog.md ├── composer.json ├── config └── apidocs.php ├── contributing.md ├── frontend ├── .browserslistrc ├── .gitignore ├── .postcssrc.js ├── README.md ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ ├── logo.png │ │ └── styles │ │ │ └── tailwind.postcss │ ├── components │ │ ├── BaseFooter.vue │ │ ├── CodeEditor.vue │ │ ├── Endpoint.vue │ │ ├── Group.vue │ │ ├── Headers.vue │ │ ├── Parameters.vue │ │ ├── QueryParameters.vue │ │ └── Response.vue │ ├── main.js │ ├── plugins │ │ ├── axios.js │ │ ├── index.js │ │ ├── jsoneditor.js │ │ ├── v-clipboard.js │ │ └── vue-sweetalert.js │ ├── router.js │ ├── store.js │ ├── utils │ │ ├── localStorage.js │ │ ├── toast.js │ │ └── url.js │ └── views │ │ └── Home.vue ├── tailwind.config.js └── vue.config.js ├── license.md ├── phpunit.xml ├── public ├── favicon.ico └── vendor │ └── apidocs │ ├── css │ ├── app.14d989bf.css │ ├── app.ae6c8111.css │ ├── chunk-vendors.94e8c3a9.css │ ├── chunk-vendors.de1f8e60.css │ └── chunk-vendors.e7214169.css │ ├── favicon.ico │ ├── img │ ├── jsoneditor-icons.d961fdfa.svg │ └── plus.bdc6b5b1.svg │ └── js │ ├── app.01e3ddc4.js │ ├── app.01e3ddc4.js.map │ ├── app.46ab911e.js │ ├── app.46ab911e.js.map │ ├── app.605d079a.js │ ├── app.605d079a.js.map │ ├── app.66b33547.js │ ├── app.66b33547.js.map │ ├── app.7209d6c2.js │ ├── app.7209d6c2.js.map │ ├── app.7545017b.js │ ├── app.7545017b.js.map │ ├── app.98396e15.js │ ├── app.98396e15.js.map │ ├── app.afb4df00.js │ ├── app.afb4df00.js.map │ ├── app.d38bd805.js │ ├── app.d38bd805.js.map │ ├── app.ef6f679d.js │ ├── app.ef6f679d.js.map │ ├── app.ffa9dd26.js │ ├── app.ffa9dd26.js.map │ ├── chunk-vendors.12e6a342.js │ ├── chunk-vendors.12e6a342.js.map │ ├── chunk-vendors.3b50cf7e.js │ ├── chunk-vendors.3b50cf7e.js.map │ ├── chunk-vendors.7bc6d880.js │ └── chunk-vendors.7bc6d880.js.map ├── readme.md ├── resources └── views │ └── index.blade.php ├── rewriteRules.php ├── routes ├── api.php └── web.php ├── rules.php ├── src ├── ApiDocs.php ├── Commands │ ├── ApiDocsInstall.php │ ├── ApiRouteListCommand.php │ └── PublishCommand.php ├── Contracts │ ├── ApiEndpointRepository.php │ └── ApiGroupRepository.php ├── Drivers │ ├── Database │ │ ├── Models │ │ │ ├── ApiGroup.php │ │ │ └── Endpoint.php │ │ ├── Repository │ │ │ ├── DatabaseApiEndpointRepository.php │ │ │ └── DatabaseApiGroupRepository.php │ │ ├── Traits │ │ │ └── RegisterDatabaseDriver.php │ │ └── migrations │ │ │ └── 2018_11_06_041130_create_endpoints_table.php │ └── File │ │ ├── FileStore.php │ │ ├── Repository │ │ ├── FileApiEndpointRepository.php │ │ └── FileApiGroupRepository.php │ │ └── Traits │ │ └── RegisterFileDriver.php ├── Facades │ └── ApiDocs.php ├── Http │ ├── Controllers │ │ ├── ConfigController.php │ │ ├── EndpointController.php │ │ └── SpaController.php │ ├── Middleware │ │ └── Authenticate.php │ ├── Requests │ │ ├── EndpointRequest.php │ │ └── Request.php │ └── Resources │ │ ├── EndpointResource.php │ │ └── GroupResource.php ├── Nova │ ├── Endpoint.php │ └── Group.php ├── Providers │ ├── ApiDocsApplicationServiceProvider.php │ ├── ApiDocsRouteServiceProvider.php │ └── ApiDocsServiceProvider.php └── Traits │ ├── ApiResource.php │ ├── AskForApiInfo.php │ ├── AuthorizesRequests.php │ └── Cachable.php └── stubs └── ApiDocsServiceProvider.stub /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /frontend/.env -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: laravel -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `ApiDocs` will be documented in this file. 4 | 5 | ## Version 1.0 6 | 7 | ### Added 8 | - Everything 9 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "harlekoy/laravel-apidocs", 3 | "description": "Replacement for Swagger. It is built for Laravel", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "Harlequin Doyon", 8 | "email": "harlequin.doyon@gmail.com", 9 | "homepage": "https://www.linkedin.com/in/harlequin-doyon-6a0318100/" 10 | } 11 | ], 12 | "homepage": "https://github.com/harlekoy/apidocs", 13 | "keywords": ["Laravel", "API Docs"], 14 | "require": { 15 | "illuminate/support": "~6", 16 | "laravel/framework": "5.7.*|5.8.*|^6.0", 17 | "webpatser/laravel-uuid": "^3.0" 18 | }, 19 | "require-dev": { 20 | "phpunit/phpunit": "~7.0", 21 | "mockery/mockery": "^1.1", 22 | "orchestra/testbench": "~3.0", 23 | "sempro/phpunit-pretty-print": "^1.0" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Harlekoy\\ApiDocs\\": "src/" 28 | } 29 | }, 30 | "autoload-dev": { 31 | "psr-4": { 32 | "Harlekoy\\ApiDocs\\Tests\\": "tests" 33 | } 34 | }, 35 | "extra": { 36 | "laravel": { 37 | "providers": [ 38 | "Harlekoy\\ApiDocs\\Providers\\ApiDocsServiceProvider" 39 | ], 40 | "aliases": { 41 | "ApiDocs": "Harlekoy\\ApiDocs\\Facades\\ApiDocs" 42 | } 43 | } 44 | }, 45 | "minimum-stability": "dev", 46 | "prefer-stable": true 47 | } 48 | -------------------------------------------------------------------------------- /config/apidocs.php: -------------------------------------------------------------------------------- 1 | '/apidocs', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | API Display Name 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This value is the name of your application's API. This value is used when the 26 | | framework needs to display the name of the application within the UI 27 | | or in other locations. Of course, you're free to change the value. 28 | | 29 | */ 30 | 31 | 'name' => 'API Endpoints', 32 | 33 | 'version' => 'v1.0.0', 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Docs API Base URL 38 | |-------------------------------------------------------------------------- 39 | | 40 | | This URL is where users will be the base URL for the API to request from. 41 | | You are free to change this URL to any location you wish depending on 42 | | the needs of your API. 43 | | 44 | */ 45 | 46 | 'url' => env('API_URL'), 47 | 48 | 'api_url' => env('API_URL').'/api/v1', 49 | 50 | 'group_open' => true, 51 | 52 | /* 53 | |-------------------------------------------------------------------------- 54 | | API Docs Route Middleware 55 | |-------------------------------------------------------------------------- 56 | | 57 | | These middleware will be assigned to every API Docs route, giving you the 58 | | chance to add your own middleware to this stack or override any of 59 | | the existing middleware. Or, you can just stick with this stack. 60 | | 61 | */ 62 | 63 | 'middleware' => [ 64 | 'web', 65 | Authenticate::class, 66 | ], 67 | 68 | /* 69 | |-------------------------------------------------------------------------- 70 | | API Docs Storage Driver 71 | |-------------------------------------------------------------------------- 72 | | 73 | | This configuration options determines the storage driver that will 74 | | be used to store API docs data. In addition, you may set any 75 | | custom options as needed by the particular driver you choose. 76 | | 77 | | Supported: "file", "database" 78 | | 79 | */ 80 | 81 | 'driver' => env('APIDOCS_DRIVER', 'file'), 82 | 83 | 'storage' => [ 84 | 'database' => [ 85 | 'connection' => env('DB_CONNECTION', 'mysql'), 86 | ], 87 | 'file' => [ 88 | 'path' => storage_path('app/apidocs'), 89 | ], 90 | ], 91 | ]; 92 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Contributions are welcome and will be fully credited. 4 | 5 | Contributions are accepted via Pull Requests on [Github](https://github.com/harlekoy/apidocs). 6 | 7 | # Things you could do 8 | If you want to contribute but do not know where to start, this list provides some starting points. 9 | - Add license text 10 | - Remove rewriteRules.php 11 | - Set up TravisCI, StyleCI, ScrutinizerCI 12 | - Write a comprehensive ReadMe 13 | 14 | ## Pull Requests 15 | 16 | - **Add tests!** - Your patch won't be accepted if it doesn't have tests. 17 | 18 | - **Document any change in behaviour** - Make sure the `readme.md` and any other relevant documentation are kept up-to-date. 19 | 20 | - **Consider our release cycle** - We try to follow [SemVer v2.0.0](http://semver.org/). Randomly breaking public APIs is not an option. 21 | 22 | - **One pull request per feature** - If you want to do more than one thing, send multiple pull requests. 23 | 24 | - **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](http://www.git-scm.com/book/en/v2/Git-Tools-Rewriting-History#Changing-Multiple-Commit-Messages) before submitting. 25 | 26 | 27 | **Happy coding**! 28 | -------------------------------------------------------------------------------- /frontend/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not ie <= 8 4 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | # local env files 6 | .env.local 7 | .env.*.local 8 | 9 | # Log files 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | 14 | # Editor directories and files 15 | .idea 16 | .vscode 17 | *.suo 18 | *.ntvs* 19 | *.njsproj 20 | *.sln 21 | *.sw* 22 | -------------------------------------------------------------------------------- /frontend/.postcssrc.js: -------------------------------------------------------------------------------- 1 | const isHotReloaded = process.argv.includes('serve') 2 | 3 | class TailwindVueExtractor { 4 | static extract (content) { 5 | const contentWithoutStyleBlocks = content.replace(//gi, '') 6 | return contentWithoutStyleBlocks.match(/[A-Za-z0-9-_:/]+/g) || [] 7 | } 8 | } 9 | 10 | const extensionsUsingCSS = [ 'vue', 'html' ] 11 | const extensionsOfCSS = [ 'css', 'less', 'pcss', 'postcss', 'sass', 'scss', 'styl' ] 12 | 13 | module.exports = { 14 | plugins: [ 15 | require('postcss-preset-env')({ stage: 2 }), 16 | require('tailwindcss')('./tailwind.config.js'), 17 | // !isHotReloaded && require('@fullhuman/postcss-purgecss')({ 18 | // content: [ `./@(public|src)/**/*.@(${extensionsUsingCSS.join('|')})` ], 19 | // css: [ `./src/**/*.@(${extensionsOfCSS.join('|')})` ], 20 | // whitelistPatterns: [/red$/, /green$/, /orange$/, /blue$/, /red-light$/, /^jsoneditor/, /^ace/], 21 | // extractors: [ 22 | // { 23 | // extractor: TailwindVueExtractor, 24 | // extensions: extensionsUsingCSS, 25 | // }, 26 | // ], 27 | // }), 28 | require('autoprefixer')(), 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # frontend 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | '@vue/app' 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "apidocs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build --no-clean" 8 | }, 9 | "dependencies": { 10 | "axios": "^0.18.0", 11 | "lodash": "^4.17.11", 12 | "v-jsoneditor": "^1.1.2", 13 | "vue": "^2.5.17", 14 | "vue-clipboard2": "^0.2.1", 15 | "vue-router": "^3.0.1", 16 | "vue-sweetalert2": "^1.5.7", 17 | "vuex": "^3.0.1" 18 | }, 19 | "devDependencies": { 20 | "@fullhuman/postcss-purgecss": "^1.1.0", 21 | "@ky-is/vue-cli-plugin-tailwind": "^1.5.0", 22 | "@vue/cli-plugin-babel": "^3.0.1", 23 | "@vue/cli-service": "^3.0.1", 24 | "node-sass": "^4.9.0", 25 | "postcss-preset-env": "^6.3.0", 26 | "sass-loader": "^7.0.1", 27 | "tailwindcss": "^0.7.0", 28 | "vue-template-compiler": "^2.5.17" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlekoy/laravel-apidocs/89ef02d9c969d6e4eff8475a6803efc7fcd13f15/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | "> 8 | 9 | 10 | API Docs 11 | 12 | 13 | 16 |
17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 49 | -------------------------------------------------------------------------------- /frontend/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlekoy/laravel-apidocs/89ef02d9c969d6e4eff8475a6803efc7fcd13f15/frontend/src/assets/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/styles/tailwind.postcss: -------------------------------------------------------------------------------- 1 | /** 2 | * This injects Tailwind's base styles, which is a combination of 3 | * Normalize.css and some additional base styles. 4 | * 5 | * You can see the styles here: 6 | * https://github.com/tailwindcss/tailwindcss/blob/master/css/preflight.css 7 | */ 8 | @import '~tailwindcss/preflight.css'; 9 | 10 | /** 11 | * This injects any component classes registered by plugins. 12 | */ 13 | @import '~tailwindcss/components.css'; 14 | 15 | /** 16 | * Here you would add any of your custom component classes; stuff that you'd 17 | * want loaded *before* the utilities so that the utilities could still 18 | * override them. 19 | * 20 | * Example: 21 | * @import 'components/buttons'; 22 | */ 23 | 24 | /** 25 | * This injects all of Tailwind's utility classes, generated based on your 26 | * config file. 27 | */ 28 | @import '~tailwindcss/utilities.css'; 29 | 30 | /** 31 | * Here you would add any custom utilities you need that don't come out of the 32 | * box with Tailwind. 33 | * 34 | * Example: 35 | * @import 'utilities/background-patterns'; 36 | */ 37 | -------------------------------------------------------------------------------- /frontend/src/components/BaseFooter.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 26 | -------------------------------------------------------------------------------- /frontend/src/components/CodeEditor.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 50 | 51 | 68 | -------------------------------------------------------------------------------- /frontend/src/components/Endpoint.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 201 | 202 | 211 | -------------------------------------------------------------------------------- /frontend/src/components/Group.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 67 | -------------------------------------------------------------------------------- /frontend/src/components/Headers.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 65 | -------------------------------------------------------------------------------- /frontend/src/components/Parameters.vue: -------------------------------------------------------------------------------- 1 | 57 | 58 | 162 | -------------------------------------------------------------------------------- /frontend/src/components/QueryParameters.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 78 | -------------------------------------------------------------------------------- /frontend/src/components/Response.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 157 | 158 | 173 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import App from './App.vue' 3 | import router from './router' 4 | import store from './store' 5 | import './plugins' 6 | 7 | Vue.config.productionTip = false 8 | 9 | new Vue({ 10 | router, 11 | store, 12 | render: h => h(App) 13 | }).$mount('#app') 14 | -------------------------------------------------------------------------------- /frontend/src/plugins/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import qs from 'qs' 3 | import store from '@/store' 4 | import { apiUrl } from '@/utils/url' 5 | import { fail } from '@/utils/toast' 6 | import { isEmpty } from 'lodash' 7 | 8 | axios.defaults.baseURL = apiUrl() 9 | axios.defaults.paramsSerializer = params => qs.stringify(params, {arrayFormat: 'repeat'}) 10 | 11 | axios.interceptors.request.use(request => { 12 | if (!isEmpty(store.getters['headers'])) { 13 | request.headers = Object.assign(request.headers, store.getters['headers']) 14 | } 15 | 16 | return request 17 | }) 18 | 19 | axios.interceptors.response.use(response => response, async (error) => { 20 | const { status, data: { message }} = error.response 21 | 22 | switch (status) { 23 | case 500: 24 | fail({ 25 | title: 'Error!', 26 | text: message, 27 | timer: 5000, 28 | position: 'bottom-right', 29 | width: 500, 30 | }) 31 | break 32 | } 33 | 34 | return Promise.reject(error) 35 | }) 36 | -------------------------------------------------------------------------------- /frontend/src/plugins/index.js: -------------------------------------------------------------------------------- 1 | import './axios' 2 | import './jsoneditor' 3 | import './v-clipboard' 4 | import './vue-sweetalert' 5 | -------------------------------------------------------------------------------- /frontend/src/plugins/jsoneditor.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VJsoneditor from 'v-jsoneditor/src/index' 3 | 4 | Vue.use(VJsoneditor) 5 | -------------------------------------------------------------------------------- /frontend/src/plugins/v-clipboard.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueClipboard from 'vue-clipboard2' 3 | 4 | Vue.use(VueClipboard) 5 | -------------------------------------------------------------------------------- /frontend/src/plugins/vue-sweetalert.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import VueSweetalert2 from 'vue-sweetalert2'; 3 | 4 | Vue.use(VueSweetalert2); 5 | -------------------------------------------------------------------------------- /frontend/src/router.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import Home from './views/Home.vue' 4 | import { apiPath } from '@/utils/url' 5 | 6 | Vue.use(Router) 7 | 8 | export default new Router({ 9 | mode: 'history', 10 | base: '/', 11 | routes: [ 12 | { 13 | path: `/${apiPath()}`, 14 | redirect: { name: 'home' } 15 | }, 16 | { 17 | path: `/${apiPath()}/list`, 18 | name: 'home', 19 | component: Home 20 | }, 21 | ] 22 | }) 23 | -------------------------------------------------------------------------------- /frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import Vue from 'vue' 3 | import Vuex from 'vuex' 4 | import { getSavedState, saveState } from '@/utils/localStorage' 5 | 6 | Vue.use(Vuex) 7 | 8 | export default new Vuex.Store({ 9 | state: { 10 | config: {}, 11 | groups: [], 12 | headers: getSavedState('apidocs.headers'), 13 | }, 14 | 15 | mutations: { 16 | SET_CONFIG (state, config) { 17 | state.config = config 18 | }, 19 | 20 | SET_GROUPS (state, groups) { 21 | state.groups = groups 22 | }, 23 | 24 | SET_HEADERS (state, headers) { 25 | state.headers = headers 26 | saveState('apidocs.headers', headers) 27 | }, 28 | }, 29 | 30 | actions: { 31 | async fetchConfig ({ commit }) { 32 | const { data } = await axios.get('apidocs-api/config') 33 | 34 | commit('SET_CONFIG', data) 35 | }, 36 | 37 | async fetchGroups ({ commit }) { 38 | const { data: response } = await axios.get('apidocs-api/endpoints') 39 | 40 | commit('SET_GROUPS', response.data) 41 | }, 42 | 43 | setHeaders ({ commit }, headers) { 44 | commit('SET_HEADERS', headers) 45 | } 46 | }, 47 | 48 | getters: { 49 | config (state) { 50 | return state.config 51 | }, 52 | 53 | groups (state) { 54 | return state.groups 55 | }, 56 | 57 | headers (state) { 58 | return state.headers 59 | }, 60 | } 61 | }) 62 | -------------------------------------------------------------------------------- /frontend/src/utils/localStorage.js: -------------------------------------------------------------------------------- 1 | export function getSavedState(key) { 2 | return JSON.parse(window.localStorage.getItem(key)) 3 | } 4 | 5 | export function saveState(key, state) { 6 | window.localStorage.setItem(key, JSON.stringify(state)) 7 | } 8 | -------------------------------------------------------------------------------- /frontend/src/utils/toast.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export function success (obj = {}) { 4 | Vue.swal(Object.assign({ 5 | toast: true, 6 | width: 350, 7 | position: 'top', 8 | showConfirmButton: false, 9 | timer: 3000, 10 | type: 'success', 11 | title: 'Success!', 12 | text: 'it\'s a good day!' 13 | }, obj)) 14 | } 15 | 16 | export function fail (obj = {}) { 17 | Vue.swal(Object.assign({ 18 | toast: true, 19 | width: 450, 20 | position: 'top', 21 | showConfirmButton: false, 22 | timer: 3000, 23 | type: 'error', 24 | title: 'Oops...', 25 | text: 'Something went wrong!' 26 | }, obj)) 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/utils/url.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | export function apiUrl () { 4 | let url = document.head.querySelector('meta[name=api_url]').content 5 | 6 | return url ? url : '/' 7 | } 8 | 9 | export function apiPath () { 10 | let url = document.head.querySelector('meta[name=api_path]').content 11 | 12 | return url ? url : '/' 13 | } 14 | -------------------------------------------------------------------------------- /frontend/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 93 | 94 | 125 | -------------------------------------------------------------------------------- /frontend/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // proxy API requests to Valet during development 3 | devServer: { 4 | proxy: 'http://laracon.test' 5 | }, 6 | 7 | baseUrl: process.env.NODE_ENV == 'production' ? '/vendor/apidocs' : '/', 8 | 9 | // output built static files to Laravel's public dir. 10 | // note the "build" script in package.json needs to be modified as well. 11 | outputDir: '../public/vendor/apidocs', 12 | 13 | // modify the location of the generated HTML file. 14 | // make sure to do this only in production. 15 | indexPath: process.env.NODE_ENV === 'production' 16 | ? '../../../resources/views/index.blade.php' 17 | : 'index.html' 18 | } 19 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # The license 2 | 3 | Copyright (c) Harlequin Doyon 4 | 5 | ...Add your license text here... -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | src/ 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlekoy/laravel-apidocs/89ef02d9c969d6e4eff8475a6803efc7fcd13f15/public/favicon.ico -------------------------------------------------------------------------------- /public/vendor/apidocs/css/app.14d989bf.css: -------------------------------------------------------------------------------- 1 | #app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50}input{border-width:1px;border-color:#dae1e7}button:focus,input:focus{outline:0;border-width:1px;border-color:#6cb2eb}.rotate-1\/4{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-1\/2{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-3\/4{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.jsoneditor-menu{display:none}.ace_editor.ace-jsoneditor .ace_gutter{background-color:#f8fafc}.ace-jsoneditor .ace_folding-enabled>.ace_gutter-cell{background-color:#f1f5f8}.jsoneditor-box .jsoneditor{border-color:#dae1e7;border-radius:.25rem;overflow:hidden}.response .jsoneditor-container{background-color:#fff}.response .jsoneditor-box .jsoneditor{border-radius:0;border-color:#fff}.response /deep/ .jsoneditor-tree tr.jsoneditor-highlight{background-color:#f1f5f8}.overflow-hidden{-webkit-transition:all .3s ease;transition:all .3s ease}.description{margin-bottom:.5rem}a{text-decoration:none}.btn-loading{position:relative;pointer-events:none;color:transparent!important}.btn-loading:after{-webkit-animation:spinAround .5s linear infinite;animation:spinAround .5s linear infinite;border:2px solid #dbdbdb;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;width:1em;position:absolute;left:calc(50% - .5em);top:calc(50% - .5em)}@-webkit-keyframes spinAround{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}} -------------------------------------------------------------------------------- /public/vendor/apidocs/css/app.ae6c8111.css: -------------------------------------------------------------------------------- 1 | #app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50}input{border-width:1px;border-color:#dae1e7}button:focus,input:focus{outline:0;border-width:1px;border-color:#6cb2eb}.rotate-1\/4{-webkit-transform:rotate(90deg);transform:rotate(90deg)}.rotate-1\/2{-webkit-transform:rotate(180deg);transform:rotate(180deg)}.rotate-3\/4{-webkit-transform:rotate(270deg);transform:rotate(270deg)}.jsoneditor-menu{display:none}.ace_editor.ace-jsoneditor .ace_gutter{background-color:#f8fafc}.ace-jsoneditor .ace_folding-enabled>.ace_gutter-cell{background-color:#f1f5f8}.jsoneditor-box .jsoneditor{border-color:#dae1e7;border-radius:.25rem;overflow:hidden}.response .jsoneditor-container{background-color:#fff}.response .jsoneditor-box .jsoneditor{border-radius:0;border-color:#fff}.response /deep/ .jsoneditor-tree tr.jsoneditor-highlight{background-color:#f1f5f8}.overflow-hidden{-webkit-transition:all .3s ease;transition:all .3s ease}.description{margin-bottom:.5rem}a{text-decoration:none}.btn-loading{position:relative;pointer-events:none;color:transparent!important}.btn-loading:after{-webkit-animation:spinAround .5s linear infinite;animation:spinAround .5s linear infinite;border:2px solid #dbdbdb;border-radius:50%;border-right-color:transparent;border-top-color:transparent;content:"";display:block;height:1em;width:1em;position:absolute;left:calc(50% - .5em);top:calc(50% - .5em)}@-webkit-keyframes spinAround{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes spinAround{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}} -------------------------------------------------------------------------------- /public/vendor/apidocs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harlekoy/laravel-apidocs/89ef02d9c969d6e4eff8475a6803efc7fcd13f15/public/vendor/apidocs/favicon.ico -------------------------------------------------------------------------------- /public/vendor/apidocs/img/plus.bdc6b5b1.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vendor/apidocs/js/app.01e3ddc4.js: -------------------------------------------------------------------------------- 1 | (function(t){function e(e){for(var s,o,i=e[0],c=e[1],l=e[2],p=0,d=[];p0&&void 0!==arguments[0]?arguments[0]:{};s["a"].swal(Object.assign({toast:!0,width:350,position:"top",showConfirmButton:!1,timer:3e3,type:"success",title:"Success!",text:"it's a good day!"},t))}function ot(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};s["a"].swal(Object.assign({toast:!0,width:450,position:"top",showConfirmButton:!1,timer:3e3,type:"error",title:"Oops...",text:"Something went wrong!"},t))}var it={props:{value:{type:Object,default:function(){return{}}}},data:function(){return{json:{},options:{mode:"tree"}}},watch:{value:{immediate:!0,handler:function(t){this.json=t}},json:function(t){this.$emit("input",t)}},computed:{hasResponse:function(){return!Object(X["isEmpty"])(this.json)},strJson:function(){return JSON.stringify(this.json)}},methods:{onError:function(){console.error("Error response")},onCopy:function(){at({title:"",text:"Copied."})},clear:function(){this.json="",at({title:"",text:"Cleared."})}}},ct=it,lt=(n("9efb"),Object(d["a"])(ct,st,rt,!1,null,null,null));lt.options.__file="Response.vue";var ut=lt.exports,pt={components:{Headers:Y,Parameters:nt,QueryParameters:F,Response:ut},props:{api:{type:Object}},data:function(){return{methodColors:{post:"green",put:"orange",patch:"orange",get:"blue",delete:"red-light"},queryParams:{},response:{},json:{},show:!1}},watch:{api:{immediate:!0,handler:function(t){this.json=t}}},computed:Object(c["a"])({},Object(l["c"])(["config"]),{anchorClass:function(){return Object(P["a"])({},"border-".concat(this.color),!0)},buttonClass:function(){return Object(P["a"])({},"bg-".concat(this.color),!0)},color:function(){return this.methodColors[this.api.method.toLowerCase()]},accordionClass:function(){return{"h-0":!this.show}},strQueryParams:function(){if(!Object(X["isEmpty"])(this.queryParams))return"?"+T.a.stringify(this.queryParams)},endpoint:function(){var t=this.api.endpoint,e=this.api.parameters;return Object(X["each"])(e,function(n,s){var r=e[s];r||(r="{".concat(s,"}")),t=t.replace("{".concat(s,"}"),r)}),t}}),methods:{toggle:function(){this.show=!this.show},request:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e,n){var s,r,a,o,i,c;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,j()({method:this.json.method,url:e,params:this.queryParams,data:n});case 3:s=t.sent,r=s.data,at({text:"Responded"}),this.response=r,t.next=17;break;case 9:t.prev=9,t.t0=t["catch"](0),a=t.t0.response.data,o="Something went wrong!",i=Object(X["get"])(a,"message",o),c=Object(X["get"])(a,"exception",o),!i&&c&&(i=Object(X["last"])(Object(X["split"])(c,"\\"))),ot({title:"",text:i});case 17:this.$refs.params.finish();case 18:case"end":return t.stop()}},t,this,[[0,9]])}));return function(e,n){return t.apply(this,arguments)}}()}},dt=pt,ft=(n("be26"),Object(d["a"])(dt,E,S,!1,null,null,null));ft.options.__file="Endpoint.vue";var ht=ft.exports,mt={props:{group:{type:Object,default:function(){return{}}}},components:{Endpoint:ht},mounted:function(){this.show=this.config.group_open},data:function(){return{show:!0}},computed:Object(c["a"])({},Object(l["c"])(["config"])),methods:{toggle:function(){this.show=!this.show}}},vt=mt,gt=Object(d["a"])(vt,O,k,!1,null,null,null);gt.options.__file="Group.vue";var bt=gt.exports,wt={components:{Group:bt},mounted:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(){return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return t.next=2,this.fetchConfig();case 2:return t.next=4,this.fetchGroups();case 4:this.load=!1;case 5:case"end":return t.stop()}},t,this)}));return function(){return t.apply(this,arguments)}}(),data:function(){return{load:!0}},computed:Object(c["a"])({},Object(l["c"])(["config","groups"])),methods:Object(c["a"])({},Object(l["b"])(["fetchConfig","fetchGroups"]))},xt=wt,_t=(n("21bb"),Object(d["a"])(xt,x,_,!1,null,null,null));_t.options.__file="Home.vue";var yt=_t.exports;s["a"].use(w["a"]);var Ct=new w["a"]({mode:"history",base:"/vendor/apidocs/",routes:[{path:"/apidocs",redirect:{name:"home"}},{path:"/apidocs/list",name:"home",component:yt}]});function jt(t){return JSON.parse(window.localStorage.getItem(t))}function Ot(t,e){window.localStorage.setItem(t,JSON.stringify(e))}s["a"].use(l["a"]);var kt=new l["a"].Store({state:{config:{},groups:[],headers:jt("apidocs.headers")},mutations:{SET_CONFIG:function(t,e){t.config=e},SET_GROUPS:function(t,e){t.groups=e},SET_HEADERS:function(t,e){t.headers=e,Ot("apidocs.headers",e)}},actions:{fetchConfig:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return n=e.commit,t.next=3,j.a.get("apidocs-api/config");case 3:s=t.sent,r=s.data,n("SET_CONFIG",r);case 6:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),fetchGroups:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return n=e.commit,t.next=3,j.a.get("apidocs-api/endpoints");case 3:s=t.sent,r=s.data,n("SET_GROUPS",r.data);case 6:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),setHeaders:function(t,e){var n=t.commit;n("SET_HEADERS",e)}},getters:{config:function(t){return t.config},groups:function(t){return t.groups},headers:function(t){return t.headers}}});function Et(){return document.head.querySelector("meta[name=api_url]").content}j.a.defaults.baseURL=Et(),j.a.defaults.paramsSerializer=function(t){return T.a.stringify(t,{arrayFormat:"repeat"})},j.a.interceptors.request.use(function(t){return Object(X["isEmpty"])(kt.getters["headers"])||(t.headers=Object.assign(t.headers,kt.getters["headers"])),t}),j.a.interceptors.response.use(function(t){return t},function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:n=e.response,s=n.status,r=n.data.message,t.t0=s,t.next=500===t.t0?4:6;break;case 4:return ot({title:"Error!",text:r,timer:5e3,position:"bottom-right",width:500}),t.abrupt("break",6);case 6:return t.abrupt("return",Promise.reject(e));case 7:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}());var St=n("27f7");s["a"].use(St["a"]);var Pt=n("4eb5"),Rt=n.n(Pt);s["a"].use(Rt.a);var $t=n("619c");s["a"].use($t["a"]),s["a"].config.productionTip=!1,new s["a"]({router:Ct,store:kt,render:function(t){return t(b)}}).$mount("#app")},"5c0b":function(t,e,n){"use strict";var s=n("5e27"),r=n.n(s);r.a},"5e27":function(t,e,n){},"92a4":function(t,e,n){},"9efb":function(t,e,n){"use strict";var s=n("b2b9"),r=n.n(s);r.a},b2b9:function(t,e,n){},b68c:function(t,e,n){"use strict";var s=n("067f"),r=n.n(s);r.a},bcc9:function(t,e,n){},be26:function(t,e,n){"use strict";var s=n("92a4"),r=n.n(s);r.a}}); 2 | //# sourceMappingURL=app.01e3ddc4.js.map -------------------------------------------------------------------------------- /public/vendor/apidocs/js/app.46ab911e.js: -------------------------------------------------------------------------------- 1 | (function(t){function e(e){for(var s,o,i=e[0],c=e[1],l=e[2],p=0,d=[];p0&&void 0!==arguments[0]?arguments[0]:{};s["a"].swal(Object.assign({toast:!0,width:350,position:"top",showConfirmButton:!1,timer:3e3,type:"success",title:"Success!",text:"it's a good day!"},t))}function ot(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};s["a"].swal(Object.assign({toast:!0,width:450,position:"top",showConfirmButton:!1,timer:3e3,type:"error",title:"Oops...",text:"Something went wrong!"},t))}var it={props:{value:{type:Object,default:function(){return{}}},status:{type:Number,required:!0,default:function(){return-1}}},data:function(){return{json:{},options:{mode:"tree"}}},watch:{value:{immediate:!0,handler:function(t){this.json=t}},json:function(t){this.$emit("input",t)}},computed:{hasResponse:function(){return!Object(X["isEmpty"])(this.json)},strJson:function(){return JSON.stringify(this.json)}},methods:{onError:function(){console.error("Error response")},onCopy:function(){at({title:"",text:"Copied."})},clear:function(){this.json="",at({title:"",text:"Cleared."})}}},ct=it,lt=(n("9efb"),Object(d["a"])(ct,st,rt,!1,null,null,null));lt.options.__file="Response.vue";var ut=lt.exports,pt={components:{Headers:Y,Parameters:nt,QueryParameters:F,Response:ut},props:{api:{type:Object}},data:function(){return{methodColors:{post:"green",put:"orange",patch:"orange",get:"blue",delete:"red-light"},queryParams:{},response:{},json:{},show:!1,status:-1}},watch:{api:{immediate:!0,handler:function(t){this.json=t}}},computed:Object(c["a"])({},Object(l["c"])(["config"]),{anchorClass:function(){return Object(P["a"])({},"border-".concat(this.color),!0)},buttonClass:function(){return Object(P["a"])({},"bg-".concat(this.color),!0)},color:function(){return this.methodColors[this.api.method.toLowerCase()]},accordionClass:function(){return{"h-0":!this.show}},strQueryParams:function(){if(!Object(X["isEmpty"])(this.queryParams))return"?"+T.a.stringify(this.queryParams)},endpoint:function(){var t=this.api.endpoint,e=this.api.parameters;return Object(X["each"])(e,function(n,s){var r=e[s];r||(r="{".concat(s,"}")),t=t.replace("{".concat(s,"}"),r)}),t}}),methods:{toggle:function(){this.show=!this.show},request:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e,n){var s,r,a,o,i;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,j()({method:this.json.method,url:e,params:this.queryParams,data:n});case 3:s=t.sent,at({text:"Responded"}),this.response=s.data,this.status=s.status,t.next=19;break;case 9:t.prev=9,t.t0=t["catch"](0),r=t.t0.response,this.response=r.data,this.status=r.status,a="Something went wrong!",o=Object(X["get"])(r.data,"message",a),i=Object(X["get"])(r.data,"exception",a),!o&&i&&(o=Object(X["last"])(Object(X["split"])(i,"\\"))),ot({title:"",text:o});case 19:this.$refs.params.finish();case 20:case"end":return t.stop()}},t,this,[[0,9]])}));return function(e,n){return t.apply(this,arguments)}}()}},dt=pt,ft=(n("be26"),Object(d["a"])(dt,E,S,!1,null,null,null));ft.options.__file="Endpoint.vue";var ht=ft.exports,mt={props:{group:{type:Object,default:function(){return{}}}},components:{Endpoint:ht},mounted:function(){this.show=this.config.group_open},data:function(){return{show:!0}},computed:Object(c["a"])({},Object(l["c"])(["config"])),methods:{toggle:function(){this.show=!this.show}}},vt=mt,gt=Object(d["a"])(vt,O,k,!1,null,null,null);gt.options.__file="Group.vue";var bt=gt.exports,xt={components:{Group:bt},mounted:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(){return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return t.next=2,this.fetchConfig();case 2:return t.next=4,this.fetchGroups();case 4:this.load=!1;case 5:case"end":return t.stop()}},t,this)}));return function(){return t.apply(this,arguments)}}(),data:function(){return{load:!0}},computed:Object(c["a"])({},Object(l["c"])(["config","groups"])),methods:Object(c["a"])({},Object(l["b"])(["fetchConfig","fetchGroups"]))},wt=xt,_t=(n("21bb"),Object(d["a"])(wt,w,_,!1,null,null,null));_t.options.__file="Home.vue";var yt=_t.exports;s["a"].use(x["a"]);var Ct=new x["a"]({mode:"history",base:"/vendor/apidocs/",routes:[{path:"/apidocs",redirect:{name:"home"}},{path:"/apidocs/list",name:"home",component:yt}]});function jt(t){return JSON.parse(window.localStorage.getItem(t))}function Ot(t,e){window.localStorage.setItem(t,JSON.stringify(e))}s["a"].use(l["a"]);var kt=new l["a"].Store({state:{config:{},groups:[],headers:jt("apidocs.headers")},mutations:{SET_CONFIG:function(t,e){t.config=e},SET_GROUPS:function(t,e){t.groups=e},SET_HEADERS:function(t,e){t.headers=e,Ot("apidocs.headers",e)}},actions:{fetchConfig:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return n=e.commit,t.next=3,j.a.get("apidocs-api/config");case 3:s=t.sent,r=s.data,n("SET_CONFIG",r);case 6:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),fetchGroups:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return n=e.commit,t.next=3,j.a.get("apidocs-api/endpoints");case 3:s=t.sent,r=s.data,n("SET_GROUPS",r.data);case 6:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),setHeaders:function(t,e){var n=t.commit;n("SET_HEADERS",e)}},getters:{config:function(t){return t.config},groups:function(t){return t.groups},headers:function(t){return t.headers}}});function Et(){return document.head.querySelector("meta[name=api_url]").content}j.a.defaults.baseURL=Et(),j.a.defaults.paramsSerializer=function(t){return T.a.stringify(t,{arrayFormat:"repeat"})},j.a.interceptors.request.use(function(t){return Object(X["isEmpty"])(kt.getters["headers"])||(t.headers=Object.assign(t.headers,kt.getters["headers"])),t}),j.a.interceptors.response.use(function(t){return t},function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:n=e.response,s=n.status,r=n.data.message,t.t0=s,t.next=500===t.t0?4:6;break;case 4:return ot({title:"Error!",text:r,timer:5e3,position:"bottom-right",width:500}),t.abrupt("break",6);case 6:return t.abrupt("return",Promise.reject(e));case 7:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}());var St=n("27f7");s["a"].use(St["a"]);var Pt=n("4eb5"),Rt=n.n(Pt);s["a"].use(Rt.a);var $t=n("619c");s["a"].use($t["a"]),s["a"].config.productionTip=!1,new s["a"]({router:Ct,store:kt,render:function(t){return t(b)}}).$mount("#app")},"5c0b":function(t,e,n){"use strict";var s=n("5e27"),r=n.n(s);r.a},"5e27":function(t,e,n){},"92a4":function(t,e,n){},"9efb":function(t,e,n){"use strict";var s=n("b2b9"),r=n.n(s);r.a},b2b9:function(t,e,n){},b68c:function(t,e,n){"use strict";var s=n("067f"),r=n.n(s);r.a},bcc9:function(t,e,n){},be26:function(t,e,n){"use strict";var s=n("92a4"),r=n.n(s);r.a}}); 2 | //# sourceMappingURL=app.46ab911e.js.map -------------------------------------------------------------------------------- /public/vendor/apidocs/js/app.7209d6c2.js: -------------------------------------------------------------------------------- 1 | (function(t){function e(e){for(var s,o,i=e[0],c=e[1],l=e[2],p=0,d=[];p0&&void 0!==arguments[0]?arguments[0]:{};s["a"].swal(Object.assign({toast:!0,width:350,position:"top",showConfirmButton:!1,timer:3e3,type:"success",title:"Success!",text:"it's a good day!"},t))}function ot(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};s["a"].swal(Object.assign({toast:!0,width:450,position:"top",showConfirmButton:!1,timer:3e3,type:"error",title:"Oops...",text:"Something went wrong!"},t))}var it={props:{json:{type:Object,default:function(){return{}}}},data:function(){return{options:{mode:"tree"}}},computed:{hasResponse:function(){return!Object(X["isEmpty"])(this.json)},strJson:function(){return JSON.stringify(this.json)}},methods:{onError:function(){console.error("Error response")},onCopy:function(){at({title:"",text:"Copied."})}}},ct=it,lt=(n("9efb"),Object(d["a"])(ct,st,rt,!1,null,null,null));lt.options.__file="Response.vue";var ut=lt.exports,pt={components:{Headers:Y,Parameters:nt,QueryParameters:F,Response:ut},props:{api:{type:Object}},data:function(){return{methodColors:{post:"green",put:"orange",get:"blue",delete:"red-light"},queryParams:{},response:{},json:{},show:!1}},watch:{api:{immediate:!0,handler:function(t){this.json=t}}},computed:Object(c["a"])({},Object(l["c"])(["config"]),{anchorClass:function(){return Object(P["a"])({},"border-".concat(this.color),!0)},buttonClass:function(){return Object(P["a"])({},"bg-".concat(this.color),!0)},color:function(){return this.methodColors[this.api.method.toLowerCase()]},accordionClass:function(){return{"h-0":!this.show}},strQueryParams:function(){if(!Object(X["isEmpty"])(this.queryParams))return"?"+T.a.stringify(this.queryParams)}}),methods:{toggle:function(){this.show=!this.show},request:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e,n){var s,r,a,o,i,c;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,j()({method:this.json.method,url:e,params:this.queryParams,data:n});case 3:s=t.sent,r=s.data,at({text:"Responded"}),this.response=r,t.next=17;break;case 9:t.prev=9,t.t0=t["catch"](0),a=t.t0.response.data,o="Something went wrong!",i=Object(X["get"])(a,"message",o),c=Object(X["get"])(a,"exception",o),!i&&c&&(i=Object(X["last"])(Object(X["split"])(c,"\\"))),ot({title:"",text:i});case 17:this.$refs.params.finish();case 18:case"end":return t.stop()}},t,this,[[0,9]])}));return function(e,n){return t.apply(this,arguments)}}()}},dt=pt,ft=(n("be26"),Object(d["a"])(dt,E,S,!1,null,null,null));ft.options.__file="Endpoint.vue";var ht=ft.exports,mt={props:{group:{type:Object,default:function(){return{}}}},components:{Endpoint:ht},mounted:function(){this.show=this.config.group_open},data:function(){return{show:!0}},computed:Object(c["a"])({},Object(l["c"])(["config"])),methods:{toggle:function(){this.show=!this.show}}},vt=mt,gt=Object(d["a"])(vt,O,k,!1,null,null,null);gt.options.__file="Group.vue";var bt=gt.exports,wt={components:{Group:bt},mounted:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(){return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return t.next=2,this.fetchConfig();case 2:return t.next=4,this.fetchGroups();case 4:this.load=!1;case 5:case"end":return t.stop()}},t,this)}));return function(){return t.apply(this,arguments)}}(),data:function(){return{load:!0}},computed:Object(c["a"])({},Object(l["c"])(["config","groups"])),methods:Object(c["a"])({},Object(l["b"])(["fetchConfig","fetchGroups"]))},xt=wt,_t=(n("21bb"),Object(d["a"])(xt,x,_,!1,null,null,null));_t.options.__file="Home.vue";var yt=_t.exports;s["a"].use(w["a"]);var Ct=new w["a"]({mode:"history",base:"/vendor/apidocs/",routes:[{path:"/apidocs",redirect:{name:"home"}},{path:"/apidocs/list",name:"home",component:yt}]});function jt(t){return JSON.parse(window.localStorage.getItem(t))}function Ot(t,e){window.localStorage.setItem(t,JSON.stringify(e))}s["a"].use(l["a"]);var kt=new l["a"].Store({state:{config:{},groups:[],headers:jt("apidocs.headers")},mutations:{SET_CONFIG:function(t,e){t.config=e},SET_GROUPS:function(t,e){t.groups=e},SET_HEADERS:function(t,e){t.headers=e,Ot("apidocs.headers",e)}},actions:{fetchConfig:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return n=e.commit,t.next=3,j.a.get("apidocs-api/config");case 3:s=t.sent,r=s.data,n("SET_CONFIG",r);case 6:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),fetchGroups:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return n=e.commit,t.next=3,j.a.get("apidocs-api/endpoints");case 3:s=t.sent,r=s.data,n("SET_GROUPS",r.data);case 6:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),setHeaders:function(t,e){var n=t.commit;n("SET_HEADERS",e)}},getters:{config:function(t){return t.config},groups:function(t){return t.groups},headers:function(t){return t.headers}}});function Et(){return document.head.querySelector("meta[name=api_url]").content}j.a.defaults.baseURL=Et(),j.a.defaults.paramsSerializer=function(t){return T.a.stringify(t,{arrayFormat:"repeat"})},j.a.interceptors.request.use(function(t){return Object(X["isEmpty"])(kt.getters["headers"])||(t.headers=Object.assign(t.headers,kt.getters["headers"])),t}),j.a.interceptors.response.use(function(t){return t},function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:n=e.response,s=n.status,r=n.data.message,t.t0=s,t.next=500===t.t0?4:6;break;case 4:return ot({title:"Error!",text:r,timer:5e3,position:"bottom-right",width:500}),t.abrupt("break",6);case 6:return t.abrupt("return",Promise.reject(e));case 7:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}());var St=n("27f7");s["a"].use(St["a"]);var Pt=n("4eb5"),Rt=n.n(Pt);s["a"].use(Rt.a);var $t=n("619c");s["a"].use($t["a"]),s["a"].config.productionTip=!1,new s["a"]({router:Ct,store:kt,render:function(t){return t(b)}}).$mount("#app")},"5c0b":function(t,e,n){"use strict";var s=n("5e27"),r=n.n(s);r.a},"5e27":function(t,e,n){},"92a4":function(t,e,n){},"9efb":function(t,e,n){"use strict";var s=n("b2b9"),r=n.n(s);r.a},b2b9:function(t,e,n){},b68c:function(t,e,n){"use strict";var s=n("067f"),r=n.n(s);r.a},bcc9:function(t,e,n){},be26:function(t,e,n){"use strict";var s=n("92a4"),r=n.n(s);r.a}}); 2 | //# sourceMappingURL=app.7209d6c2.js.map -------------------------------------------------------------------------------- /public/vendor/apidocs/js/app.ffa9dd26.js: -------------------------------------------------------------------------------- 1 | (function(t){function e(e){for(var s,o,i=e[0],c=e[1],l=e[2],p=0,d=[];p0&&void 0!==arguments[0]?arguments[0]:{};s["a"].swal(Object.assign({toast:!0,width:350,position:"top",showConfirmButton:!1,timer:3e3,type:"success",title:"Success!",text:"it's a good day!"},t))}function ot(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};s["a"].swal(Object.assign({toast:!0,width:450,position:"top",showConfirmButton:!1,timer:3e3,type:"error",title:"Oops...",text:"Something went wrong!"},t))}var it={props:{json:{type:Object,default:function(){return{}}}},data:function(){return{options:{mode:"tree"}}},computed:{hasResponse:function(){return!Object(X["isEmpty"])(this.json)},strJson:function(){return JSON.stringify(this.json)}},methods:{onError:function(){console.error("Error response")},onCopy:function(){at({title:"",text:"Copied."})}}},ct=it,lt=(n("9efb"),Object(d["a"])(ct,st,rt,!1,null,null,null));lt.options.__file="Response.vue";var ut=lt.exports,pt={components:{Headers:Y,Parameters:nt,QueryParameters:F,Response:ut},props:{api:{type:Object}},data:function(){return{methodColors:{post:"green",put:"orange",patch:"orange",get:"blue",delete:"red-light"},queryParams:{},response:{},json:{},show:!1}},watch:{api:{immediate:!0,handler:function(t){this.json=t}}},computed:Object(c["a"])({},Object(l["c"])(["config"]),{anchorClass:function(){return Object(P["a"])({},"border-".concat(this.color),!0)},buttonClass:function(){return Object(P["a"])({},"bg-".concat(this.color),!0)},color:function(){return this.methodColors[this.api.method.toLowerCase()]},accordionClass:function(){return{"h-0":!this.show}},strQueryParams:function(){if(!Object(X["isEmpty"])(this.queryParams))return"?"+T.a.stringify(this.queryParams)}}),methods:{toggle:function(){this.show=!this.show},request:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e,n){var s,r,a,o,i,c;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return t.prev=0,t.next=3,j()({method:this.json.method,url:e,params:this.queryParams,data:n});case 3:s=t.sent,r=s.data,at({text:"Responded"}),this.response=r,t.next=17;break;case 9:t.prev=9,t.t0=t["catch"](0),a=t.t0.response.data,o="Something went wrong!",i=Object(X["get"])(a,"message",o),c=Object(X["get"])(a,"exception",o),!i&&c&&(i=Object(X["last"])(Object(X["split"])(c,"\\"))),ot({title:"",text:i});case 17:this.$refs.params.finish();case 18:case"end":return t.stop()}},t,this,[[0,9]])}));return function(e,n){return t.apply(this,arguments)}}()}},dt=pt,ft=(n("be26"),Object(d["a"])(dt,E,S,!1,null,null,null));ft.options.__file="Endpoint.vue";var ht=ft.exports,mt={props:{group:{type:Object,default:function(){return{}}}},components:{Endpoint:ht},mounted:function(){this.show=this.config.group_open},data:function(){return{show:!0}},computed:Object(c["a"])({},Object(l["c"])(["config"])),methods:{toggle:function(){this.show=!this.show}}},vt=mt,gt=Object(d["a"])(vt,O,k,!1,null,null,null);gt.options.__file="Group.vue";var bt=gt.exports,wt={components:{Group:bt},mounted:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(){return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return t.next=2,this.fetchConfig();case 2:return t.next=4,this.fetchGroups();case 4:this.load=!1;case 5:case"end":return t.stop()}},t,this)}));return function(){return t.apply(this,arguments)}}(),data:function(){return{load:!0}},computed:Object(c["a"])({},Object(l["c"])(["config","groups"])),methods:Object(c["a"])({},Object(l["b"])(["fetchConfig","fetchGroups"]))},xt=wt,_t=(n("21bb"),Object(d["a"])(xt,x,_,!1,null,null,null));_t.options.__file="Home.vue";var yt=_t.exports;s["a"].use(w["a"]);var Ct=new w["a"]({mode:"history",base:"/vendor/apidocs/",routes:[{path:"/apidocs",redirect:{name:"home"}},{path:"/apidocs/list",name:"home",component:yt}]});function jt(t){return JSON.parse(window.localStorage.getItem(t))}function Ot(t,e){window.localStorage.setItem(t,JSON.stringify(e))}s["a"].use(l["a"]);var kt=new l["a"].Store({state:{config:{},groups:[],headers:jt("apidocs.headers")},mutations:{SET_CONFIG:function(t,e){t.config=e},SET_GROUPS:function(t,e){t.groups=e},SET_HEADERS:function(t,e){t.headers=e,Ot("apidocs.headers",e)}},actions:{fetchConfig:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return n=e.commit,t.next=3,j.a.get("apidocs-api/config");case 3:s=t.sent,r=s.data,n("SET_CONFIG",r);case 6:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),fetchGroups:function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:return n=e.commit,t.next=3,j.a.get("apidocs-api/endpoints");case 3:s=t.sent,r=s.data,n("SET_GROUPS",r.data);case 6:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}(),setHeaders:function(t,e){var n=t.commit;n("SET_HEADERS",e)}},getters:{config:function(t){return t.config},groups:function(t){return t.groups},headers:function(t){return t.headers}}});function Et(){return document.head.querySelector("meta[name=api_url]").content}j.a.defaults.baseURL=Et(),j.a.defaults.paramsSerializer=function(t){return T.a.stringify(t,{arrayFormat:"repeat"})},j.a.interceptors.request.use(function(t){return Object(X["isEmpty"])(kt.getters["headers"])||(t.headers=Object.assign(t.headers,kt.getters["headers"])),t}),j.a.interceptors.response.use(function(t){return t},function(){var t=Object(y["a"])(regeneratorRuntime.mark(function t(e){var n,s,r;return regeneratorRuntime.wrap(function(t){while(1)switch(t.prev=t.next){case 0:n=e.response,s=n.status,r=n.data.message,t.t0=s,t.next=500===t.t0?4:6;break;case 4:return ot({title:"Error!",text:r,timer:5e3,position:"bottom-right",width:500}),t.abrupt("break",6);case 6:return t.abrupt("return",Promise.reject(e));case 7:case"end":return t.stop()}},t,this)}));return function(e){return t.apply(this,arguments)}}());var St=n("27f7");s["a"].use(St["a"]);var Pt=n("4eb5"),Rt=n.n(Pt);s["a"].use(Rt.a);var $t=n("619c");s["a"].use($t["a"]),s["a"].config.productionTip=!1,new s["a"]({router:Ct,store:kt,render:function(t){return t(b)}}).$mount("#app")},"5c0b":function(t,e,n){"use strict";var s=n("5e27"),r=n.n(s);r.a},"5e27":function(t,e,n){},"92a4":function(t,e,n){},"9efb":function(t,e,n){"use strict";var s=n("b2b9"),r=n.n(s);r.a},b2b9:function(t,e,n){},b68c:function(t,e,n){"use strict";var s=n("067f"),r=n.n(s);r.a},bcc9:function(t,e,n){},be26:function(t,e,n){"use strict";var s=n("92a4"),r=n.n(s);r.a}}); 2 | //# sourceMappingURL=app.ffa9dd26.js.map -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # API Docs 2 | 3 | [![Latest Version on Packagist][ico-version]][link-packagist] 4 | [![Total Downloads][ico-downloads]][link-downloads] 5 | [![Build Status][ico-travis]][link-travis] 6 | [![StyleCI][ico-styleci]][link-styleci] 7 | 8 | This is where your description should go. Take a look at [contributing.md](contributing.md) to see a to do list. 9 | 10 |

11 | 12 |

13 | 14 | ## Requirements 15 | 16 | - Laravel 5.5 and above 17 | 18 | ## Installation 19 | 20 | Via Composer 21 | 22 | ``` bash 23 | $ composer require harlekoy/apidocs 24 | ``` 25 | 26 | After installing API Docs, publish its assets, and migration file using the `apidocs:install` Artisan command. 27 | 28 | ``` bash 29 | $ php artisan apidocs:install 30 | ``` 31 | 32 | ## Updating API Docs 33 | 34 | When updating, you should re-publish API Docs's assets: 35 | 36 | ``` bash 37 | $ php artisan apidocs:publish 38 | ``` 39 | 40 | ## Reread API routes 41 | 42 | When updating your API docs route list, you should run this command to get your latest Laravel `route:list`: 43 | 44 | ``` 45 | $ php artisan apidocs:routes 46 | ``` 47 | 48 | ## Usage 49 | 50 | Access your API documentation in you browser 51 | ``` 52 | /apidocs/list 53 | ``` 54 | > If you don't have any local server running in your machine, run this command first in you command line tool `php artisan serve` then it generate this `http://127.0.0.1:8000`. Now you can access the path with that hostname `http://127.0.0.1:8000/apidocs/list` enjoy using it 😄. 55 | 56 | ## Change log 57 | 58 | Please see the [changelog](changelog.md) for more information on what has changed recently. 59 | 60 | ## Testing 61 | 62 | ``` bash 63 | $ composer test 64 | ``` 65 | 66 | ## Contributing 67 | 68 | Please see [contributing.md](contributing.md) for details and a todolist. 69 | 70 | ## Security 71 | 72 | If you discover any security related issues, please email me@harlekoy.com instead of using the issue tracker. 73 | 74 | ## Credits 75 | 76 | - [Harlequin Doyon][link-author] 77 | - [All Contributors][link-contributors] 78 | 79 | ## Road map 80 | 81 | Laravel API Docs is still under development, I decided to ship it in this early stage so you can help me make it better. however, you can use it in production. Lets finish the task below so we can have a version 1 up and running with the right features needed. 82 | 83 | Here's the plan for what's coming: 84 | 85 | - [x] Add support for API Docs custom path 86 | - [ ] Add transition effect for groups and endpoints accordion 87 | - [ ] Add a way to edit API Docs groups and endpoints in its the web interface 88 | - [ ] Add tests. 89 | - [ ] Add validation for required parameters 90 | 91 | ## License 92 | 93 | license. Please see the [license file](license.md) for more information. 94 | 95 | [ico-version]: https://img.shields.io/packagist/v/harlekoy/apidocs.svg?style=flat-square 96 | [ico-downloads]: https://img.shields.io/packagist/dt/harlekoy/apidocs.svg?style=flat-square 97 | [ico-travis]: https://img.shields.io/travis/harlekoy/apidocs/master.svg?style=flat-square 98 | [ico-styleci]: https://styleci.io/repos/12345678/shield 99 | 100 | [link-packagist]: https://packagist.org/packages/harlekoy/apidocs 101 | [link-downloads]: https://packagist.org/packages/harlekoy/apidocs 102 | [link-travis]: https://travis-ci.org/harlekoy/apidocs 103 | [link-styleci]: https://styleci.io/repos/12345678 104 | [link-author]: https://github.com/harlekoy 105 | [link-contributors]: ../../contributors] -------------------------------------------------------------------------------- /resources/views/index.blade.php: -------------------------------------------------------------------------------- 1 | API Docs
-------------------------------------------------------------------------------- /rewriteRules.php: -------------------------------------------------------------------------------- 1 | 'src/ApiDocs.php', 6 | 'config/mypackage.php' => 'config/apidocs.php', 7 | 'src/Facades/MyPackage.php' => 'src/Facades/ApiDocs.php', 8 | 'src/MyPackageServiceProvider.php' => 'src/ApiDocsServiceProvider.php', 9 | ]; -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | name('apidocs.config'); 12 | 13 | Route::get('endpoints', 'EndpointController@index') 14 | ->name('apidocs.endpoints'); 15 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | where('any', '.*'); 11 | -------------------------------------------------------------------------------- /rules.php: -------------------------------------------------------------------------------- 1 | 'src/ApiDocs.php', 6 | 'config/mypackage.php' => 'config/apidocs.php', 7 | 'src/Facades/MyPackage.php' => 'src/Facades/ApiDocs.php', 8 | 'src/MyPackageServiceProvider.php' => 'src/ApiDocsServiceProvider.php', 9 | ]; -------------------------------------------------------------------------------- /src/ApiDocs.php: -------------------------------------------------------------------------------- 1 | asks(); 38 | 39 | $this->comment('Publishing API Docs Service Provider...'); 40 | $this->callSilent('vendor:publish', ['--tag' => 'apidocs-provider']); 41 | 42 | if ($this->shouldMigrate()) { 43 | $this->comment('Migrating API Docs tables...'); 44 | $this->callSilent('migrate'); 45 | } 46 | 47 | $this->comment('Publishing API Docs Assets...'); 48 | $this->callSilent('vendor:publish', ['--tag' => 'apidocs-assets']); 49 | 50 | $this->registerApiDocsConfig(); 51 | $this->registerApiDocsServiceProvider(); 52 | $this->registerApiRoutes(); 53 | 54 | $this->info('API Docs scaffolding installed successfully.'); 55 | } 56 | 57 | /** 58 | * Register API routes. 59 | * 60 | * @return void 61 | */ 62 | public function registerApiRoutes() 63 | { 64 | $this->callSilent('apidocs:routes', [ 65 | '--middleware' => $this->middleware, 66 | '--prefix' => $this->prefix, 67 | ]); 68 | 69 | $this->comment('Scafolding API route list...'); 70 | } 71 | 72 | /** 73 | * Register API Docs configuration. 74 | * 75 | * @return void 76 | */ 77 | public function registerApiDocsConfig() 78 | { 79 | $this->comment('Publishing API Docs Configuration...'); 80 | $this->callSilent('vendor:publish', ['--tag' => 'apidocs-config']); 81 | 82 | $config = file_get_contents(config_path('apidocs.php')); 83 | 84 | if (!Str::contains($config, "'/api/v1'")) { 85 | return; 86 | } 87 | 88 | $version = trim($this->prefix, '/'); 89 | 90 | file_put_contents(config_path('apidocs.php'), str_replace( 91 | "'/api/v1'", 92 | "'/{$version}'", 93 | $config 94 | )); 95 | } 96 | 97 | /** 98 | * Register the API Docs service provider in the application configuration file. 99 | * 100 | * @return void 101 | */ 102 | protected function registerApiDocsServiceProvider() 103 | { 104 | $namespace = str_replace_last('\\', '', $this->getAppNamespace()); 105 | 106 | $appConfig = file_get_contents(config_path('app.php')); 107 | 108 | if (Str::contains($appConfig, $namespace.'\\Providers\\ApiDocsServiceProvider::class')) { 109 | return; 110 | } 111 | 112 | file_put_contents(config_path('app.php'), str_replace( 113 | "{$namespace}\\Providers\EventServiceProvider::class,".PHP_EOL, 114 | "{$namespace}\\Providers\EventServiceProvider::class,".PHP_EOL." {$namespace}\Providers\ApiDocsServiceProvider::class,".PHP_EOL, 115 | $appConfig 116 | )); 117 | 118 | file_put_contents(app_path('Providers/ApiDocsServiceProvider.php'), str_replace( 119 | "namespace App\Providers;", 120 | "namespace {$namespace}\Providers;", 121 | file_get_contents(app_path('Providers/ApiDocsServiceProvider.php')) 122 | )); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Commands/ApiRouteListCommand.php: -------------------------------------------------------------------------------- 1 | router = $router; 53 | $this->routes = $router->getRoutes(); 54 | 55 | // Repositories 56 | $this->group = app(ApiGroupRepository::class); 57 | $this->endpoint = app(ApiEndpointRepository::class); 58 | } 59 | 60 | /** 61 | * Execute the console command. 62 | * 63 | * @return void 64 | */ 65 | public function handle() 66 | { 67 | $this->asks(); 68 | $this->registerApiRouteList(); 69 | } 70 | 71 | /** 72 | * Register API route list. 73 | * 74 | * @return void 75 | */ 76 | public function registerApiRouteList() 77 | { 78 | foreach ($this->filterRoutes() as $title => $endpoints) { 79 | $groupId = $this->group->save(['name' => $title]); 80 | 81 | foreach ($endpoints as $endpoint) { 82 | $this->endpoint->save([ 83 | 'endpoint' => $endpoint['uri'], 84 | 'api_group_id' => $groupId, 85 | 'method' => str_replace(['|HEAD', '|PATCH'], '', $endpoint['method']), 86 | ]); 87 | } 88 | } 89 | 90 | Cache::forget('apidocs:endpoints'); 91 | 92 | $this->comment('Scafolding API route list...'); 93 | } 94 | 95 | /** 96 | * Filter routes. 97 | * 98 | * @return \Illuminate\Support\Collection 99 | */ 100 | public function filterRoutes() 101 | { 102 | return collect($this->getRoutes()) 103 | ->filter(function ($route) { 104 | $middlewares = explode( 105 | ',', array_get($route, 'middleware', '') 106 | ); 107 | 108 | return in_array($this->middleware, $middlewares) 109 | && str_contains(array_get($route, 'uri'), $this->prefix); 110 | }) 111 | ->map(function ($route) { 112 | $uri = array_get($route, 'uri'); 113 | 114 | return array_merge($route, [ 115 | 'uri' => str_replace($this->prefix, '', $uri), 116 | ]); 117 | }) 118 | ->groupBy(function ($route) { 119 | $uri = array_get($route, 'uri'); 120 | $route = preg_replace('/\/\{\w+\??\}/', '',$uri); 121 | $fields = array_filter(explode('/', $route)); 122 | 123 | $title = implode(' ', $fields); 124 | 125 | return ucfirst($title); 126 | }); 127 | } 128 | 129 | /** 130 | * Get the console command options. 131 | * 132 | * @return array 133 | */ 134 | protected function getOptions() 135 | { 136 | return array_merge(parent::getOptions(), [ 137 | ['middleware', null, InputOption::VALUE_OPTIONAL, 'API middleware'], 138 | ['prefix', null, InputOption::VALUE_OPTIONAL, 'API prefix'], 139 | ]); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Commands/PublishCommand.php: -------------------------------------------------------------------------------- 1 | call('vendor:publish', [ 31 | '--tag' => 'apidocs-config', 32 | '--force' => $this->option('force'), 33 | ]); 34 | 35 | $this->call('vendor:publish', [ 36 | '--tag' => 'apidocs-assets', 37 | '--force' => true, 38 | ]); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Contracts/ApiEndpointRepository.php: -------------------------------------------------------------------------------- 1 | hasMany(Endpoint::class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Drivers/Database/Models/Endpoint.php: -------------------------------------------------------------------------------- 1 | 'array', 35 | 'sample_response' => 'array', 36 | ]; 37 | 38 | 39 | /** 40 | * Get endpoint group. 41 | * 42 | * @return \Illuminate\Database\Eloquent\Relations\BelongsTo 43 | */ 44 | public function group() 45 | { 46 | return $this->belongsTo(ApiGroup::class, 'api_group_id'); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Drivers/Database/Repository/DatabaseApiEndpointRepository.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 26 | } 27 | 28 | /** 29 | * Return all the entries of a given type. 30 | * 31 | * @return \Illuminate\Support\Collection 32 | */ 33 | public function get() 34 | { 35 | return collect( 36 | Endpoint::on($this->connection) 37 | ->get() 38 | ->toArray() 39 | ); 40 | } 41 | 42 | /** 43 | * Save the given entries. 44 | * 45 | * @param \Illuminate\Support\Collection $endpoints 46 | * 47 | * @return integer 48 | */ 49 | public function save(array $data) 50 | { 51 | $endpoint = Endpoint::on($this->connection) 52 | ->updateOrCreate(array_only($data , 'endpoint'), $data); 53 | 54 | return $endpoint->id; 55 | } 56 | 57 | /** 58 | * Store the given API endpoint. 59 | * 60 | * @param array $data 61 | * 62 | * @return void 63 | */ 64 | public function store(array $data) 65 | { 66 | Endpoint::on($this->connection) 67 | ->create($data); 68 | } 69 | 70 | /** 71 | * Return an API endpoint with the given id. 72 | * 73 | * @param mixed $id 74 | * 75 | * @return \Harlekoy\ApiDocs\Drivers\Database\Models\Endpoint 76 | */ 77 | public function find($id) 78 | { 79 | return Endpoint::on($this->connection)->find($id); 80 | } 81 | 82 | /** 83 | * Store the given API endpoint updates. 84 | * 85 | * @param array $data 86 | * 87 | * @return void 88 | */ 89 | public function update(array $data) 90 | { 91 | $endpoint = $this->find(array_get($data, 'id')); 92 | 93 | if ($endpoint) { 94 | $endpoint->fill($data)->save(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Drivers/Database/Repository/DatabaseApiGroupRepository.php: -------------------------------------------------------------------------------- 1 | connection = $connection; 27 | } 28 | 29 | /** 30 | * Return all the entries of a given type. 31 | * 32 | * @return \Illuminate\Support\Collection 33 | */ 34 | public function get() 35 | { 36 | return collect( 37 | ApiGroup::on($this->connection) 38 | ->orderBy('name') 39 | ->get() 40 | ->toArray() 41 | ); 42 | } 43 | 44 | /** 45 | * Save the given entries. 46 | * 47 | * @param \Illuminate\Support\Collection $endpoints 48 | * 49 | * @return integer 50 | */ 51 | public function save(array $data) 52 | { 53 | $group = ApiGroup::on($this->connection) 54 | ->firstOrNew(array_only($data, 'name'), $data); 55 | 56 | $group->save(); 57 | 58 | return $group->id; 59 | } 60 | 61 | /** 62 | * Store the given API group. 63 | * 64 | * @param array $data 65 | * 66 | * @return void 67 | */ 68 | public function store(array $data) 69 | { 70 | ApiGroup::on($this->connection)->create($data); 71 | } 72 | 73 | /** 74 | * Return an API group with the given id. 75 | * 76 | * @param mixed $id 77 | * 78 | * @return \Harlekoy\ApiDocs\Drivers\Database\Models\ApiGroup 79 | */ 80 | public function find($id) 81 | { 82 | return ApiGroup::on($this->connection)->find($id); 83 | } 84 | 85 | /** 86 | * Store the given API group updates. 87 | * 88 | * @param array $data 89 | * 90 | * @return void 91 | */ 92 | public function update(array $data) 93 | { 94 | $group = $this->find(array_get($data , 'id')); 95 | 96 | if ($group) { 97 | $group->fill($data)->save(); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Drivers/Database/Traits/RegisterDatabaseDriver.php: -------------------------------------------------------------------------------- 1 | app->singleton( 20 | ApiGroupRepository::class, DatabaseApiGroupRepository::class 21 | ); 22 | 23 | $this->app->singleton( 24 | ApiEndpointRepository::class, DatabaseApiEndpointRepository::class 25 | ); 26 | 27 | $this->app->when(DatabaseApiGroupRepository::class) 28 | ->needs('$connection') 29 | ->give(config('apidocs.storage.database.connection')); 30 | 31 | $this->app->when(DatabaseApiEndpointRepository::class) 32 | ->needs('$connection') 33 | ->give(config('apidocs.storage.database.connection')); 34 | } 35 | 36 | /** 37 | * Register the package's migrations. 38 | * 39 | * @return void 40 | */ 41 | private function registerMigrations() 42 | { 43 | if ($this->app->runningInConsole() && $this->shouldMigrate()) { 44 | $this->loadMigrationsFrom(__DIR__.'../migrations'); 45 | } 46 | } 47 | 48 | /** 49 | * Determine if we should register the migrations. 50 | * 51 | * @return bool 52 | */ 53 | protected function shouldMigrate() 54 | { 55 | return config('apidocs.driver') === 'database'; 56 | } 57 | } -------------------------------------------------------------------------------- /src/Drivers/Database/migrations/2018_11_06_041130_create_endpoints_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name'); 19 | $table->string('description')->nullable(); 20 | $table->timestamps(); 21 | }); 22 | 23 | Schema::create('endpoints', function (Blueprint $table) { 24 | $table->increments('id'); 25 | $table->unsignedInteger('api_group_id'); 26 | $table->string('endpoint'); 27 | $table->string('method'); 28 | $table->string('description')->nullable(); 29 | $table->json('parameters')->nullable(); 30 | $table->json('sample_response')->nullable(); 31 | $table->timestamps(); 32 | }); 33 | } 34 | 35 | /** 36 | * Reverse the migrations. 37 | * 38 | * @return void 39 | */ 40 | public function down() 41 | { 42 | Schema::dropIfExists('api_groups'); 43 | Schema::dropIfExists('endpoints'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Drivers/File/FileStore.php: -------------------------------------------------------------------------------- 1 | path(); 34 | 35 | if (!File::exists($path)) { 36 | File::put($path, '{}'); 37 | } 38 | 39 | $object = json_decode(File::get($path), true); 40 | 41 | if ($this->currentObject) { 42 | return array_get($object, $this->currentObject, []); 43 | } 44 | 45 | return $object; 46 | } 47 | 48 | /** 49 | * Get full content. 50 | * 51 | * @return array 52 | */ 53 | public function fullContent() 54 | { 55 | $current = $this->currentObject; 56 | $content = $this->setCurrentObject(null)->content(); 57 | $this->setCurrentObject($current); 58 | 59 | return $content; 60 | } 61 | 62 | /** 63 | * Get content as collection. 64 | * 65 | * @return \Illuminate\Support\Collection 66 | */ 67 | public function get() 68 | { 69 | return collect($this->content()); 70 | } 71 | 72 | /** 73 | * Collection push. 74 | * 75 | * @param array $item 76 | * 77 | * @return void 78 | */ 79 | public function push(array $item) 80 | { 81 | $id = Uuid::generate()->string; 82 | $content = $this->get()->push(array_merge($item, compact('id'))); 83 | 84 | $this->put($content); 85 | 86 | return $id; 87 | } 88 | 89 | /** 90 | * File put content. 91 | * 92 | * @param Collection $content 93 | * 94 | * @return void 95 | */ 96 | public function put(Collection $content) 97 | { 98 | $full = $this->fullContent(); 99 | $full[$this->currentObject] = $content; 100 | 101 | File::put($this->path(), collect($full)->toJson(JSON_PRETTY_PRINT)); 102 | } 103 | 104 | /** 105 | * Set current object accessing. 106 | * 107 | * @param string $name 108 | */ 109 | public function setCurrentObject($name) 110 | { 111 | $this->currentObject= $name; 112 | 113 | return $this; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Drivers/File/Repository/FileApiEndpointRepository.php: -------------------------------------------------------------------------------- 1 | file = $file->setCurrentObject('endpoints'); 25 | } 26 | 27 | /** 28 | * Return all the entries of a given type. 29 | * 30 | * @return \Illuminate\Support\Collection 31 | */ 32 | public function get() 33 | { 34 | return $this->file->get(); 35 | } 36 | 37 | /** 38 | * Save the given entries. 39 | * 40 | * @param \Illuminate\Support\Collection $endpoints 41 | * 42 | * @return boolean 43 | */ 44 | public function save(array $data) 45 | { 46 | $endpoint = $this->find(array_only($data, [ 47 | 'endpoint', 'method', 48 | ])); 49 | 50 | if ($endpoint) { 51 | $this->update(array_merge($endpoint, $data)); 52 | } else { 53 | $this->store($data); 54 | } 55 | } 56 | 57 | /** 58 | * Get the API group title. 59 | * 60 | * @param array $endpoint 61 | * 62 | * @return string 63 | */ 64 | protected function title($endpoint) 65 | { 66 | $uri = array_get($endpoint, 'endpoint'); 67 | $route = preg_replace('/\/\{\w+\??\}/', '',$uri); 68 | $fields = array_filter(explode('/', $route)); 69 | $title = implode(' ', $fields); 70 | 71 | return ucfirst($title); 72 | } 73 | 74 | /** 75 | * Store the given API group. 76 | * 77 | * @param array $data 78 | * 79 | * @return void 80 | */ 81 | public function store(array $data) 82 | { 83 | $this->file->push($data); 84 | } 85 | 86 | /** 87 | * Return an API group with the given name. 88 | * 89 | * @param array $attributes 90 | * 91 | * @return array 92 | */ 93 | public function find($attributes) 94 | { 95 | return $this->file 96 | ->get() 97 | ->where('endpoint', array_get($attributes, 'endpoint')) 98 | ->where('method', array_get($attributes, 'method')) 99 | ->first(); 100 | } 101 | 102 | /** 103 | * Store the given API group updates. 104 | * 105 | * @param array $data 106 | * 107 | * @return void 108 | */ 109 | public function update(array $data) 110 | { 111 | $content = $this->file 112 | ->get() 113 | ->transform(function ($item) use ($data) { 114 | return array_get($item, 'endpoint') === array_get($data, 'endpoint') && 115 | array_get($item, 'method') === array_get($data, 'method') 116 | ? array_merge($item, $data) 117 | : $item; 118 | }); 119 | 120 | $this->file->put($content); 121 | } 122 | 123 | /** 124 | * Update endpoint. 125 | * 126 | * @param array $endpoints 127 | * @param array $data 128 | * 129 | * @return \Illuminate\Support\Collection 130 | */ 131 | protected function updateEndpoint($endpoints, $data) 132 | { 133 | return collect($endpoints) 134 | ->transform(function ($item) use ($data) { 135 | return array_get($item, 'endpoint') === array_get($data, 'endpoint') || 136 | array_get($item, 'method') === array_get($data, 'method') 137 | ? array_merge($item, $data) 138 | : $item; 139 | }); 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /src/Drivers/File/Repository/FileApiGroupRepository.php: -------------------------------------------------------------------------------- 1 | file = $file->setCurrentObject('groups'); 26 | } 27 | 28 | /** 29 | * Return all the entries of a given type. 30 | * 31 | * @return \Illuminate\Support\Collection 32 | */ 33 | public function get() 34 | { 35 | return $this->file->get(); 36 | } 37 | 38 | /** 39 | * Save the given entries. 40 | * 41 | * @param \Illuminate\Support\Collection $endpoints 42 | * 43 | * @return boolean 44 | */ 45 | public function save(array $data) 46 | { 47 | $name = array_get($data, 'name'); 48 | $group = $this->find($name); 49 | 50 | if ($group) { 51 | $this->update( 52 | array_merge($group, array_except($data, 'name')) 53 | ); 54 | 55 | return $group['id'] ?? null; 56 | } else { 57 | return $this->store($data); 58 | } 59 | } 60 | 61 | /** 62 | * Store the given API group. 63 | * 64 | * @param array $data 65 | * 66 | * @return void 67 | */ 68 | public function store(array $data) 69 | { 70 | return $this->file->push($data); 71 | } 72 | 73 | /** 74 | * Return an API group with the given name. 75 | * 76 | * @param mixed $name 77 | * 78 | * @return array 79 | */ 80 | public function find($name) 81 | { 82 | return $this->file 83 | ->get() 84 | ->firstWhere('name', $name); 85 | } 86 | 87 | /** 88 | * Store the given API group updates. 89 | * 90 | * @param array $data 91 | * 92 | * @return void 93 | */ 94 | public function update(array $data) 95 | { 96 | $content = $this->file 97 | ->get() 98 | ->transform(function ($item) use ($data) { 99 | return array_get($item, 'name') === array_get($data, 'name') 100 | ? array_merge($item, $data) 101 | : $item; 102 | }); 103 | 104 | $this->file->put($content); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/Drivers/File/Traits/RegisterFileDriver.php: -------------------------------------------------------------------------------- 1 | app->singleton( 23 | ApiGroupRepository::class, FileApiGroupRepository::class 24 | ); 25 | 26 | $this->app->singleton( 27 | ApiEndpointRepository::class, FileApiEndpointRepository::class 28 | ); 29 | 30 | $this->app->when(FileApiGroupRepository::class) 31 | ->needs('$file') 32 | ->give($this->getFile()); 33 | 34 | $this->app->when(FileApiEndpointRepository::class) 35 | ->needs('$file') 36 | ->give($this->getFile()); 37 | } 38 | 39 | /** 40 | * Get file. 41 | * 42 | * @return \Harlekoy\ApiDocs\Drivers\File\FileStore 43 | */ 44 | protected function getFile() 45 | { 46 | return new FileStore(); 47 | } 48 | } -------------------------------------------------------------------------------- /src/Facades/ApiDocs.php: -------------------------------------------------------------------------------- 1 | get() 27 | ->map(function ($data) use ($endpoint) { 28 | return array_merge($data, [ 29 | 'endpoints' => $endpoint->get() 30 | ->where('api_group_id', $data['id']) 31 | ]); 32 | }); 33 | } 34 | ); 35 | 36 | return response()->json(['data' => $groups]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Http/Controllers/SpaController.php: -------------------------------------------------------------------------------- 1 | method()); 27 | 28 | if (method_exists($this, $method)) { 29 | return $this->$method(); 30 | } 31 | 32 | return []; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Http/Resources/EndpointResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'endpoint' => $this->endpoint, 20 | 'method' => $this->method, 21 | 'description' => $this->description, 22 | 'parameters' => $this->parameters, 23 | 'sample_response' => $this->sample_response, 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Http/Resources/GroupResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 20 | 'name' => $this->name, 21 | 'description' => $this->description, 22 | 'endpoints' => EndpointResource::collection($this->endpoints), 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Nova/Endpoint.php: -------------------------------------------------------------------------------- 1 | endpoint; 56 | } 57 | 58 | /** 59 | * Get the fields displayed by the resource. 60 | * 61 | * @param \Illuminate\Http\Request $request 62 | * @return array 63 | */ 64 | public function fields(Request $request) 65 | { 66 | return [ 67 | ID::make()->sortable(), 68 | 69 | BelongsTo::make('Group') 70 | ->rules('required', 'max:255'), 71 | 72 | Select::make('Method')->options([ 73 | 'GET' => 'GET', 74 | 'POST' => 'POST', 75 | 'PUT' => 'PUT', 76 | 'DELETE' => 'DELETE', 77 | ]) 78 | ->rules('required', 'max:255'), 79 | 80 | Text::make('Endpoint') 81 | ->sortable() 82 | ->rules('required', 'max:255'), 83 | 84 | Textarea::make('Description'), 85 | 86 | Code::make('Parameters') 87 | ->json(), 88 | 89 | Code::make('Sample Response') 90 | ->json() 91 | ]; 92 | } 93 | 94 | /** 95 | * Get the cards available for the request. 96 | * 97 | * @param \Illuminate\Http\Request $request 98 | * @return array 99 | */ 100 | public function cards(Request $request) 101 | { 102 | return []; 103 | } 104 | 105 | /** 106 | * Get the filters available for the resource. 107 | * 108 | * @param \Illuminate\Http\Request $request 109 | * @return array 110 | */ 111 | public function filters(Request $request) 112 | { 113 | return []; 114 | } 115 | 116 | /** 117 | * Get the lenses available for the resource. 118 | * 119 | * @param \Illuminate\Http\Request $request 120 | * @return array 121 | */ 122 | public function lenses(Request $request) 123 | { 124 | return []; 125 | } 126 | 127 | /** 128 | * Get the actions available for the resource. 129 | * 130 | * @param \Illuminate\Http\Request $request 131 | * @return array 132 | */ 133 | public function actions(Request $request) 134 | { 135 | return []; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Nova/Group.php: -------------------------------------------------------------------------------- 1 | description; 52 | } 53 | 54 | /** 55 | * Get the fields displayed by the resource. 56 | * 57 | * @param \Illuminate\Http\Request $request 58 | * @return array 59 | */ 60 | public function fields(Request $request) 61 | { 62 | return [ 63 | ID::make()->sortable(), 64 | 65 | HasMany::make('Endpoints') 66 | ->sortable(), 67 | 68 | Text::make('Name') 69 | ->sortable(), 70 | 71 | Textarea::make('Description'), 72 | ]; 73 | } 74 | 75 | /** 76 | * Get the cards available for the request. 77 | * 78 | * @param \Illuminate\Http\Request $request 79 | * @return array 80 | */ 81 | public function cards(Request $request) 82 | { 83 | return []; 84 | } 85 | 86 | /** 87 | * Get the filters available for the resource. 88 | * 89 | * @param \Illuminate\Http\Request $request 90 | * @return array 91 | */ 92 | public function filters(Request $request) 93 | { 94 | return []; 95 | } 96 | 97 | /** 98 | * Get the lenses available for the resource. 99 | * 100 | * @param \Illuminate\Http\Request $request 101 | * @return array 102 | */ 103 | public function lenses(Request $request) 104 | { 105 | return []; 106 | } 107 | 108 | /** 109 | * Get the actions available for the resource. 110 | * 111 | * @param \Illuminate\Http\Request $request 112 | * @return array 113 | */ 114 | public function actions(Request $request) 115 | { 116 | return []; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Providers/ApiDocsApplicationServiceProvider.php: -------------------------------------------------------------------------------- 1 | authorization(); 19 | } 20 | 21 | /** 22 | * Configure the Telescope authorization services. 23 | * 24 | * @return void 25 | */ 26 | protected function authorization() 27 | { 28 | $this->gate(); 29 | 30 | ApiDocs::auth(function ($request) { 31 | return app()->environment('local') || 32 | Gate::check('viewApiDocs', [$request->user()]); 33 | }); 34 | } 35 | 36 | /** 37 | * Register the Telescope gate. 38 | * 39 | * This gate determines who can access Telescope in non-local environments. 40 | * 41 | * @return void 42 | */ 43 | protected function gate() 44 | { 45 | Gate::define('viewApiDocs', function ($user) { 46 | return in_array($user->email, [ 47 | // 48 | ]); 49 | }); 50 | } 51 | 52 | /** 53 | * Register any application services. 54 | * 55 | * @return void 56 | */ 57 | public function register() 58 | { 59 | // 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Providers/ApiDocsRouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 28 | 29 | $this->mapWebRoutes(); 30 | } 31 | 32 | /** 33 | * Define the "web" routes for the application. 34 | * 35 | * These routes all receive session state, CSRF protection, etc. 36 | * 37 | * @return void 38 | */ 39 | protected function mapWebRoutes() 40 | { 41 | Route::prefix(config('apidocs.path')) 42 | ->middleware(config('apidocs.middleware')) 43 | ->namespace($this->namespace) 44 | ->group(__DIR__.'/../../routes/web.php'); 45 | } 46 | 47 | /** 48 | * Define the "api" routes for the application. 49 | * 50 | * These routes are typically stateless. 51 | * 52 | * @return void 53 | */ 54 | protected function mapApiRoutes() 55 | { 56 | Route::prefix('apidocs-api') 57 | ->namespace($this->namespace) 58 | ->group(__DIR__.'/../../routes/api.php'); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Providers/ApiDocsServiceProvider.php: -------------------------------------------------------------------------------- 1 | registerResources(); 28 | $this->registerMigrations(); 29 | 30 | // Publishing is only necessary when using the CLI. 31 | if ($this->app->runningInConsole()) { 32 | $this->definePublishing(); 33 | } 34 | } 35 | 36 | /** 37 | * Register any package services. 38 | * 39 | * @return void 40 | */ 41 | public function register() 42 | { 43 | if (! defined('APIDOCS_PATH')) { 44 | define('APIDOCS_PATH', realpath(__DIR__.'/../../')); 45 | } 46 | 47 | $this->app->register(ApiDocsRouteServiceProvider::class); 48 | 49 | $this->mergeConfigFrom(APIDOCS_PATH.'/config/apidocs.php', 'apidocs'); 50 | 51 | // Register the service the package provides. 52 | $this->app->singleton('apidocs', function ($app) { 53 | return new ApiDocs; 54 | }); 55 | 56 | $this->registerStorageDriver(); 57 | 58 | // Registering package commands. 59 | $this->commands([ 60 | ApiDocsInstall::class, 61 | ApiRouteListCommand::class, 62 | PublishCommand::class, 63 | ]); 64 | } 65 | 66 | /** 67 | * Get the services provided by the provider. 68 | * 69 | * @return array 70 | */ 71 | public function provides() 72 | { 73 | return ['apidocs']; 74 | } 75 | 76 | /** 77 | * Register the Totem resources. 78 | * 79 | * @return void 80 | */ 81 | protected function registerResources() 82 | { 83 | $this->loadViewsFrom(APIDOCS_PATH.'/resources/views', 'apidocs'); 84 | } 85 | 86 | /** 87 | * Define the publishing configuration. 88 | * 89 | * @return void 90 | */ 91 | public function definePublishing() 92 | { 93 | $this->publishes([ 94 | APIDOCS_PATH.'/public/vendor/apidocs/js' => public_path('vendor/apidocs/js'), 95 | ], 'apidocs-assets'); 96 | 97 | $this->publishes([ 98 | APIDOCS_PATH.'/public/vendor/apidocs/css' => public_path('vendor/apidocs/css'), 99 | ], 'apidocs-assets'); 100 | 101 | $this->publishes([ 102 | APIDOCS_PATH.'/public/vendor/apidocs/img' => public_path('vendor/apidocs/img'), 103 | ], 'apidocs-assets'); 104 | 105 | $this->publishes([ 106 | APIDOCS_PATH.'/config/apidocs.php' => config_path('apidocs.php'), 107 | ], 'apidocs-config'); 108 | 109 | $this->publishes([ 110 | APIDOCS_PATH.'/stubs/ApiDocsServiceProvider.stub' => app_path('Providers/ApiDocsServiceProvider.php'), 111 | ], 'apidocs-provider'); 112 | } 113 | 114 | /** 115 | * Register the package storage driver. 116 | * 117 | * @return void 118 | */ 119 | protected function registerStorageDriver() 120 | { 121 | $driver = config('apidocs.driver'); 122 | 123 | if (method_exists($this, $method = 'register'.ucfirst($driver).'Driver')) { 124 | $this->$method(); 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Traits/ApiResource.php: -------------------------------------------------------------------------------- 1 | apiInstances($this->init()); 30 | } 31 | 32 | /** 33 | * Display a listing of the resource. 34 | * 35 | * @return \Illuminate\Http\Response 36 | */ 37 | public function index(Request $request, Model $model) 38 | { 39 | $records = $this->fetchRecords(); 40 | 41 | return $this->apiResponse($records); 42 | } 43 | 44 | /** 45 | * Store a newly created resource in storage. 46 | * 47 | * @param \Illuminate\Http\Request $request 48 | * @return \Illuminate\Http\Response 49 | */ 50 | public function store(Request $request, Model $model) 51 | { 52 | $this->fillAndSave($model); 53 | 54 | return $this->apiResponse($model); 55 | } 56 | 57 | /** 58 | * Display the specified resource. 59 | * 60 | * @param int $id 61 | * @return \Illuminate\Http\Response 62 | */ 63 | public function show($id) 64 | { 65 | $model = $this->fetchModel($id); 66 | 67 | return $this->apiResponse($model); 68 | } 69 | 70 | /** 71 | * Update the specified resource in storage. 72 | * 73 | * @param \Illuminate\Http\Request $request 74 | * @param int $id 75 | * 76 | * @return \Illuminate\Http\Response 77 | */ 78 | public function update(Request $request, $id) 79 | { 80 | $model = $this->fetchModel($id); 81 | 82 | $this->fillAndSave($model); 83 | 84 | return $this->apiResponse($model); 85 | } 86 | 87 | /** 88 | * Remove the specified resource from storage. 89 | * 90 | * @param int $id 91 | * @return \Illuminate\Http\Response 92 | */ 93 | public function destroy($id) 94 | { 95 | $model = $this->fetchModel($id); 96 | 97 | $model->delete(); 98 | 99 | return $this->apiResponse($model); 100 | } 101 | 102 | /** 103 | * API resource response. 104 | * 105 | * @param \Illuminate\Database\Eloquent\Model $model 106 | * 107 | * @return array 108 | */ 109 | public function apiResponse($model) 110 | { 111 | $apiResource = $this->resource; 112 | 113 | if ($model instanceOf Collection || 114 | $model instanceOf LengthAwarePaginator) { 115 | return $apiResource::collection($model); 116 | } 117 | 118 | return new $apiResource($model); 119 | } 120 | 121 | /** 122 | * Fetch model. 123 | * 124 | * @return \Illuminate\Database\Eloquent\Model 125 | */ 126 | public function fetchModel($id) 127 | { 128 | $model = app(Model::class); 129 | 130 | return $model->findOrFail($id); 131 | } 132 | 133 | /** 134 | * Fetch records based on query params. 135 | * 136 | * @return \Illuminate\Database\Eloquent\Collection|\Illuminate\Contracts\Pagination\LengthAwarePaginator 137 | */ 138 | public function fetchRecords() 139 | { 140 | $model = app(Model::class); 141 | 142 | if ($page = request()->get('page')) { 143 | return $model->paginate($this->limit()); 144 | } 145 | 146 | return $model->get(); 147 | } 148 | 149 | /** 150 | * Get request limit or return the default if not exist. 151 | * 152 | * @return int 153 | */ 154 | public function limit() 155 | { 156 | return request()->get('limit', $this->limit); 157 | } 158 | 159 | /** 160 | * Fill and save model. 161 | * 162 | * @param \Illuminate\Database\Eloquent\Model $model 163 | * @return boolean 164 | */ 165 | public function fillAndSave(&$model) 166 | { 167 | return $model->fill(request()->all())->save(); 168 | } 169 | 170 | /** 171 | * Boot controller app instances. 172 | * 173 | * @return void 174 | */ 175 | public function apiInstances($instances) 176 | { 177 | app()->instance(Request::class, new $instances['request']); 178 | app()->instance(Model::class, new $instances['model']); 179 | 180 | $this->resource = $instances['resource']; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/Traits/AskForApiInfo.php: -------------------------------------------------------------------------------- 1 | option(); 25 | 26 | // Ask for middleware to filter API routes 27 | if ($middleware = array_get($options, 'middleware')) { 28 | $this->middleware = $middleware; 29 | } else { 30 | $this->middleware = $this->ask( 31 | 'Enter name to customize the middleware used to generate this doc? Default:', 'api' 32 | ); 33 | } 34 | 35 | // Ask for the prefix to filter API routes 36 | if ($prefix = array_get($options, 'prefix')) { 37 | $this->prefix = $prefix; 38 | } else { 39 | $this->prefix = $this->ask('Enter api prefix and version? Default:', 'api'); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Traits/AuthorizesRequests.php: -------------------------------------------------------------------------------- 1 | environment('local'); 37 | })($request); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Traits/Cachable.php: -------------------------------------------------------------------------------- 1 | email, [ 30 | // 31 | ]); 32 | }); 33 | } 34 | } 35 | --------------------------------------------------------------------------------