├── .docker ├── .dockerignore ├── Dockerfile ├── bin │ ├── apachelinker.sh │ └── entrypoint.sh └── conf │ ├── .bashrc │ └── apache.conf ├── .editorconfig ├── .env.example ├── .env.testing.example ├── .eslintignore ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .rsync-filter ├── .styleci.yml ├── API.md ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── apache.conf ├── app ├── Console │ ├── Commands │ │ ├── CompanyClientCommand.php │ │ ├── FixNoCompany.php │ │ └── SetupApplicationCommand.php │ └── Kernel.php ├── Constants │ ├── BoardKeys.php │ ├── BoardListsKeys.php │ ├── CardTypes.php │ ├── LabelKeys.php │ ├── RouteConstants.php │ ├── TeamKeys.php │ └── UserStoryStatuses.php ├── Events │ └── UserRegisteredEvent.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Auth │ │ │ ├── ConfirmPasswordController.php │ │ │ ├── ForgotPasswordController.php │ │ │ ├── LoginController.php │ │ │ ├── RegisterController.php │ │ │ ├── ResetPasswordController.php │ │ │ └── VerificationController.php │ │ ├── BacklogLabelController.php │ │ ├── BoardController.php │ │ ├── BoardListController.php │ │ ├── CardController.php │ │ ├── Controller.php │ │ ├── EventController.php │ │ ├── GoalController.php │ │ ├── LabelController.php │ │ ├── MemberController.php │ │ ├── MilestoneController.php │ │ ├── ProcessController.php │ │ ├── SprintReportController.php │ │ ├── TeamController.php │ │ ├── UserController.php │ │ └── WorkspaceController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── Authenticate.php │ │ ├── CheckForMaintenanceMode.php │ │ ├── EnableCompanyGlobalScope.php │ │ ├── EncryptCookies.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── ResolveCompanyTenant.php │ │ ├── TrimStrings.php │ │ ├── TrustProxies.php │ │ └── VerifyCsrfToken.php │ ├── Requests │ │ └── StoreCardRequest.php │ └── Resources │ │ ├── BacklogLabelResource.php │ │ ├── CardResource.php │ │ ├── LabelResource.php │ │ ├── MemberResource.php │ │ ├── MilestoneResource.php │ │ ├── ProcessResource.php │ │ ├── SprintReportResource.php │ │ ├── TeamResource.php │ │ └── WorkspaceResource.php ├── Library │ └── Utils │ │ └── BagOfConstants.php ├── Listeners │ └── SendWelcomingEmail.php ├── Mail │ └── WelcomeMail.php ├── Models │ ├── BacklogLabel.php │ ├── Board.php │ ├── BoardList.php │ ├── Card.php │ ├── Company.php │ ├── CompanyClient.php │ ├── Event.php │ ├── Goal.php │ ├── Label.php │ ├── Member.php │ ├── Milestone.php │ ├── Process.php │ ├── Scopes │ │ └── CompanyScope.php │ ├── SprintReport.php │ ├── Team.php │ ├── TeamMember.php │ └── Workspace.php ├── Observers │ ├── CardObserver.php │ └── TeamObserver.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── Services │ ├── BoardListService.php │ ├── CardService.php │ └── WorkspaceService.php └── User.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── database.php ├── filesystems.php ├── hashing.php ├── logging.php ├── mail.php ├── queue.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── factories │ ├── BoardListFactory.php │ ├── CardFactory.php │ ├── CompanyFactory.php │ ├── MemberFactory.php │ ├── TeamFactory.php │ ├── UserFactory.php │ └── WorkspaceFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ └── 2014_10_12_100000_create_password_resets_table.php └── seeds │ └── DatabaseSeeder.php ├── deploy.bash ├── docker-compose.yml ├── init-infra.sh ├── mongo_indexs.js ├── on-server.sh ├── package-lock.json ├── package.json ├── phpcs.xml ├── phpunit.xml ├── public ├── .htaccess ├── favicon.ico ├── images │ ├── christmas-divider.png │ ├── elofy.png │ ├── empty-list.svg │ ├── favicon.ico │ ├── logo-dark.svg │ ├── logo-natal.png │ ├── logo.svg │ └── sysvale-logo.svg ├── index.php └── robots.txt ├── requirements.txt ├── resources ├── js │ ├── bootstrap.js │ ├── core │ │ ├── constants │ │ │ ├── milestoneStatuses.js │ │ │ └── userStoryStatuses.js │ │ └── utils │ │ │ ├── convertKeysToCamelCase.js │ │ │ ├── convertKeysToSnakeCase.js │ │ │ ├── generateUUID.js │ │ │ ├── makeBoardStore.js │ │ │ ├── makeCrudServices.js │ │ │ ├── makeFormFields.js │ │ │ ├── makeMutations.js │ │ │ ├── makeRequestStore.js │ │ │ ├── requestsStates │ │ │ ├── failureState.js │ │ │ ├── index.js │ │ │ ├── initialState.js │ │ │ ├── requestState.js │ │ │ └── successState.js │ │ │ └── upperCamelCase.js │ ├── http.js │ ├── modules │ │ ├── board │ │ │ ├── app.js │ │ │ ├── components │ │ │ │ ├── AcceptanceCriteriaForm.vue │ │ │ │ ├── ArtifactItem.vue │ │ │ │ ├── ArtifactsForm.vue │ │ │ │ ├── Board.vue │ │ │ │ ├── BoardContainer.vue │ │ │ │ ├── BoardContent.vue │ │ │ │ ├── Card.vue │ │ │ │ ├── ChecklistForm.vue │ │ │ │ ├── EventsBoard.vue │ │ │ │ ├── InlineDeleteConfirmation.vue │ │ │ │ ├── LabelItem.vue │ │ │ │ ├── LabelList.vue │ │ │ │ ├── LabelSelect.vue │ │ │ │ ├── LinkChip.vue │ │ │ │ ├── List.vue │ │ │ │ ├── ListContainer.vue │ │ │ │ ├── ListSkeletonLoader.vue │ │ │ │ ├── MemberItem.vue │ │ │ │ ├── MemberList.vue │ │ │ │ ├── MemberSelect.vue │ │ │ │ ├── SprintBacklogOverview.vue │ │ │ │ ├── SprintTabContent.vue │ │ │ │ ├── SwitchButton.vue │ │ │ │ ├── TeamChip.vue │ │ │ │ ├── TooltipRating.vue │ │ │ │ ├── UserStoriesBoard.vue │ │ │ │ ├── UserStoryPipeline.vue │ │ │ │ └── UserStoryPipelineItem.vue │ │ │ ├── constants │ │ │ │ ├── BoardKeys.js │ │ │ │ ├── BoardListKeys.js │ │ │ │ ├── CardTypes.js │ │ │ │ ├── LabelKeys.js │ │ │ │ └── PlanningGroups.js │ │ │ ├── routes │ │ │ │ └── index.js │ │ │ ├── services │ │ │ │ ├── boards.js │ │ │ │ ├── cards.js │ │ │ │ ├── events.js │ │ │ │ ├── goals.js │ │ │ │ ├── planning.js │ │ │ │ ├── sprint.js │ │ │ │ ├── sprintReport.js │ │ │ │ └── userStories.js │ │ │ ├── store │ │ │ │ ├── boards.js │ │ │ │ ├── cards.js │ │ │ │ ├── events.js │ │ │ │ ├── goals.js │ │ │ │ ├── index.js │ │ │ │ ├── planning.js │ │ │ │ ├── sprint.js │ │ │ │ └── sprintReport.js │ │ │ └── views │ │ │ │ ├── App.vue │ │ │ │ ├── CompanyPlanning.vue │ │ │ │ ├── Home.vue │ │ │ │ ├── Planning.vue │ │ │ │ ├── Sprint.vue │ │ │ │ └── WorkspaceSelection.vue │ │ ├── milestones │ │ │ ├── components │ │ │ │ ├── MilestoneSelect.vue │ │ │ │ └── TeamsSelect.vue │ │ │ ├── routes │ │ │ │ └── index.js │ │ │ ├── services │ │ │ │ └── milestones.js │ │ │ ├── store │ │ │ │ ├── index.js │ │ │ │ └── milestones.js │ │ │ └── views │ │ │ │ ├── Board.vue │ │ │ │ └── Home.vue │ │ ├── processes │ │ │ ├── components │ │ │ │ ├── CardFromProcessModal.vue │ │ │ │ ├── ChecklistFromProcessSelect.vue │ │ │ │ ├── ProcessForm.vue │ │ │ │ └── ViewProcessModal.vue │ │ │ ├── routes │ │ │ │ └── index.js │ │ │ ├── services │ │ │ │ └── processes.js │ │ │ ├── store │ │ │ │ ├── index.js │ │ │ │ └── processes.js │ │ │ └── views │ │ │ │ ├── Create.vue │ │ │ │ ├── Edit.vue │ │ │ │ └── Home.vue │ │ ├── reports │ │ │ ├── components │ │ │ │ ├── SprintReportDialog.vue │ │ │ │ └── SprintReportDialogContent.vue │ │ │ ├── routes │ │ │ │ └── index.js │ │ │ ├── services │ │ │ │ └── sprintReports.js │ │ │ ├── store │ │ │ │ ├── index.js │ │ │ │ └── sprintReports.js │ │ │ └── views │ │ │ │ ├── Home.vue │ │ │ │ └── SprintReport.vue │ │ └── settings │ │ │ ├── routes │ │ │ └── index.js │ │ │ ├── services │ │ │ ├── backlogLabels.js │ │ │ ├── labels.js │ │ │ ├── members.js │ │ │ ├── teams.js │ │ │ └── workspaces.js │ │ │ ├── store │ │ │ ├── backlogLabels.js │ │ │ ├── index.js │ │ │ ├── labels.js │ │ │ ├── members.js │ │ │ ├── teams.js │ │ │ └── workspaces.js │ │ │ └── views │ │ │ ├── BacklogLabels.vue │ │ │ ├── Home.vue │ │ │ ├── Labels.vue │ │ │ ├── Members.vue │ │ │ ├── Teams.vue │ │ │ └── Workspaces.vue │ └── vuetify.js ├── lang │ └── en │ │ ├── auth.php │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php ├── sass │ ├── _variables.scss │ ├── app.scss │ └── home.scss └── views │ ├── auth │ ├── login.blade.php │ ├── passwords │ │ ├── confirm.blade.php │ │ ├── email.blade.php │ │ └── reset.blade.php │ ├── register.blade.php │ └── verify.blade.php │ ├── index.blade.php │ ├── layouts │ └── app.blade.php │ └── vendor │ ├── emails │ └── welcome.blade.php │ └── mail │ ├── html │ ├── button.blade.php │ ├── footer.blade.php │ ├── header.blade.php │ ├── layout.blade.php │ ├── message.blade.php │ ├── panel.blade.php │ ├── promotion.blade.php │ ├── promotion │ │ └── button.blade.php │ ├── subcopy.blade.php │ ├── table.blade.php │ └── themes │ │ └── default.css │ └── text │ ├── button.blade.php │ ├── footer.blade.php │ ├── header.blade.php │ ├── layout.blade.php │ ├── message.blade.php │ ├── panel.blade.php │ ├── promotion.blade.php │ ├── promotion │ └── button.blade.php │ ├── subcopy.blade.php │ └── table.blade.php ├── routes ├── api.php ├── channels.php ├── console.php └── web.php ├── server.php ├── storage ├── app │ ├── .gitignore │ └── public │ │ └── .gitignore ├── framework │ ├── .gitignore │ ├── cache │ │ ├── .gitignore │ │ └── data │ │ │ └── .gitignore │ ├── sessions │ │ └── .gitignore │ ├── testing │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore ├── tests ├── CreatesApplication.php ├── Feature │ └── App │ │ ├── Console │ │ └── Commands │ │ │ ├── CompanyClientCommandTest.php │ │ │ └── FixNoCompanyTest.php │ │ ├── Http │ │ └── Controllers │ │ │ ├── CardControllerTest.php │ │ │ ├── MemberControllerTest.php │ │ │ ├── TeamControllerTest.php │ │ │ └── WorkspaceControllerTest.php │ │ └── Observers │ │ └── CardObserverTest.php ├── TestCase.php └── Unit │ └── ExampleTest.php └── webpack.mix.js /.docker/.dockerignore: -------------------------------------------------------------------------------- 1 | mongo 2 | -------------------------------------------------------------------------------- /.docker/bin/apachelinker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | PUBLIC_FOLDER=$1 4 | 5 | ln -sfn $1 /var/www/dev 6 | service apache2 start 7 | -------------------------------------------------------------------------------- /.docker/bin/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./.docker/bin/apachelinker.sh /home/board/public 4 | 5 | chown www-data:$USER bootstrap/cache -R 6 | chown www-data storage -R 7 | 8 | if ! [ -d /tmp/dev.log ]; then 9 | touch /tmp/dev.log 10 | fi 11 | 12 | tail -f /tmp/dev.log 13 | -------------------------------------------------------------------------------- /.docker/conf/.bashrc: -------------------------------------------------------------------------------- 1 | export TZ=America/Recife 2 | 3 | echo " _ *** ${BASE_IMAGE} ***" 4 | echo " *v* *** ${IMAGE_ALIAS} ***" 5 | echo " /(_)\ " 6 | echo " ^ ^ " `date` 7 | 8 | echo -e ${INFO_IMAGE} 9 | 10 | export PS1="\[$(tput bold)\][\[\e[33m\]${BASE_IMAGE}${SEPARATOR}${IMAGE_ALIAS}\[\e[m\]\[$(tput bold)\]] \[$(tput bold)\]\[\033[38;5;4m\]\w\[$(tput sgr0)\]\[$(tput sgr0)\]\[\033[38;5;15m\]\[$(tput sgr0)\]\$(__git_ps1) # " 11 | 12 | alias ls='ls --color=auto' 13 | 14 | if ! shopt -oq posix; then 15 | if [ -f /usr/share/bash-completion/bash_completion ]; then 16 | . /usr/share/bash-completion/bash_completion 17 | elif [ -f /etc/bash_completion ]; then 18 | . /etc/bash_completion 19 | fi 20 | fi 21 | -------------------------------------------------------------------------------- /.docker/conf/apache.conf: -------------------------------------------------------------------------------- 1 | # credits: github.com/lissonpsantos2 2 | 3 | 4 | ServerName dev.app 5 | ServerAlias www.dev.app 6 | 7 | ServerAdmin webmaster@localhost 8 | DocumentRoot /var/www/dev 9 | 10 | 11 | Options -Indexes +FollowSymLinks +MultiViews +Includes 12 | AllowOverride AuthConfig 13 | Require all granted 14 | 15 | DirectoryIndex index.php 16 | 17 | 18 | 19 | Options -Indexes +FollowSymLinks +MultiViews +Includes 20 | AllowOverride All 21 | Require all granted 22 | RewriteEngine on 23 | RewriteBase / 24 | RewriteCond %{REQUEST_FILENAME} !-f 25 | RewriteCond %{REQUEST_FILENAME} !-d 26 | RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] 27 | RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 28 | 29 | 30 | 31 | ErrorLog /tmp/dev.log 32 | CustomLog /tmp/dev.log combined 33 | 34 | 35 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | [*.yml] 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Trelássio 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=daily 8 | 9 | DB_CONNECTION=mongodb 10 | DB_HOST=mongo 11 | DB_PORT=27017 12 | DB_DATABASE=trelassio 13 | DB_USERNAME= 14 | DB_PASSWORD= 15 | 16 | BROADCAST_DRIVER=log 17 | CACHE_DRIVER=file 18 | QUEUE_CONNECTION=sync 19 | SESSION_DRIVER=file 20 | SESSION_LIFETIME=120 21 | 22 | REDIS_HOST=127.0.0.1 23 | REDIS_PASSWORD=null 24 | REDIS_PORT=6379 25 | 26 | MAIL_DRIVER=smtp 27 | MAIL_HOST=smtp.mailtrap.io 28 | MAIL_PORT=2525 29 | MAIL_USERNAME=null 30 | MAIL_PASSWORD=null 31 | MAIL_ENCRYPTION=null 32 | 33 | AWS_ACCESS_KEY_ID= 34 | AWS_SECRET_ACCESS_KEY= 35 | AWS_DEFAULT_REGION=us-east-1 36 | AWS_BUCKET= 37 | 38 | PUSHER_APP_ID= 39 | PUSHER_APP_KEY= 40 | PUSHER_APP_SECRET= 41 | PUSHER_APP_CLUSTER=mt1 42 | 43 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 44 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 45 | 46 | MIX_UTIL_LINKS= 47 | -------------------------------------------------------------------------------- /.env.testing.example: -------------------------------------------------------------------------------- 1 | APP_NAME=Trelássio 2 | APP_ENV=testing 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=http://localhost 6 | 7 | LOG_CHANNEL=daily 8 | 9 | DB_CONNECTION=mongodb 10 | DB_HOST=mongo 11 | DB_PORT=27017 12 | DB_DATABASE=trelassio_test 13 | DB_USERNAME= 14 | DB_PASSWORD= 15 | 16 | BROADCAST_DRIVER=log 17 | CACHE_DRIVER=file 18 | QUEUE_CONNECTION=sync 19 | SESSION_DRIVER=file 20 | SESSION_LIFETIME=120 21 | 22 | REDIS_HOST=127.0.0.1 23 | REDIS_PASSWORD=null 24 | REDIS_PORT=6379 25 | 26 | MAIL_DRIVER=smtp 27 | MAIL_HOST=smtp.mailtrap.io 28 | MAIL_PORT=2525 29 | MAIL_USERNAME=null 30 | MAIL_PASSWORD=null 31 | MAIL_ENCRYPTION=null 32 | 33 | AWS_ACCESS_KEY_ID= 34 | AWS_SECRET_ACCESS_KEY= 35 | AWS_DEFAULT_REGION=us-east-1 36 | AWS_BUCKET= 37 | 38 | PUSHER_APP_ID= 39 | PUSHER_APP_KEY= 40 | PUSHER_APP_SECRET= 41 | PUSHER_APP_CLUSTER=mt1 42 | 43 | MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" 44 | MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" 45 | 46 | GITLAB_TOKEN= 47 | GITLAB_PROJECT_ID= 48 | MIX_UTIL_LINKS= 49 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sysvale/board/8cad9124064683250de0dcc312a1a296d29e8cdc/.eslintignore -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "parserOptions": { 3 | "parser": "babel-eslint", 4 | "ecmaVersion": 2015, 5 | "sourceType": "module" 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:vue/recommended", 10 | "airbnb-base" 11 | ], 12 | 13 | "settings": { 14 | "import/resolver": { 15 | "node": { 16 | "extensions": [ 17 | ".js", 18 | ".vue" 19 | ] 20 | } 21 | } 22 | }, 23 | 24 | "rules": { 25 | "vue/html-indent": ["error", "tab"], 26 | "indent": ["error", "tab"], 27 | "semi": ["error", "always"], 28 | "eol-last": ["error", "always"], 29 | "no-tabs": "off", 30 | "no-param-reassign": "off", 31 | "camelcase": "off", 32 | "no-else-return": "error" 33 | }, 34 | 35 | "globals": { 36 | "localStorage": true, 37 | "d3": true, 38 | "_": true, 39 | "moment": true, 40 | "axios": true, 41 | "swal": true 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | *.js linguist-vendored 5 | CHANGELOG.md export-ignore 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /public/*.js 5 | /public/css 6 | /public/js 7 | /public/mix-manifest.json 8 | /storage/*.key 9 | /vendor 10 | .env 11 | .env.backup 12 | .env.testing 13 | .phpunit.result.cache 14 | Homestead.json 15 | Homestead.yaml 16 | npm-debug.log 17 | yarn-error.log 18 | public/mix-manifest.json 19 | .docker/mongo 20 | .virtualenv 21 | .vscode/ 22 | .idea/ 23 | -------------------------------------------------------------------------------- /.rsync-filter: -------------------------------------------------------------------------------- 1 | - .docker 2 | - node_modules 3 | - storage/logs/* 4 | - .git 5 | - .env 6 | - .env.example 7 | - tests 8 | - storage/framework/sessions/* 9 | - storage/framework/cache/* 10 | - production 11 | - public/.htaccess 12 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | php: 2 | preset: laravel 3 | disabled: 4 | - unused_use 5 | finder: 6 | not-name: 7 | - index.php 8 | - server.php 9 | js: 10 | finder: 11 | not-name: 12 | - webpack.mix.js 13 | css: true 14 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Sysvale Health Tech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /apache.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerName dev.app 3 | ServerAlias www.dev.app 4 | 5 | ServerAdmin webmaster@localhost 6 | DocumentRoot /var/www/dev 7 | 8 | 9 | Options -Indexes +FollowSymLinks +MultiViews +Includes 10 | AllowOverride AuthConfig 11 | Order allow,deny 12 | allow from all 13 | 14 | 15 | 16 | Options -Indexes +FollowSymLinks +MultiViews +Includes 17 | Order allow,deny 18 | allow from all 19 | RewriteEngine on 20 | RewriteBase / 21 | RewriteCond %{REQUEST_FILENAME} !-f 22 | RewriteCond %{REQUEST_FILENAME} !-d 23 | RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] 24 | RewriteRule ^ - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 25 | 26 | 27 | 28 | ErrorLog /tmp/dev.log 29 | CustomLog /tmp/dev.log combined 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/Console/Commands/CompanyClientCommand.php: -------------------------------------------------------------------------------- 1 | argument('company_id')); 19 | if (!$company) { 20 | $this->error('Company not found'); 21 | return; 22 | } 23 | 24 | $company_client = CompanyClient::firstOrNew(['company_id' => $company->id]); 25 | 26 | if (!$company_client->client) { 27 | try { 28 | $client = $this->createClient(); 29 | $company_client->client_id = $client['client_id']; 30 | $company_client->save(); 31 | } catch (RuntimeException $exception) { 32 | $this->error($exception->getMessage()); 33 | return; 34 | } 35 | } 36 | 37 | $this->line('Client ID: ' . $company_client->client_id); 38 | $this->line('Client Secret: ' . $company_client->refresh()->client->secret); 39 | } 40 | 41 | private function createClient(): array 42 | { 43 | $process = new Process(['php', 'artisan', 'passport:client', '--client']); 44 | $process->run(); 45 | 46 | throw_if(!$process->isSuccessful(), RuntimeException::class, $process->getErrorOutput()); 47 | 48 | $output = $process->getOutput(); 49 | 50 | if (!preg_match('/Client ID: (.+)\nClient secret: (.+)\n/', $output, $matches)) { 51 | throw new RuntimeException('Não foi possível extrair o client_id e o client_secret.'); 52 | } 53 | 54 | return [ 55 | 'client_id' => $matches[1], 56 | 'client_secret' => $matches[2], 57 | ]; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 28 | // ->hourly(); 29 | } 30 | 31 | /** 32 | * Register the commands for the application. 33 | * 34 | * @return void 35 | */ 36 | protected function commands() 37 | { 38 | $this->load(__DIR__.'/Commands'); 39 | 40 | require base_path('routes/console.php'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Constants/BoardKeys.php: -------------------------------------------------------------------------------- 1 | user = $user; 28 | } 29 | 30 | /** 31 | * Get the channels the event should broadcast on. 32 | * 33 | * @return \Illuminate\Broadcasting\Channel|array 34 | */ 35 | public function broadcastOn() 36 | { 37 | return new PrivateChannel('channel-name'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ForgotPasswordController.php: -------------------------------------------------------------------------------- 1 | middleware('guest')->except('logout'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/ResetPasswordController.php: -------------------------------------------------------------------------------- 1 | configured_password = true; 35 | $user->password = Hash::make($password); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/VerificationController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 39 | $this->middleware('signed')->only('verify'); 40 | $this->middleware('throttle:6,1')->only('verify', 'resend'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/Http/Controllers/BacklogLabelController.php: -------------------------------------------------------------------------------- 1 | get(); 16 | } 17 | 18 | public function store(Request $request) 19 | { 20 | $data = $request->validate([ 21 | 'name' => 'required|string', 22 | 'text_color' => 'required|string', 23 | 'color' => 'required|string', 24 | ]); 25 | 26 | $backlog_label = BacklogLabel::create($data); 27 | 28 | return new BacklogLabelResource($backlog_label); 29 | } 30 | 31 | public function update(Request $request, BacklogLabel $backlog_label) 32 | { 33 | $data = $request->validate([ 34 | 'name' => 'required|string', 35 | 'text_color' => 'required|string', 36 | 'color' => 'required|string', 37 | ]); 38 | 39 | $backlog_label->update($data); 40 | 41 | return new BacklogLabelResource($backlog_label); 42 | } 43 | 44 | public function destroy(BacklogLabel $backlog_label) 45 | { 46 | $backlog_label->delete(); 47 | 48 | return Response::json('Categoria excluída com sucesso!'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/Http/Controllers/BoardController.php: -------------------------------------------------------------------------------- 1 | getTaskLists($in->team_id); 17 | } 18 | 19 | public function getDevlogLists(Request $in) 20 | { 21 | return (new BoardListService())->getDevLists($in->team_id); 22 | 23 | } 24 | 25 | public function getPlanningLists(Workspace $workspace) 26 | { 27 | return (new BoardListService())->getPlanningLists($workspace->id); 28 | } 29 | 30 | public function getCompanyPlanningLists() 31 | { 32 | return (new BoardListService())->getCompanyPlanningLists(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | id) 14 | ->get() 15 | ->sortByDesc('created_at') 16 | ->values(); 17 | 18 | return $impediments; 19 | } 20 | 21 | public function store(Request $in) 22 | { 23 | $event = Event::create([ 24 | 'name' => $in->name, 25 | 'members' => $in->members, 26 | 'team_id' => $in->team_id, 27 | 'labels' => $in->labels, 28 | 'start' => $in->start, 29 | 'end' => $in->end, 30 | ]); 31 | 32 | return $event; 33 | } 34 | 35 | public function update(Request $in, $id) 36 | { 37 | $params = [ 38 | 'name' => $in->name, 39 | 'labels' => $in->labels, 40 | 'members' => $in->members, 41 | 'team_id' => $in->team_id ?? $in->team['id'], 42 | 'start' => $in->start, 43 | 'end' => $in->end, 44 | ]; 45 | 46 | $event = Event::where('_id', $id); 47 | $event->update($params); 48 | 49 | return $event->get()->first(); 50 | } 51 | 52 | public function destroy($id) 53 | { 54 | $event = Event::where('_id', $id) 55 | ->delete(); 56 | return $event; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Http/Controllers/GoalController.php: -------------------------------------------------------------------------------- 1 | $in->title, 19 | 'workspace_id' => $in->workspace_id, 20 | 'team_id' => $in->team_id, 21 | ]); 22 | 23 | return $goal; 24 | } 25 | 26 | public function update(Request $in, $id) 27 | { 28 | $params = [ 29 | 'title' => $in->title, 30 | 'workspace_id' => $in->workspace_id, 31 | 'team_id' => $in->team_id, 32 | ]; 33 | 34 | $goal = Goal::where('_id', $id); 35 | $goal->update($params); 36 | 37 | return $goal->get()->first(); 38 | } 39 | 40 | public function destroy($id) 41 | { 42 | $goal = Goal::where('_id', $id) 43 | ->delete(); 44 | return $goal; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Controllers/LabelController.php: -------------------------------------------------------------------------------- 1 | get(); 16 | } 17 | 18 | public function store(Request $request) 19 | { 20 | $data = $request->validate([ 21 | 'name' => 'required|string', 22 | 'text_color' => 'required|string', 23 | 'color' => 'required|string', 24 | 'workspace_id' => 'required|string', 25 | ]); 26 | 27 | $label = Label::create($data); 28 | 29 | return new LabelResource($label); 30 | } 31 | 32 | public function update(Request $request, Label $label) 33 | { 34 | $data = $request->validate([ 35 | 'name' => 'required|string', 36 | 'text_color' => 'required|string', 37 | 'color' => 'required|string', 38 | 'workspace_id' => 'required|string', 39 | ]); 40 | 41 | $label->update($data); 42 | 43 | return new LabelResource($label); 44 | } 45 | 46 | public function destroy(Label $label) 47 | { 48 | $label->delete(); 49 | 50 | return Response::json('Categoria excluída com sucesso!'); 51 | } 52 | 53 | public function getLabelsByWorkspaceId(Workspace $workspace) 54 | { 55 | return Label::where('workspace_id', $workspace->id)->orderBy('name')->get(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Http/Controllers/ProcessController.php: -------------------------------------------------------------------------------- 1 | validate([ 27 | 'name' => 'required|string', 28 | 'team_ids' => 'required|array', 29 | 'checklists' => 'required|array', 30 | ]); 31 | 32 | $process = Process::create($data); 33 | 34 | $process->company()->associate( 35 | auth()->user()->member->company_id 36 | ); 37 | 38 | $process->save(); 39 | 40 | return new ProcessResource($process); 41 | } 42 | 43 | public function update(Request $request, Process $process) 44 | { 45 | $data = $request->validate([ 46 | 'name' => 'required|string', 47 | 'team_ids' => 'required|array', 48 | 'checklists' => 'required|array', 49 | ]); 50 | 51 | $process->update($data); 52 | 53 | return new ProcessResource($process); 54 | } 55 | 56 | public function destroy(Process $process) 57 | { 58 | $process->delete(); 59 | 60 | return Response::json('Deletado com sucesso.', 200); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Http/Controllers/UserController.php: -------------------------------------------------------------------------------- 1 | validate([ 16 | 'user_id' => 'required|string', 17 | ]); 18 | 19 | $user = User::findOrFail($request->user_id); 20 | Mail::to($user->email)->send(new WelcomeMail($user)); 21 | 22 | return Response::json('E-mail enviado com sucesso!'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/CheckForMaintenanceMode.php: -------------------------------------------------------------------------------- 1 | company_id)) { 25 | $company_scope = new CompanyScope($request->company_id); 26 | Card::addGlobalScope($company_scope); 27 | Member::addGlobalScope($company_scope); 28 | Team::addGlobalScope($company_scope); 29 | Workspace::addGlobalScope($company_scope); 30 | Process::addGlobalScope($company_scope); 31 | } 32 | 33 | return $next($request); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | check()) { 22 | return redirect(RouteServiceProvider::HOME); 23 | } 24 | 25 | return $next($request); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Middleware/ResolveCompanyTenant.php: -------------------------------------------------------------------------------- 1 | parser = $parser; 21 | $this->tokenRepository = $tokenRepository; 22 | } 23 | 24 | public function handle(Request $request, Closure $next) 25 | { 26 | if(Auth::check()) { 27 | $company_id = auth()->user()->member->company_id; 28 | } 29 | 30 | if (!empty($request->bearerToken())) { 31 | $bearerToken = request()->bearerToken(); 32 | $tokenId = $this->parser->parse($bearerToken)->claims()->get('jti'); 33 | $token = $this->tokenRepository->find($tokenId); 34 | $company_id = CompanyClient::where('client_id', $token->client_id)->first()->company_id; 35 | } 36 | 37 | $request->merge(['company_id' => $company_id ?? null]); 38 | 39 | return $next($request); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | globalRules(), 19 | $this->getRulesByType() 20 | ); 21 | } 22 | 23 | protected function globalRules(): array 24 | { 25 | $types = CardTypes::allJoin(); 26 | 27 | return [ 28 | 'title' => 'required|string', 29 | 'type' => "required|in:$types", 30 | 'board_list_id' => 'required|string|exists:board_lists,_id', 31 | 'user_story_id' => 'nullable|string', 32 | 'members' => 'nullable|array', 33 | 'team_id' => 'nullable|string', 34 | 'board_id' => 'nullable|string', 35 | 'labels' => 'nullable|string', 36 | 'link' => 'nullable|string', 37 | 'workspace_id' => 'nullable|string', 38 | 'checklist' => 'nullable|array', 39 | ]; 40 | } 41 | 42 | protected function getRulesByType(): array 43 | { 44 | if ($this->type === CardTypes::NOT_PRIORITIZED) { 45 | return $this->notPrioritizedRules(); 46 | } 47 | 48 | if ($this->type === CardTypes::USER_STORY) { 49 | return $this->userStoryRules(); 50 | } 51 | 52 | return []; 53 | } 54 | 55 | protected function notPrioritizedRules(): array 56 | { 57 | return [ 58 | 'rating' => 'nullable|numeric|min:0|max:5', 59 | 'description' => 'nullable|string', 60 | 'backlog_labels' => 'nullable|string', 61 | ]; 62 | } 63 | 64 | protected function userStoryRules(): array 65 | { 66 | return [ 67 | 'bimester_goal' => 'nullable|boolean', 68 | 'is_recurrent' => 'nullable|boolean', 69 | 'backlog_labels' => 'nullable|string', 70 | 'milestone_id' => 'nullable|string', 71 | ]; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/Http/Resources/BacklogLabelResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'name' => $this->name, 20 | 'text_color' => $this->text_color, 21 | 'color' => $this->color, 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Resources/LabelResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'name' => $this->name, 20 | 'text_color' => $this->text_color, 21 | 'color' => $this->color, 22 | 'workspace_id' => $this->workspace_id, 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Resources/MemberResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'name' => $this->name, 20 | 'email' => $this->email, 21 | 'full_email' => $this->full_email, 22 | 'has_login' => $this->has_login, 23 | 'user_id' => optional($this->user)->id, 24 | 'avatar_url' => $this->avatar_url, 25 | 'teams' => implode(', ', $this->getTeams()->pluck('name')->toArray()), 26 | 'team_ids' => $this->team_ids, 27 | 'workspace_ids' => $this->workspace_ids, 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/Http/Resources/MilestoneResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'title' => $this->title, 20 | 'description' => $this->description, 21 | 'start_date' => $this->start_date, 22 | 'end_date' => $this->end_date, 23 | 'team_ids' => $this->when(isset($this->team_ids), $this->team_ids), 24 | 'acceptance_criteria' => $this->when(isset($this->acceptance_criteria), $this->acceptance_criteria), 25 | 'status' => $this->status, 26 | 'closed' => $this->closed, 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Http/Resources/ProcessResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'name' => $this->name, 20 | 'team_ids' => $this->team_ids, 21 | 'checklists' => (array) $this->checklists, 22 | ]; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Http/Resources/SprintReportResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'started_at' => $this->started_at, 20 | 'finished_at' => $this->finished_at, 21 | 'members' => $this->members, 22 | 'notes' => $this->notes, 23 | 'impediments_quantity' => $this->impediments_quantity, 24 | 'cards' => $this->cards, 25 | ]; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Resources/TeamResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'name' => $this->name, 20 | 'extended_task_flow' => $this->extended_task_flow, 21 | 'key' => $this->key, 22 | 'workspace_id' => $this->workspace_id, 23 | 'board_lists' => $this->board_lists, 24 | ]; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Http/Resources/WorkspaceResource.php: -------------------------------------------------------------------------------- 1 | $this->id, 19 | 'name' => $this->name, 20 | 'team_ids' => $this->team_ids, 21 | 'label_ids' => $this->label_ids, 22 | 'team_names' => $this->team_names, 23 | 'lottie_file' => $this->lottie_file, 24 | 'settings' => (object) $this->settings, 25 | 'inactive' => $this->inactive ?? false, 26 | 'status' => $this->inactive ? 'Desativado' : '', 27 | ]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Library/Utils/BagOfConstants.php: -------------------------------------------------------------------------------- 1 | getConstants()); 12 | 13 | return array_filter($constant_values, function ($value) { 14 | return !is_array($value) && !is_object($value); 15 | }); 16 | } 17 | 18 | public static function allJoin() 19 | { 20 | return implode(',', static::all()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Listeners/SendWelcomingEmail.php: -------------------------------------------------------------------------------- 1 | user->email)->send(new WelcomeMail($event->user)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/Mail/WelcomeMail.php: -------------------------------------------------------------------------------- 1 | user = $user; 26 | } 27 | 28 | /** 29 | * Build the message. 30 | 31 | * @return $this 32 | */ 33 | public function build() 34 | { 35 | $first_name = explode(' ', $this->user->name)[0]; 36 | 37 | $token = app('auth.password.broker')->createToken($this->user); 38 | 39 | $url = route('password.reset', [ 40 | 'token' => $token, 41 | 'email' => $this->user->email, 42 | ]); 43 | 44 | return $this 45 | ->subject('Bem vindo ao Trelássio!') 46 | ->markdown('vendor.emails.welcome', [ 47 | 'url' => $url, 48 | 'name' => $first_name 49 | ]); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/Models/BacklogLabel.php: -------------------------------------------------------------------------------- 1 | hasMany('App\Models\BoardList'); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/Models/BoardList.php: -------------------------------------------------------------------------------- 1 | orderBy('position'); 33 | }); 34 | } 35 | 36 | public function cards() 37 | { 38 | return $this->hasMany('App\Models\Card'); 39 | } 40 | 41 | public function team() 42 | { 43 | return $this->belongsTo('App\Models\Team'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Models/Company.php: -------------------------------------------------------------------------------- 1 | hasMany(User::class); 30 | } 31 | 32 | public function workspace() 33 | { 34 | return $this->hasMany(Workspace::class); 35 | } 36 | 37 | public function teams() 38 | { 39 | return $this->hasMany(Team::class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Models/CompanyClient.php: -------------------------------------------------------------------------------- 1 | belongsTo(Company::class); 22 | } 23 | 24 | public function client(): BelongsTo 25 | { 26 | return $this->belongsTo(Client::class); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Models/Event.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Models\Team'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Models/Goal.php: -------------------------------------------------------------------------------- 1 | belongsTo('App\Models\Team'); 23 | } 24 | 25 | public function workspace() 26 | { 27 | return $this->belongsTo('App\Models\Workspace'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Models/Label.php: -------------------------------------------------------------------------------- 1 | belongsTo(Workspace::class); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/Models/Member.php: -------------------------------------------------------------------------------- 1 | hasOne(User::class, 'member_id'); 26 | } 27 | 28 | public function company() 29 | { 30 | return $this->belongsTo(Company::class); 31 | } 32 | 33 | public function getFullEmailAttribute() 34 | { 35 | return optional($this->user)->email ?? ''; 36 | } 37 | 38 | public function getEmailAttribute() 39 | { 40 | return $this->full_email; 41 | } 42 | 43 | public function getHasLoginAttribute() 44 | { 45 | return !!optional($this->user)->configured_password; 46 | } 47 | 48 | public function teamMembers() 49 | { 50 | return $this->hasMany(TeamMember::class); 51 | } 52 | 53 | public function getTeams() 54 | { 55 | return $this->teamMembers->map(function ($pivot) { 56 | return $pivot->team; 57 | }); 58 | } 59 | 60 | public function getTeamIdsAttribute() 61 | { 62 | return $this->getTeams()->pluck('id')->toArray(); 63 | } 64 | 65 | public function getWorkspaceIdsAttribute() 66 | { 67 | return $this->getTeams()->pluck('workspace_id')->toArray(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/Models/Milestone.php: -------------------------------------------------------------------------------- 1 | hasMany(Team::class); 26 | } 27 | 28 | public function company() 29 | { 30 | return $this->belongsTo(Company::class); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Models/Scopes/CompanyScope.php: -------------------------------------------------------------------------------- 1 | company_id = $company_id; 16 | } 17 | 18 | public function apply(Builder $builder, Model $model): void 19 | { 20 | if ($this->company_id) { 21 | $builder->where('company_id', $this->company_id); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/Models/SprintReport.php: -------------------------------------------------------------------------------- 1 | belongsTo(Team::class); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Models/Team.php: -------------------------------------------------------------------------------- 1 | hasMany(Card::class); 29 | } 30 | 31 | public function workspace() 32 | { 33 | return $this->belongsTo(Workspace::class); 34 | } 35 | 36 | public function company() 37 | { 38 | return $this->belongsTo(Company::class); 39 | } 40 | 41 | public function board_lists() 42 | { 43 | return $this->hasMany(BoardList::class); 44 | } 45 | 46 | public function syncBoardLists($from_request) 47 | { 48 | 49 | $current = $this->board_lists; 50 | 51 | $add = array_filter($from_request, function ($item) { 52 | return empty($item['id']); 53 | }); 54 | 55 | $update = array_filter($from_request, function ($item) { 56 | return !empty($item['id']); 57 | }); 58 | 59 | $remove = array_diff( 60 | $current->pluck('id')->toArray(), 61 | collect($from_request)->pluck('id')->toArray() 62 | ); 63 | 64 | 65 | if (empty($add) && empty($remove) && empty($update)) { 66 | return; 67 | } 68 | 69 | $board_list_service = new BoardListService(); 70 | $board_list_service->createMany($add, $this->id); 71 | $board_list_service->deleteMany($remove); 72 | $board_list_service->updateMany($update); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/Models/TeamMember.php: -------------------------------------------------------------------------------- 1 | belongsTo(Team::class); 22 | } 23 | 24 | public function member() 25 | { 26 | return $this->belongsTo(Member::class); 27 | } 28 | 29 | public function company() 30 | { 31 | return $this->belongsTo(Company::class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Observers/CardObserver.php: -------------------------------------------------------------------------------- 1 | filled('company_id')) { 13 | $card->company()->associate(request()->company_id); 14 | $card->user_id = auth()->check() ? Auth::user()->id : null; 15 | } 16 | 17 | $card->number = (Card::withTrashed()->max('number') ?: 0) + 1; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/Observers/TeamObserver.php: -------------------------------------------------------------------------------- 1 | key = $team->id; 22 | $team->save(); 23 | 24 | BoardList::create([ 25 | 'name' => 'Sprint - '. $team->name, 26 | 'key' => $team->id, 27 | 'user_story_holder' => true, 28 | 'accepts_card_type' => CardTypes::USER_STORY, 29 | 'is_goalable' => true, 30 | 'position' => 3, 31 | ]); 32 | 33 | Goal::create([ 34 | 'title' => 'Defina um objetivo', 35 | 'team_id' => $team->id, 36 | ]); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->singleton(FakerGenerator::class, function () { 24 | return FakerFactory::create('pt_BR'); 25 | }); 26 | } 27 | 28 | /** 29 | * Bootstrap any application services. 30 | * 31 | * @return void 32 | */ 33 | public function boot() 34 | { 35 | JsonResource::withoutWrapping(); 36 | Card::observe(CardObserver::class); 37 | Team::observe(TeamObserver::class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Providers/AuthServiceProvider.php: -------------------------------------------------------------------------------- 1 | 'App\Policies\ModelPolicy', 18 | ]; 19 | 20 | /** 21 | * Register any authentication / authorization services. 22 | * 23 | * @return void 24 | */ 25 | public function boot() 26 | { 27 | $this->registerPolicies(); 28 | Passport::routes(); 29 | // 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 23 | SendEmailVerificationNotification::class, 24 | ], 25 | 'App\Events\UserRegisteredEvent' => [ 26 | 'App\Listeners\SendWelcomingEmail', 27 | ], 28 | ]; 29 | 30 | /** 31 | * Register any events for your application. 32 | * 33 | * @return void 34 | */ 35 | public function boot() 36 | { 37 | parent::boot(); 38 | // 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 46 | 47 | $this->mapWebRoutes(); 48 | 49 | // 50 | } 51 | 52 | /** 53 | * Define the "web" routes for the application. 54 | * 55 | * These routes all receive session state, CSRF protection, etc. 56 | * 57 | * @return void 58 | */ 59 | protected function mapWebRoutes() 60 | { 61 | Route::middleware('web') 62 | ->namespace($this->namespace) 63 | ->group(base_path('routes/web.php')); 64 | } 65 | 66 | /** 67 | * Define the "api" routes for the application. 68 | * 69 | * These routes are typically stateless. 70 | * 71 | * @return void 72 | */ 73 | protected function mapApiRoutes() 74 | { 75 | Route::prefix('api') 76 | ->middleware('api') 77 | ->namespace($this->namespace) 78 | ->group(base_path('routes/api.php')); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/Services/CardService.php: -------------------------------------------------------------------------------- 1 | first()->id; 20 | 21 | $cards = Card::where('board_list_id', $sprintListId) 22 | ->where('type', CardTypes::USER_STORY) 23 | ->orderBy('position') 24 | ->get(); 25 | 26 | return CardResource::collection($cards); 27 | } 28 | 29 | public function getTasksFromUserStory($user_story_id, $team_board_lists) 30 | { 31 | $cards = Card::whereIn('board_list_id', $team_board_lists) 32 | ->where('user_story_id', $user_story_id) 33 | ->orderBy('position') 34 | ->get(); 35 | 36 | return CardResource::collection($cards)->groupBy('board_list_id'); 37 | } 38 | 39 | public function getTasksFromBoard($board_id, $team_board_lists) 40 | { 41 | $cards = Card::whereIn('board_list_id', $team_board_lists) 42 | ->where('board_id', $board_id) 43 | ->orderBy('position') 44 | ->get(); 45 | 46 | return CardResource::collection($cards)->groupBy('board_list_id'); 47 | } 48 | 49 | public function getCardsByBoardListsIds($team_board_lists) 50 | { 51 | $cards = Card::whereIn('board_list_id', $team_board_lists) 52 | ->orderBy('position') 53 | ->get(); 54 | 55 | return CardResource::collection($cards)->groupBy('board_list_id'); 56 | } 57 | 58 | public function deleteMany($cards_ids) 59 | { 60 | if(empty($cards_ids)) return; 61 | 62 | foreach ($cards_ids as $id) { 63 | $find = Card::find($id); 64 | if($find) { 65 | $find->delete(); 66 | } 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/Services/WorkspaceService.php: -------------------------------------------------------------------------------- 1 | orWhereNull('inactive') 18 | ->get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/User.php: -------------------------------------------------------------------------------- 1 | UserRegisteredEvent::class, 18 | ]; 19 | 20 | /** 21 | * The attributes that are mass assignable. 22 | * 23 | * @var array 24 | */ 25 | protected $fillable = [ 26 | 'name', 27 | 'email', 28 | 'password', 29 | 'member_id', 30 | 'configured_password', 31 | ]; 32 | 33 | /** 34 | * The attributes that should be hidden for arrays. 35 | * 36 | * @var array 37 | */ 38 | protected $hidden = [ 39 | 'password', 'remember_token', 40 | ]; 41 | 42 | /** 43 | * The attributes that should be cast to native types. 44 | * 45 | * @var array 46 | */ 47 | protected $casts = [ 48 | 'email_verified_at' => 'datetime', 49 | ]; 50 | 51 | public function member() 52 | { 53 | return $this->belongsTo(Member::class); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /artisan: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | make(Illuminate\Contracts\Console\Kernel::class); 34 | 35 | $status = $kernel->handle( 36 | $input = new Symfony\Component\Console\Input\ArgvInput, 37 | new Symfony\Component\Console\Output\ConsoleOutput 38 | ); 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Shutdown The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once Artisan has finished running, we will fire off the shutdown events 46 | | so that any final work may be done by the application before we shut 47 | | down the process. This is the last thing to happen to the request. 48 | | 49 | */ 50 | 51 | $kernel->terminate($input, $status); 52 | 53 | exit($status); 54 | -------------------------------------------------------------------------------- /bootstrap/app.php: -------------------------------------------------------------------------------- 1 | singleton( 30 | Illuminate\Contracts\Http\Kernel::class, 31 | App\Http\Kernel::class 32 | ); 33 | 34 | $app->singleton( 35 | Illuminate\Contracts\Console\Kernel::class, 36 | App\Console\Kernel::class 37 | ); 38 | 39 | $app->singleton( 40 | Illuminate\Contracts\Debug\ExceptionHandler::class, 41 | App\Exceptions\Handler::class 42 | ); 43 | 44 | /* 45 | |-------------------------------------------------------------------------- 46 | | Return The Application 47 | |-------------------------------------------------------------------------- 48 | | 49 | | This script returns the application instance. The instance is given to 50 | | the calling script so we can separate the building of the instances 51 | | from the actual running of the application and sending responses. 52 | | 53 | */ 54 | 55 | return $app; 56 | -------------------------------------------------------------------------------- /bootstrap/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /config/broadcasting.php: -------------------------------------------------------------------------------- 1 | env('BROADCAST_DRIVER', 'null'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Broadcast Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may define all of the broadcast connections that will be used 26 | | to broadcast events to other systems or over websockets. Samples of 27 | | each available type of connection are provided inside this array. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'pusher' => [ 34 | 'driver' => 'pusher', 35 | 'key' => env('PUSHER_APP_KEY'), 36 | 'secret' => env('PUSHER_APP_SECRET'), 37 | 'app_id' => env('PUSHER_APP_ID'), 38 | 'options' => [ 39 | 'cluster' => env('PUSHER_APP_CLUSTER'), 40 | 'useTLS' => true, 41 | ], 42 | ], 43 | 44 | 'redis' => [ 45 | 'driver' => 'redis', 46 | 'connection' => 'default', 47 | ], 48 | 49 | 'log' => [ 50 | 'driver' => 'log', 51 | ], 52 | 53 | 'null' => [ 54 | 'driver' => 'null', 55 | ], 56 | 57 | ], 58 | 59 | ]; 60 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Bcrypt Options 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may specify the configuration options that should be used when 26 | | passwords are hashed using the Bcrypt algorithm. This will allow you 27 | | to control the amount of time it takes to hash the given password. 28 | | 29 | */ 30 | 31 | 'bcrypt' => [ 32 | 'rounds' => env('BCRYPT_ROUNDS', 10), 33 | ], 34 | 35 | /* 36 | |-------------------------------------------------------------------------- 37 | | Argon Options 38 | |-------------------------------------------------------------------------- 39 | | 40 | | Here you may specify the configuration options that should be used when 41 | | passwords are hashed using the Argon algorithm. These will allow you 42 | | to control the amount of time it takes to hash the given password. 43 | | 44 | */ 45 | 46 | 'argon' => [ 47 | 'memory' => 1024, 48 | 'threads' => 2, 49 | 'time' => 2, 50 | ], 51 | 52 | ]; 53 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => env('MAILGUN_DOMAIN'), 19 | 'secret' => env('MAILGUN_SECRET'), 20 | 'endpoint' => env('MAILGUN_ENDPOINT', 'api.mailgun.net'), 21 | ], 22 | 23 | 'postmark' => [ 24 | 'token' => env('POSTMARK_TOKEN'), 25 | ], 26 | 27 | 'ses' => [ 28 | 'key' => env('AWS_ACCESS_KEY_ID'), 29 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 30 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 31 | ], 32 | 33 | 'sparkpost' => [ 34 | 'secret' => env('SPARKPOST_SECRET'), 35 | ], 36 | 37 | ]; 38 | -------------------------------------------------------------------------------- /config/view.php: -------------------------------------------------------------------------------- 1 | [ 17 | resource_path('views'), 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Compiled View Path 23 | |-------------------------------------------------------------------------- 24 | | 25 | | This option determines where all the compiled Blade templates will be 26 | | stored for your application. Typically, this is within the storage 27 | | directory. However, as usual, you are free to change this value. 28 | | 29 | */ 30 | 31 | 'compiled' => env( 32 | 'VIEW_COMPILED_PATH', 33 | realpath(storage_path('framework/views')) 34 | ), 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /database/.gitignore: -------------------------------------------------------------------------------- 1 | *.sqlite 2 | *.sqlite-journal 3 | -------------------------------------------------------------------------------- /database/factories/BoardListFactory.php: -------------------------------------------------------------------------------- 1 | define(BoardList::class, function (Faker $faker) { 9 | return [ 10 | 'name' => $faker->name, 11 | 'position' => $faker->numberBetween(1, 10), 12 | 'key' => $faker->md5, 13 | 'user_story_holder' => $faker->name, 14 | 'accepts_card_type' => [], 15 | 'team_id' => $faker->md5, 16 | 'is_devlog' => $faker->boolean(50), 17 | 'is_goalable' => $faker->boolean(50), 18 | ]; 19 | }); 20 | -------------------------------------------------------------------------------- /database/factories/CardFactory.php: -------------------------------------------------------------------------------- 1 | define(Card::class, function (Faker $faker) { 10 | return [ 11 | 'number' => $faker->numberBetween(1, 100), 12 | 'title' => $faker->sentence(), 13 | 'link' => $faker->url(), 14 | 'position' => $faker->numberBetween(1, 100), 15 | 'type' => 'user-story', 16 | ]; 17 | }); 18 | 19 | $factory->state(Card::class, 'with-board', function () { 20 | return [ 21 | 'board_list_id' => factory(BoardList::class) 22 | ->create() 23 | ->id, 24 | ]; 25 | }); 26 | -------------------------------------------------------------------------------- /database/factories/CompanyFactory.php: -------------------------------------------------------------------------------- 1 | define(Company::class, function (Faker $faker) { 9 | return [ 10 | 'cnpj' => $faker->regexify('[0-9]{15}'), 11 | 'name' => $faker->name(), 12 | 'phone' => $faker->phoneNumber(), 13 | 'email' => $faker->email(), 14 | ]; 15 | }); 16 | -------------------------------------------------------------------------------- /database/factories/MemberFactory.php: -------------------------------------------------------------------------------- 1 | define(Member::class, function (Faker $faker) { 10 | return [ 11 | 'name' => $faker->name(), 12 | 'avatar_url' => $faker->url(), 13 | ]; 14 | }); 15 | 16 | $factory->state(Member::class, 'with-company', function () { 17 | return [ 18 | 'company_id' => factory(Company::class)->create()->id, 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /database/factories/TeamFactory.php: -------------------------------------------------------------------------------- 1 | define(Team::class, function (Faker $faker) { 10 | return [ 11 | 'name' => $faker->name(), 12 | 'board_lists' => [1,2], 13 | 'board_lists.0.name' => $faker->name(), 14 | 'board_lists.1.name' => $faker->name(), 15 | 'board_lists.0.position' => $faker->numberBetween(1,9), 16 | 'board_lists.1.position' => $faker->numberBetween(1,9), 17 | 'lottie_file' => $faker->url(), 18 | ]; 19 | }); 20 | 21 | $factory->state(Team::class, 'with-company', function() { 22 | return [ 23 | 'company_id' => factory(Company::class)->create()->id, 24 | ]; 25 | }); 26 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(User::class, function (Faker $faker) { 23 | return [ 24 | 'name' => $faker->name, 25 | 'email' => $faker->unique()->safeEmail, 26 | 'email_verified_at' => now(), 27 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 28 | 'remember_token' => Str::random(10), 29 | 'active' => true, 30 | 'type' => 'A', 31 | ]; 32 | }); 33 | 34 | $factory->state(User::class, 'with-member', function () { 35 | return [ 36 | 'member_id' => factory(Member::class) 37 | ->create() 38 | ->id, 39 | ]; 40 | }); 41 | 42 | $factory->state(User::class, 'with-member-company', function () { 43 | return [ 44 | 'member_id' => factory(Member::class) 45 | ->state('with-company') 46 | ->create() 47 | ->id, 48 | ]; 49 | }); 50 | -------------------------------------------------------------------------------- /database/factories/WorkspaceFactory.php: -------------------------------------------------------------------------------- 1 | define(Workspace::class, function (Faker $faker) { 10 | return [ 11 | 'name' => $faker->name(), 12 | 'lottie_file' => $faker->url(), 13 | ]; 14 | }); 15 | 16 | $factory->state(Workspace::class, 'with-company', function() { 17 | return [ 18 | 'company_id' => factory(Company::class)->create()->id, 19 | ]; 20 | }); 21 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | bigIncrements('id'); 18 | $table->string('name'); 19 | $table->string('email')->unique(); 20 | $table->timestamp('email_verified_at')->nullable(); 21 | $table->string('password'); 22 | $table->rememberToken(); 23 | $table->timestamps(); 24 | }); 25 | } 26 | 27 | /** 28 | * Reverse the migrations. 29 | * 30 | * @return void 31 | */ 32 | public function down() 33 | { 34 | Schema::dropIfExists('users'); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token'); 19 | $table->timestamp('created_at')->nullable(); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::dropIfExists('password_resets'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call([]); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | board-server: 5 | image: elaynelemos/ubuntu-20.04-laravel-6.x:node-12.x-php-7.4-apache-2 6 | volumes: 7 | - ./:/home/board/ 8 | ports: 9 | - 8090:80 10 | stdin_open: true 11 | tty: true 12 | entrypoint: 13 | - ".docker/bin/entrypoint.sh" 14 | - bash 15 | working_dir: /home/board/ 16 | mem_limit: 2g 17 | depends_on: 18 | - mongo 19 | 20 | mongo: 21 | image: mongo:4.2 22 | volumes: 23 | - './.docker/mongo/data:/data/db:z' 24 | ports: 25 | - '27003:27017' 26 | 27 | volumes: 28 | mongodb_data: 29 | driver: local 30 | server_data: 31 | driver: local 32 | -------------------------------------------------------------------------------- /init-infra.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | EXIT_FAILURE=1 6 | EXIT_SUCCESS=0 7 | 8 | virtual_env="${APPLICATION_IMAGES_VIRTUAL_ENV:-.virtualenv}" 9 | 10 | pip3 install virtualenv 11 | virtualenv -p python3 $virtual_env 12 | source $virtual_env/bin/activate 13 | pip install --upgrade pip 14 | pip install -r requirements.txt 15 | 16 | exit $EXIT_SUCCESS 17 | -------------------------------------------------------------------------------- /mongo_indexs.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sysvale/board/8cad9124064683250de0dcc312a1a296d29e8cdc/mongo_indexs.js -------------------------------------------------------------------------------- /on-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose exec board-server $* 4 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | A custom coding standard 10 | 11 | 17 | 18 | 19 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 0 36 | 37 | 38 | 39 | */database/* 40 | 41 | 42 | vendor 43 | 44 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/Unit 14 | 15 | 16 | 17 | ./tests/Feature 18 | 19 | 20 | 21 | 22 | ./app 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews -Indexes 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Handle Authorization Header 9 | RewriteCond %{HTTP:Authorization} . 10 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 11 | 12 | # Redirect Trailing Slashes If Not A Folder... 13 | RewriteCond %{REQUEST_FILENAME} !-d 14 | RewriteCond %{REQUEST_URI} (.+)/$ 15 | RewriteRule ^ %1 [L,R=301] 16 | 17 | # Handle Front Controller... 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteCond %{REQUEST_FILENAME} !-f 20 | RewriteRule ^ index.php [L] 21 | 22 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sysvale/board/8cad9124064683250de0dcc312a1a296d29e8cdc/public/favicon.ico -------------------------------------------------------------------------------- /public/images/christmas-divider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sysvale/board/8cad9124064683250de0dcc312a1a296d29e8cdc/public/images/christmas-divider.png -------------------------------------------------------------------------------- /public/images/elofy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sysvale/board/8cad9124064683250de0dcc312a1a296d29e8cdc/public/images/elofy.png -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sysvale/board/8cad9124064683250de0dcc312a1a296d29e8cdc/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/logo-natal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sysvale/board/8cad9124064683250de0dcc312a1a296d29e8cdc/public/images/logo-natal.png -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | define('LARAVEL_START', microtime(true)); 11 | 12 | /* 13 | |-------------------------------------------------------------------------- 14 | | Register The Auto Loader 15 | |-------------------------------------------------------------------------- 16 | | 17 | | Composer provides a convenient, automatically generated class loader for 18 | | our application. We just need to utilize it! We'll simply require it 19 | | into the script here so that we don't have to worry about manual 20 | | loading any of our classes later on. It feels great to relax. 21 | | 22 | */ 23 | 24 | require __DIR__.'/../vendor/autoload.php'; 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Turn On The Lights 29 | |-------------------------------------------------------------------------- 30 | | 31 | | We need to illuminate PHP development, so let us turn on the lights. 32 | | This bootstraps the framework and gets it ready for use, then it 33 | | will load up this application so that we can run it and send 34 | | the responses back to the browser and delight our users. 35 | | 36 | */ 37 | 38 | $app = require_once __DIR__.'/../bootstrap/app.php'; 39 | 40 | /* 41 | |-------------------------------------------------------------------------- 42 | | Run The Application 43 | |-------------------------------------------------------------------------- 44 | | 45 | | Once we have the application, we can handle the incoming request 46 | | through the kernel, and send the associated response back to 47 | | the client's browser allowing them to enjoy the creative 48 | | and wonderful application we have prepared for them. 49 | | 50 | */ 51 | 52 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 53 | 54 | $response = $kernel->handle( 55 | $request = Illuminate\Http\Request::capture() 56 | ); 57 | 58 | $response->send(); 59 | 60 | $kernel->terminate($request, $response); 61 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | ansible==2.9.13 2 | ansible-lint==4.3.5 3 | arrow==0.16.0 4 | bcrypt==3.2.0 5 | binaryornot==0.4.4 6 | Cerberus==1.3.2 7 | certifi==2020.6.20 8 | cffi==1.14.3 9 | chardet==3.0.4 10 | click==7.1.2 11 | click-completion==0.5.2 12 | click-help-colors==0.8 13 | colorama==0.4.3 14 | commonmark==0.9.1 15 | cookiecutter==1.7.2 16 | cryptography==3.1 17 | distro==1.5.0 18 | docker==4.3.1 19 | fasteners==0.15 20 | idna==2.10 21 | Jinja2==2.11.2 22 | jinja2-time==0.2.0 23 | MarkupSafe==1.1.1 24 | molecule==3.0.8 25 | monotonic==1.5 26 | paramiko==2.7.2 27 | pathspec==0.8.0 28 | pexpect==4.8.0 29 | pluggy==0.13.1 30 | poyo==0.5.0 31 | ptyprocess==0.6.0 32 | pycparser==2.20 33 | Pygments==2.7.1 34 | PyNaCl==1.4.0 35 | python-dateutil==2.8.1 36 | python-gilt==1.2.3 37 | python-slugify==4.0.1 38 | PyYAML==5.3.1 39 | requests==2.24.0 40 | rich==7.0.0 41 | ruamel.yaml==0.16.12 42 | ruamel.yaml.clib==0.2.2 43 | selinux==0.2.1 44 | sh==1.13.1 45 | shellingham==1.3.2 46 | six==1.15.0 47 | tabulate==0.8.7 48 | text-unidecode==1.3 49 | tree-format==0.1.2 50 | typing-extensions==3.7.4.3 51 | urllib3==1.25.10 52 | websocket-client==0.57.0 53 | yamllint==1.24.2 54 | -------------------------------------------------------------------------------- /resources/js/core/constants/milestoneStatuses.js: -------------------------------------------------------------------------------- 1 | export const dictionary = { 2 | INCOMPLETE: 'incomplete', 3 | IN_PROGRESS: 'in-progress', 4 | DONE: 'done', 5 | CANCELED: 'canceled', 6 | NOT_STARTED: 'not-started', 7 | }; 8 | 9 | export const options = [ 10 | { 11 | text: 'Em andamento', 12 | value: dictionary.IN_PROGRESS, 13 | }, 14 | { 15 | text: 'Concluído', 16 | value: dictionary.DONE, 17 | }, 18 | { 19 | text: 'Incompleto', 20 | value: dictionary.INCOMPLETE, 21 | }, 22 | { 23 | text: 'Cancelado', 24 | value: dictionary.CANCELED, 25 | }, 26 | { 27 | text: 'Não iniciado', 28 | value: dictionary.NOT_STARTED, 29 | }, 30 | ]; 31 | -------------------------------------------------------------------------------- /resources/js/core/constants/userStoryStatuses.js: -------------------------------------------------------------------------------- 1 | export const dictionary = { 2 | IN_PROGRESS: 'in-progress', 3 | DONE: 'done', 4 | CANCELED: 'canceled', 5 | NOT_STARTED: 'not-started', 6 | }; 7 | 8 | export const options = [ 9 | { 10 | text: 'Em andamento', 11 | value: dictionary.IN_PROGRESS, 12 | }, 13 | { 14 | text: 'Feito', 15 | value: dictionary.DONE, 16 | }, 17 | { 18 | text: 'Cancelado', 19 | value: dictionary.CANCELED, 20 | }, 21 | { 22 | text: 'Não iniciado', 23 | value: dictionary.NOT_STARTED, 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /resources/js/core/utils/convertKeysToCamelCase.js: -------------------------------------------------------------------------------- 1 | const convertKeysToCamelCase = (data) => { 2 | if (_.isArray(data)) { 3 | return data.map((element) => { 4 | if (_.isObject(element) || _.isArray(element)) { 5 | return convertKeysToCamelCase(element); 6 | } 7 | return element; 8 | }); 9 | } 10 | const newData = {}; 11 | Object.keys(data).forEach((key) => { 12 | if (_.isObject(data[key]) || _.isArray(data[key])) { 13 | newData[_.camelCase(key)] = convertKeysToCamelCase(data[key]); 14 | } else { 15 | newData[_.camelCase(key)] = data[key]; 16 | } 17 | }); 18 | 19 | return newData; 20 | }; 21 | 22 | export default convertKeysToCamelCase; 23 | -------------------------------------------------------------------------------- /resources/js/core/utils/convertKeysToSnakeCase.js: -------------------------------------------------------------------------------- 1 | const convertKeysToSnakeCase = (data) => { 2 | if (_.isArray(data)) { 3 | return data.map((element) => { 4 | if (_.isObject(element) || _.isArray(element)) { 5 | return convertKeysToSnakeCase(element); 6 | } 7 | return element; 8 | }); 9 | } 10 | const newData = {}; 11 | Object.keys(data).forEach((key) => { 12 | if (_.isObject(data[key]) || _.isArray(data[key])) { 13 | newData[_.snakeCase(key)] = convertKeysToSnakeCase(data[key]); 14 | } else { 15 | newData[_.snakeCase(key)] = data[key]; 16 | } 17 | }); 18 | 19 | return newData; 20 | }; 21 | 22 | export default convertKeysToSnakeCase; 23 | -------------------------------------------------------------------------------- /resources/js/core/utils/generateUUID.js: -------------------------------------------------------------------------------- 1 | const generateUUID = () => { // Public Domain/MIT 2 | var d = new Date().getTime();//Timestamp 3 | var d2 = (performance && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported 4 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 5 | var r = Math.random() * 16;//random number between 0 and 16 6 | if(d > 0){//Use timestamp until depleted 7 | r = (d + r)%16 | 0; 8 | d = Math.floor(d/16); 9 | } else {//Use microseconds since page-load if supported 10 | r = (d2 + r)%16 | 0; 11 | d2 = Math.floor(d2/16); 12 | } 13 | return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16); 14 | }); 15 | }; 16 | 17 | export default generateUUID; 18 | -------------------------------------------------------------------------------- /resources/js/core/utils/makeCrudServices.js: -------------------------------------------------------------------------------- 1 | import convertKeysToSnakeCase from './convertKeysToSnakeCase'; 2 | 3 | export default function makeCrudServices(entity, apiPath) { 4 | return { 5 | apiPath: `/api/${apiPath}`, 6 | httpClient: axios, 7 | 8 | [`get${_.upperFirst(_.camelCase(entity))}`](payload = {}) { 9 | payload = convertKeysToSnakeCase(payload); 10 | 11 | return axios.get(`/api/${apiPath}`, { params: payload }) 12 | .then(response => response.data); 13 | }, 14 | 15 | [`read${_.upperFirst(_.camelCase(entity))}`](id) { 16 | return axios.get(`/api/${apiPath}/${id}`) 17 | .then(response => response.data); 18 | }, 19 | 20 | [`post${_.upperFirst(_.camelCase(entity))}`](payload) { 21 | payload = convertKeysToSnakeCase(payload); 22 | 23 | return axios.post(`/api/${apiPath}`, payload) 24 | .then(response => response.data); 25 | }, 26 | 27 | [`put${_.upperFirst(_.camelCase(entity))}`]({ id, ...payload }) { 28 | payload = convertKeysToSnakeCase(payload); 29 | 30 | return axios.put(`/api/${apiPath}/${id}`, payload) 31 | .then(response => response.data); 32 | }, 33 | 34 | [`delete${_.upperFirst(_.camelCase(entity))}`](id) { 35 | return axios.delete(`/api/${apiPath}/${id}`) 36 | .then(response => response.data); 37 | }, 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /resources/js/core/utils/makeFormFields.js: -------------------------------------------------------------------------------- 1 | import upperCamelCase from './upperCamelCase'; 2 | 3 | export default (module, fields) => { 4 | let computed = {}; 5 | fields.forEach((field) => { 6 | computed = { 7 | ...computed, 8 | [field]: { 9 | get() { 10 | return this.$store.getters[`${module}/get${upperCamelCase(field)}`]; 11 | }, 12 | 13 | set(newValue) { 14 | this.$store.commit(`${module}/set${upperCamelCase(field)}`, newValue, { root: true }); 15 | }, 16 | }, 17 | }; 18 | }); 19 | return computed; 20 | }; 21 | -------------------------------------------------------------------------------- /resources/js/core/utils/makeMutations.js: -------------------------------------------------------------------------------- 1 | import { 2 | requestState, 3 | successState, 4 | failureState, 5 | } from './requestsStates'; 6 | 7 | function callbacksResolver() { 8 | return { 9 | request: (state) => { 10 | Object.assign(state, requestState); 11 | }, 12 | 13 | success: (state) => { 14 | Object.assign(state, successState); 15 | }, 16 | 17 | failure: (state, errors) => { 18 | Object.assign(state, failureState(errors)); 19 | }, 20 | }; 21 | } 22 | 23 | export default function makeMutations(requestName) { 24 | const { request, success, failure } = callbacksResolver(); 25 | 26 | return { 27 | [`${requestName}Request`]: request, 28 | [`${requestName}Success`]: success, 29 | [`${requestName}Failure`]: failure, 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /resources/js/core/utils/makeRequestStore.js: -------------------------------------------------------------------------------- 1 | import { initialState } from './requestsStates'; 2 | import makeMutations from './makeMutations'; 3 | 4 | export default (module, namespaced = false) => { 5 | const name = Object.keys(module)[0]; 6 | 7 | return { 8 | [name]: { 9 | namespaced, 10 | state: { ...initialState }, 11 | 12 | actions: { 13 | [name]({ commit }, params) { 14 | commit(`${name}Request`); 15 | 16 | return module[name](params) 17 | .then(({ data }) => { 18 | commit(`${name}Success`); 19 | return data; 20 | }) 21 | .catch(error => commit(`${name}Failure`, error)); 22 | }, 23 | }, 24 | 25 | mutations: { 26 | ...makeMutations(name), 27 | }, 28 | } 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /resources/js/core/utils/requestsStates/failureState.js: -------------------------------------------------------------------------------- 1 | import initialState from './initialState'; 2 | 3 | const failureState = errors => ({ 4 | ...initialState, 5 | isFetching: false, 6 | hasFailed: true, 7 | errors, 8 | }); 9 | 10 | export default failureState; 11 | -------------------------------------------------------------------------------- /resources/js/core/utils/requestsStates/index.js: -------------------------------------------------------------------------------- 1 | export { default as initialState } from './initialState'; 2 | export { default as requestState } from './requestState'; 3 | export { default as successState } from './successState'; 4 | export { default as failureState } from './failureState'; 5 | -------------------------------------------------------------------------------- /resources/js/core/utils/requestsStates/initialState.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | isFetching: false, 3 | hasFailed: false, 4 | hasSucceeded: false, 5 | errors: {}, 6 | }; 7 | 8 | export default initialState; 9 | -------------------------------------------------------------------------------- /resources/js/core/utils/requestsStates/requestState.js: -------------------------------------------------------------------------------- 1 | import initialState from './initialState'; 2 | 3 | const requestState = { 4 | ...initialState, 5 | isFetching: true, 6 | }; 7 | 8 | export default requestState; 9 | -------------------------------------------------------------------------------- /resources/js/core/utils/requestsStates/successState.js: -------------------------------------------------------------------------------- 1 | import initialState from './initialState'; 2 | 3 | const successState = { 4 | ...initialState, 5 | hasSucceeded: true, 6 | }; 7 | 8 | export default successState; 9 | -------------------------------------------------------------------------------- /resources/js/core/utils/upperCamelCase.js: -------------------------------------------------------------------------------- 1 | import lodash from 'lodash'; 2 | 3 | const upperCamelCase = string => lodash.upperFirst(lodash.camelCase(string)); 4 | 5 | export default upperCamelCase; 6 | -------------------------------------------------------------------------------- /resources/js/http.js: -------------------------------------------------------------------------------- 1 | import AxiosCache from 'axios-cache-plugin'; 2 | 3 | const axios = require('axios'); 4 | 5 | let token = document.head.querySelector('meta[name="csrf-token"]'); 6 | 7 | if (!token) { 8 | console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token'); 9 | } 10 | 11 | let typeUser = document.head.querySelector('meta[name="type-user"]'); 12 | 13 | let axiosInstance = axios.create({ 14 | withCredentials: false, 15 | headers: { 16 | 'X-Requested-With': 'XMLHttpRequest', 17 | 'X-CSRF-TOKEN': (token.content || null), 18 | }, 19 | }); 20 | 21 | const wrapper = AxiosCache(axiosInstance, { 22 | maxCacheSize: 15 * 60 * 1000, 23 | }); 24 | export default wrapper; 25 | -------------------------------------------------------------------------------- /resources/js/modules/board/app.js: -------------------------------------------------------------------------------- 1 | require('../../bootstrap'); 2 | 3 | import Vue from 'vue' 4 | import VueRouter from 'vue-router'; 5 | import vuetify from '../../vuetify'; 6 | import App from './views/App.vue'; 7 | import draggable from 'vuedraggable'; 8 | import { 9 | ValidationProvider, 10 | ValidationObserver, 11 | extend, 12 | setInteractionMode, 13 | } from 'vee-validate'; 14 | import moment from 'moment'; 15 | 16 | import validateLocale from 'vee-validate/dist/locale/pt_BR.json'; 17 | import routes from './routes'; 18 | import store from './store'; 19 | 20 | setInteractionMode('lazy'); //Mode interation validation fields; 21 | 22 | //Import rules 23 | import * as rules from 'vee-validate/dist/rules'; 24 | 25 | for (let rule in rules) { 26 | extend(rule, { 27 | ...rules[rule], // add the rule 28 | message: validateLocale.messages[rule] // add its message 29 | }); 30 | } 31 | 32 | Vue.use(VueRouter); 33 | 34 | // Routes 35 | const router = new VueRouter({ 36 | mode: 'history', 37 | routes, 38 | }); 39 | 40 | // emite evento customizado para capturar mudanças de rota sem depender do Vue 41 | router.afterEach((to, from) => { 42 | const event = new CustomEvent('routeChange', { 43 | detail: { 44 | to, 45 | from, 46 | }, 47 | }); 48 | 49 | window.dispatchEvent(event); 50 | }); 51 | 52 | Vue.component('ValidationObserver', ValidationObserver); 53 | Vue.component('ValidationProvider', ValidationProvider); 54 | Vue.component('Draggable', draggable); 55 | 56 | Vue.prototype.$isChristmasSeason = (function isChristmasSeason() { 57 | const today = moment(); 58 | const currentYear = today.year(); 59 | const christmasSeasonStartDate = moment(`${currentYear}-12-10`); 60 | const christmasSeasonEndDate = moment(`${+currentYear + 1}-01-02`); 61 | 62 | return today.isBetween(christmasSeasonStartDate, christmasSeasonEndDate); 63 | }()); 64 | 65 | const app = new Vue({ 66 | render: (h) => h(App), 67 | vuetify, 68 | router, 69 | store, 70 | }).$mount('#app'); 71 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/AcceptanceCriteriaForm.vue: -------------------------------------------------------------------------------- 1 | 58 | 97 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/ArtifactItem.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 30 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/BoardContainer.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 45 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/InlineDeleteConfirmation.vue: -------------------------------------------------------------------------------- 1 | 42 | 57 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/LabelItem.vue: -------------------------------------------------------------------------------- 1 | 17 | 35 | 43 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/LabelList.vue: -------------------------------------------------------------------------------- 1 | 21 | 78 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/LabelSelect.vue: -------------------------------------------------------------------------------- 1 | 13 | 51 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/LinkChip.vue: -------------------------------------------------------------------------------- 1 | 28 | 94 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/ListContainer.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/ListSkeletonLoader.vue: -------------------------------------------------------------------------------- 1 | 18 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/MemberList.vue: -------------------------------------------------------------------------------- 1 | 21 | 76 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/SwitchButton.vue: -------------------------------------------------------------------------------- 1 | 18 | 51 | 59 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/TeamChip.vue: -------------------------------------------------------------------------------- 1 | 15 | 44 | 65 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/TooltipRating.vue: -------------------------------------------------------------------------------- 1 | 35 | 83 | 91 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/UserStoryPipeline.vue: -------------------------------------------------------------------------------- 1 | 37 | 88 | -------------------------------------------------------------------------------- /resources/js/modules/board/components/UserStoryPipelineItem.vue: -------------------------------------------------------------------------------- 1 | 17 | 35 | 50 | -------------------------------------------------------------------------------- /resources/js/modules/board/constants/BoardKeys.js: -------------------------------------------------------------------------------- 1 | export const NOT_PLANNED = 'notPlanned'; 2 | export const IMPEDIMENTS = 'impediments'; 3 | export const SPRINT_DEVLOG = 'sprintDevlog'; 4 | export const SPRINT_BACKLOG = 'sprintBacklog'; 5 | export const KAIZEN = 'kaizen'; 6 | -------------------------------------------------------------------------------- /resources/js/modules/board/constants/BoardListKeys.js: -------------------------------------------------------------------------------- 1 | export const TODO = 'todo'; 2 | export const DEVELOPMENT = 'development'; 3 | export const CODE_REVIEW = 'codeReview'; 4 | export const DONE = 'done'; 5 | export const DEPLOY = 'deploy'; 6 | export const BUGS = 'bugs'; 7 | export const DEVLOG = 'devlog'; 8 | export const BACKLOG = 'backlog'; 9 | export const HELPDESK = 'helpDesk'; 10 | export const DEVTASK = 'devtask'; 11 | export const AVENGERS = 'avangers'; 12 | export const STEPPER = 'stepper'; 13 | export const BREAKOUT_ONE = 'breakoutOne'; 14 | export const SYS_IN = 'sysIn'; 15 | export const SYS_OUT = 'sysOut'; 16 | export const GRASSHOPPER = 'grasshopper'; 17 | export const NOT_PRIORITIZED = 'notPrioritized'; 18 | 19 | export default { 20 | TODO, 21 | DEVELOPMENT, 22 | CODE_REVIEW, 23 | DONE, 24 | DEPLOY, 25 | BUGS, 26 | DEVLOG, 27 | BACKLOG, 28 | HELPDESK, 29 | DEVTASK, 30 | AVENGERS, 31 | STEPPER, 32 | BREAKOUT_ONE, 33 | SYS_IN, 34 | SYS_OUT, 35 | NOT_PRIORITIZED, 36 | }; 37 | -------------------------------------------------------------------------------- /resources/js/modules/board/constants/CardTypes.js: -------------------------------------------------------------------------------- 1 | export const USER_STORY = 'user-story'; 2 | export const NOT_PRIORITIZED = 'not-prioritized'; 3 | export const TASK = 'task'; 4 | 5 | export default { 6 | USER_STORY, 7 | NOT_PRIORITIZED, 8 | TASK, 9 | }; 10 | -------------------------------------------------------------------------------- /resources/js/modules/board/constants/LabelKeys.js: -------------------------------------------------------------------------------- 1 | export const BACKEND = 'backend'; 2 | export const FRONTEND = 'frontend'; 3 | export const MOCKUP = 'mockup'; 4 | export const SECURITY = 'secutiry'; 5 | export const BUG = 'bug'; 6 | export const DOCUMENTATION = 'documentation'; 7 | export const INFRA = 'infra'; 8 | export const HELP_DESK = 'helpDesk'; 9 | export const MEET = 'meet'; 10 | export const UX = 'ux'; 11 | export const APP = 'app'; 12 | export const EXPORT = 'export'; 13 | 14 | export default { 15 | BACKEND, 16 | FRONTEND, 17 | MOCKUP, 18 | SECURITY, 19 | BUG, 20 | DOCUMENTATION, 21 | INFRA, 22 | HELP_DESK, 23 | MEET, 24 | UX, 25 | APP, 26 | EXPORT, 27 | }; 28 | -------------------------------------------------------------------------------- /resources/js/modules/board/constants/PlanningGroups.js: -------------------------------------------------------------------------------- 1 | const PLANNING = 'planning'; 2 | const PROBLEMS = 'problems'; 3 | 4 | export default { 5 | PLANNING, 6 | PROBLEMS 7 | }; 8 | -------------------------------------------------------------------------------- /resources/js/modules/board/routes/index.js: -------------------------------------------------------------------------------- 1 | import settingsRoutes from '../../settings/routes'; 2 | import processesRoutes from '../../processes/routes'; 3 | import reportsRoutes from '../../reports/routes'; 4 | import milestonesRoutes from '../../milestones/routes'; 5 | 6 | import Home from '../views/Home.vue'; 7 | import Planning from '../views/Planning.vue'; 8 | import Sprint from '../views/Sprint.vue'; 9 | import WorkspaceSelection from '../views/WorkspaceSelection.vue'; 10 | import CompanyPlanning from '../views/CompanyPlanning.vue'; 11 | 12 | export default [ 13 | { 14 | path: '/home', 15 | name: 'home', 16 | component: Home, 17 | meta: { 18 | title: 'Home', 19 | }, 20 | }, 21 | { 22 | path: '/workspace/:workspaceId/planning', 23 | name: 'planning', 24 | component: Planning, 25 | meta: { 26 | title: 'Planning', 27 | }, 28 | }, 29 | { 30 | path: '/workspace/:workspaceId/sprint/:teamId', 31 | name: 'sprint', 32 | component: Sprint, 33 | meta: { 34 | title: 'Sprint', 35 | }, 36 | props: true, 37 | }, 38 | { 39 | path: '/workspace/select', 40 | name: 'workspace.select', 41 | component: WorkspaceSelection, 42 | meta: { 43 | title: 'Selecione seu Workspace', 44 | }, 45 | }, 46 | { 47 | path: '/workspace/company', 48 | name: 'workspace.company', 49 | component: CompanyPlanning, 50 | meta: { 51 | title: 'Backlogs', 52 | }, 53 | }, 54 | ...settingsRoutes, 55 | ...processesRoutes, 56 | ...reportsRoutes, 57 | ...milestonesRoutes, 58 | ]; 59 | -------------------------------------------------------------------------------- /resources/js/modules/board/services/boards.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getBoards = () => http.get('/boards'); 4 | -------------------------------------------------------------------------------- /resources/js/modules/board/services/cards.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const createCard = (payload) => http.post('/cards', payload); 4 | 5 | export const createCards = (payload) => http.post('/cards/store-many', payload); 6 | 7 | export const getTaskCardsFromUserStory = (params) => http.get('/cards/from-user-story', { 8 | params, 9 | }); 10 | 11 | export const getTaskCardsFromDevlog = (params) => http.get('/cards/from-devlog', { 12 | params, 13 | }); 14 | 15 | export const getTaskCardsFromNotPlanned = (params) => http.get('/cards/from-not-planned', { 16 | params, 17 | }); 18 | 19 | export const getTaskCardsFromKaizen = (params) => http.get('/cards/from-kaizen', { 20 | params, 21 | }); 22 | 23 | export const getCompanyPlanningCards = (params) => http.get('/cards/from-company-planning', { 24 | params, 25 | }); 26 | 27 | export const getPlanningCards = (params) => http.get('/cards/from-planning', { 28 | params, 29 | }); 30 | 31 | export const updateCard = ({ id, ...params }) => http.put(`/cards/${id}`, params); 32 | 33 | export const deleteCard = (id) => http.delete(`/cards/${id}`); 34 | 35 | export const deleteManyCards = (payload) => http.delete('/cards/delete-many', { data: payload }); 36 | 37 | export const updateCardsPositions = (cards = []) => http.post('/cards/update-positions', { cards }); 38 | -------------------------------------------------------------------------------- /resources/js/modules/board/services/events.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const createEvent = (payload) => http.post('/events', payload); 4 | 5 | export const updateEvent = ({ id, ...params }) => http.put(`/events/${id}`, params); 6 | 7 | export const deleteEvent = (id) => http.delete(`/events/${id}`); 8 | 9 | export const getEventsByTeam = (teamId) => http.get(`/events/${teamId}`); 10 | -------------------------------------------------------------------------------- /resources/js/modules/board/services/goals.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const createGoal = (payload) => http.post('/goals', payload); 4 | 5 | export const updateGoal = ({ id, ...params }) => http.put(`/goals/${id}`, params); 6 | 7 | export const deleteGoal = (id) => http.delete(`/goals/${id}`); 8 | 9 | export const getGoals = () => http.get(`/goals/`); 10 | -------------------------------------------------------------------------------- /resources/js/modules/board/services/planning.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getPlanningLists = (workspaceId) => http.get(`/lists/planning/${workspaceId}`); 4 | 5 | export const getProblemsLists = () => http.get('/lists/problems'); 6 | 7 | export const getCompanyPlanningLists = () => http.get('/lists/planning/company'); 8 | -------------------------------------------------------------------------------- /resources/js/modules/board/services/sprint.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getDefaultLists = (params) => http.get('/lists/default', { params }); 4 | 5 | export const getDevlogLists = (params) => http.get('/lists/devlog', { params }); 6 | 7 | export const getPlanningLists = () => http.get('/lists/planning'); 8 | 9 | export const getCurrentSprintSummaryByTeam = (teamId) => http.get(`/sprint/summary/current/${teamId}`); 10 | 11 | export const getCurrentSprintOverviewByTeam = (teamId) => http.get(`/reports/sprint-overview/${teamId}`); 12 | -------------------------------------------------------------------------------- /resources/js/modules/board/services/sprintReport.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const createSprintReport = (payload) => http.post('/sprint-reports', payload); 4 | 5 | export const getSprintReports = () => http.get('/sprint-reports'); 6 | -------------------------------------------------------------------------------- /resources/js/modules/board/services/userStories.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getUserStoriesByTeam = (teamId) => http.get(`/user-stories/${teamId}`); 4 | -------------------------------------------------------------------------------- /resources/js/modules/board/store/boards.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getBoards, 6 | } from '../services/boards'; 7 | 8 | const modules = [ 9 | { getBoards }, 10 | ]; 11 | 12 | export default { 13 | namespaced: true, 14 | modules: { 15 | ...modules.reduce((acc, module) => ({ 16 | ...acc, 17 | ...makeRequestStore(module), 18 | }), {}), 19 | }, 20 | state: { 21 | items: [], 22 | }, 23 | mutations: { 24 | setItems(state, payload) { 25 | state.items = convertKeysToCamelCase(payload); 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /resources/js/modules/board/store/cards.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | 3 | import { 4 | deleteCard, 5 | deleteManyCards, 6 | createCard, 7 | updateCard, 8 | updateCardsPositions, 9 | createCards, 10 | } from '../services/cards'; 11 | 12 | const modules = [ 13 | { deleteCard }, 14 | { deleteManyCards }, 15 | { createCard }, 16 | { updateCard }, 17 | { updateCardsPositions }, 18 | { createCards }, 19 | ]; 20 | 21 | export default { 22 | namespaced: true, 23 | modules: { 24 | ...modules.reduce((acc, module) => ({ 25 | ...acc, 26 | ...makeRequestStore(module), 27 | }), {}), 28 | }, 29 | } 30 | -------------------------------------------------------------------------------- /resources/js/modules/board/store/events.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | 3 | import { 4 | deleteEvent, 5 | createEvent, 6 | updateEvent, 7 | } from '../services/events'; 8 | 9 | const modules = [ 10 | { deleteEvent }, 11 | { createEvent }, 12 | { updateEvent }, 13 | ]; 14 | 15 | export default { 16 | namespaced: true, 17 | modules: { 18 | ...modules.reduce((acc, module) => ({ 19 | ...acc, 20 | ...makeRequestStore(module), 21 | }), {}), 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /resources/js/modules/board/store/goals.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | import { BACKLOG } from '../constants/BoardListKeys'; 4 | 5 | import { 6 | deleteGoal, 7 | createGoal, 8 | updateGoal, 9 | getGoals, 10 | } from '../services/goals'; 11 | 12 | const modules = [ 13 | { deleteGoal }, 14 | { createGoal }, 15 | { updateGoal }, 16 | { getGoals }, 17 | ]; 18 | 19 | export default { 20 | namespaced: true, 21 | modules: { 22 | ...modules.reduce((acc, module) => ({ 23 | ...acc, 24 | ...makeRequestStore(module), 25 | }), {}), 26 | }, 27 | state: { 28 | items: [], 29 | }, 30 | getters: { 31 | getGoalByKey(state, _, rootState, rootGetters) { 32 | return (key) => { 33 | if(key.split('-').includes(BACKLOG)) { 34 | return state.items.filter(({ workspaceId }) => { 35 | return workspaceId === rootGetters['workspaces/currentWorkspace']?.id || (key.split('-')[1] || null) === workspaceId; 36 | })[0]; 37 | } 38 | 39 | const team = rootState['teams'].items.filter((team) => team.key === key)[0]; 40 | return state.items.filter(({ teamId }) => { 41 | return teamId === team.id; 42 | })[0]; 43 | } 44 | }, 45 | 46 | getGoalByTeamId(state) { 47 | return (id) => { 48 | return state.items.filter(({ teamId }) => { 49 | return teamId === id; 50 | })[0]; 51 | } 52 | }, 53 | }, 54 | mutations: { 55 | setItems(state, payload) { 56 | state.items = convertKeysToCamelCase(payload); 57 | }, 58 | }, 59 | } 60 | -------------------------------------------------------------------------------- /resources/js/modules/board/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuex from 'vuex'; 3 | 4 | import cards from './cards'; 5 | import boards from './boards'; 6 | import sprint from './sprint'; 7 | import events from './events'; 8 | import goals from './goals'; 9 | import planning from './planning'; 10 | import sprintReport from './sprintReport'; 11 | 12 | import settingsModules from '../../settings/store'; 13 | import processesModules from '../../processes/store'; 14 | import reportsModules from '../../reports/store'; 15 | import milestonesModules from '../../milestones/store'; 16 | 17 | Vue.use(Vuex); 18 | 19 | export default new Vuex.Store({ 20 | namespaced: true, 21 | modules: { 22 | cards, 23 | boards, 24 | sprint, 25 | events, 26 | goals, 27 | planning, 28 | sprintReport, 29 | ...settingsModules, 30 | ...processesModules, 31 | ...reportsModules, 32 | ...milestonesModules, 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /resources/js/modules/board/store/planning.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | 3 | import { 4 | getPlanningLists, 5 | getCompanyPlanningLists, 6 | } from '../services/planning'; 7 | 8 | const modules = [ 9 | { getPlanningLists }, 10 | { getCompanyPlanningLists }, 11 | ]; 12 | 13 | export default { 14 | namespaced: true, 15 | modules: { 16 | ...modules.reduce((acc, module) => ({ 17 | ...acc, 18 | ...makeRequestStore(module), 19 | }), {}), 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /resources/js/modules/board/store/sprint.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | 3 | import { 4 | getCurrentSprintSummaryByTeam, 5 | getCurrentSprintOverviewByTeam, 6 | getDefaultLists, 7 | getDevlogLists, 8 | } from '../services/sprint'; 9 | 10 | const modules = [ 11 | { getCurrentSprintSummaryByTeam }, 12 | { getCurrentSprintOverviewByTeam }, 13 | { getDefaultLists }, 14 | { getDevlogLists }, 15 | { getDefaultLists }, 16 | ]; 17 | 18 | export default { 19 | namespaced: true, 20 | modules: { 21 | ...modules.reduce((acc, module) => ({ 22 | ...acc, 23 | ...makeRequestStore(module), 24 | }), {}), 25 | }, 26 | }; 27 | -------------------------------------------------------------------------------- /resources/js/modules/board/store/sprintReport.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | 3 | import { 4 | createSprintReport, 5 | getSprintReports, 6 | } from '../services/sprintReport'; 7 | 8 | const modules = [ 9 | { createSprintReport }, 10 | { getSprintReports }, 11 | ]; 12 | 13 | export default { 14 | namespaced: true, 15 | modules: { 16 | ...modules.reduce((acc, module) => ({ 17 | ...acc, 18 | ...makeRequestStore(module), 19 | }), {}), 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /resources/js/modules/board/views/CompanyPlanning.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 80 | -------------------------------------------------------------------------------- /resources/js/modules/board/views/Home.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 75 | -------------------------------------------------------------------------------- /resources/js/modules/board/views/Sprint.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 67 | -------------------------------------------------------------------------------- /resources/js/modules/milestones/components/MilestoneSelect.vue: -------------------------------------------------------------------------------- 1 | 22 | 61 | -------------------------------------------------------------------------------- /resources/js/modules/milestones/components/TeamsSelect.vue: -------------------------------------------------------------------------------- 1 | 25 | 64 | -------------------------------------------------------------------------------- /resources/js/modules/milestones/routes/index.js: -------------------------------------------------------------------------------- 1 | import Home from '../views/Home.vue'; 2 | import Board from '../views/Board.vue'; 3 | 4 | export default [ 5 | { 6 | path: '/milestone', 7 | name: 'milestones', 8 | component: Home, 9 | meta: { 10 | title: 'Milestones', 11 | }, 12 | }, 13 | { 14 | path: '/milestones/:id/board', 15 | name: 'milestones.board', 16 | component: Board, 17 | meta: { 18 | title: 'Milestones', 19 | }, 20 | props: true, 21 | }, 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/js/modules/milestones/services/milestones.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getMilestones = () => http.get('/milestones'); 4 | 5 | export const getMilestone = (id) => http.get(`/milestones/${id}`); 6 | 7 | export const createMilestone = (payload) => http.post('/milestones', payload); 8 | 9 | export const updateMilestone = ({ id, ...params }) => http.put(`/milestones/${id}`, params); 10 | 11 | export const deleteMilestone = (id) => http.delete(`/milestones/${id}`); 12 | 13 | export const getNotStartedItems = (id) => http.get(`/milestones/${id}/backlog-items/not-started`); 14 | 15 | export const getOnGoingItems = (id) => http.get(`/milestones/${id}/backlog-items/on-going`); 16 | 17 | export const getFinishedItems = (id) => http.get(`/milestones/${id}/backlog-items/finished`); 18 | -------------------------------------------------------------------------------- /resources/js/modules/milestones/store/index.js: -------------------------------------------------------------------------------- 1 | import milestones from './milestones'; 2 | 3 | export default { 4 | milestones, 5 | }; 6 | -------------------------------------------------------------------------------- /resources/js/modules/milestones/store/milestones.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getMilestones, 6 | getMilestone, 7 | createMilestone, 8 | updateMilestone, 9 | deleteMilestone, 10 | getFinishedItems, 11 | getOnGoingItems, 12 | getNotStartedItems, 13 | } from '../services/milestones'; 14 | 15 | const modules = [ 16 | { getMilestones }, 17 | { getMilestone }, 18 | { createMilestone }, 19 | { updateMilestone }, 20 | { deleteMilestone }, 21 | { getFinishedItems }, 22 | { getOnGoingItems }, 23 | { getNotStartedItems }, 24 | ]; 25 | 26 | export default { 27 | namespaced: true, 28 | modules: { 29 | ...modules.reduce((acc, module) => ({ 30 | ...acc, 31 | ...makeRequestStore(module), 32 | }), {}), 33 | }, 34 | state: { 35 | items: [], 36 | selectedWorkspaceId: null, 37 | }, 38 | getters: { 39 | itemsByWorkspace(state, _, __, rootGetters) { 40 | return state.items.filter(({ workspaceId }) => { 41 | return workspaceId === rootGetters['workspaces/currentWorkspace']?.id; 42 | }); 43 | }, 44 | }, 45 | mutations: { 46 | setItems(state, payload) { 47 | state.items = convertKeysToCamelCase(payload); 48 | }, 49 | setSelectedWorkspaceId(state, payload) { 50 | state.selectedWorkspaceId = payload; 51 | }, 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /resources/js/modules/processes/components/ViewProcessModal.vue: -------------------------------------------------------------------------------- 1 | 37 | 66 | -------------------------------------------------------------------------------- /resources/js/modules/processes/routes/index.js: -------------------------------------------------------------------------------- 1 | import Home from '../views/Home.vue'; 2 | import Create from '../views/Create.vue'; 3 | import Edit from '../views/Edit.vue'; 4 | 5 | export default [ 6 | { 7 | path: '/process', 8 | name: 'processes', 9 | component: Home, 10 | meta: { 11 | title: 'Central de Processos', 12 | }, 13 | }, 14 | { 15 | path: '/process/edit/:id', 16 | name: 'processes.edit', 17 | component: Edit, 18 | meta: { 19 | title: 'Editar Processo', 20 | }, 21 | props: true, 22 | }, 23 | { 24 | path: '/process/new', 25 | name: 'processes.new', 26 | component: Create, 27 | meta: { 28 | title: 'Novo Processo', 29 | }, 30 | }, 31 | ]; 32 | -------------------------------------------------------------------------------- /resources/js/modules/processes/services/processes.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getProcesses = () => http.get('/processes'); 4 | 5 | export const getProcess = (id) => http.get(`/processes/${id}`); 6 | 7 | export const createProcess = (payload) => http.post('/processes', payload); 8 | 9 | export const updateProcess = ({ id, ...params }) => http.put(`/processes/${id}`, params); 10 | 11 | export const deleteProcess = (id) => http.delete(`/processes/${id}`); 12 | -------------------------------------------------------------------------------- /resources/js/modules/processes/store/index.js: -------------------------------------------------------------------------------- 1 | import processes from './processes'; 2 | 3 | export default { 4 | processes, 5 | }; 6 | -------------------------------------------------------------------------------- /resources/js/modules/processes/store/processes.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getProcess, 6 | getProcesses, 7 | createProcess, 8 | updateProcess, 9 | deleteProcess, 10 | } from '../services/processes'; 11 | 12 | const modules = [ 13 | { getProcess }, 14 | { getProcesses }, 15 | { createProcess }, 16 | { updateProcess }, 17 | { deleteProcess }, 18 | ]; 19 | 20 | export default { 21 | namespaced: true, 22 | modules: { 23 | ...modules.reduce((acc, module) => ({ 24 | ...acc, 25 | ...makeRequestStore(module), 26 | }), {}), 27 | }, 28 | state: { 29 | items: [], 30 | }, 31 | getters: { 32 | itemsByTeam(state) { 33 | return (teamId) => state.items.filter(({ teamIds }) => { 34 | return teamIds.indexOf(teamId) > -1; 35 | }) 36 | }, 37 | }, 38 | mutations: { 39 | setItems(state, payload) { 40 | state.items = convertKeysToCamelCase(payload); 41 | }, 42 | }, 43 | } 44 | -------------------------------------------------------------------------------- /resources/js/modules/processes/views/Create.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 56 | -------------------------------------------------------------------------------- /resources/js/modules/reports/routes/index.js: -------------------------------------------------------------------------------- 1 | import Home from '../views/Home.vue'; 2 | import SprintReport from '../views/SprintReport.vue'; 3 | 4 | export default [ 5 | { 6 | path: '/reports', 7 | name: 'reports', 8 | component: Home, 9 | meta: { 10 | title: 'Relatórios', 11 | }, 12 | }, 13 | { 14 | path: '/reports/sprints', 15 | name: 'reports.sprint', 16 | component: SprintReport, 17 | meta: { 18 | title: 'Relatórios de Sprint', 19 | }, 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /resources/js/modules/reports/services/sprintReports.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | // eslint-disable-next-line import/prefer-default-export 4 | export const getSprintReportByTeamId = (teamId) => http.get(`/sprint-reports/${teamId}`); 5 | -------------------------------------------------------------------------------- /resources/js/modules/reports/store/index.js: -------------------------------------------------------------------------------- 1 | import sprintReports from './sprintReports'; 2 | 3 | export default { 4 | sprintReports, 5 | }; 6 | -------------------------------------------------------------------------------- /resources/js/modules/reports/store/sprintReports.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getSprintReportByTeamId, 6 | } from '../services/sprintReports'; 7 | 8 | const modules = [ 9 | { getSprintReportByTeamId }, 10 | ]; 11 | 12 | export default { 13 | namespaced: true, 14 | modules: { 15 | ...modules.reduce((acc, module) => ({ 16 | ...acc, 17 | ...makeRequestStore(module), 18 | }), {}), 19 | }, 20 | state: { 21 | items: [], 22 | selectedTeamId: null, 23 | }, 24 | getters: { 25 | itemsByTeam(state) { 26 | return (teamId) => state.items.filter(({ teamIds }) => { 27 | return teamIds.indexOf(teamId) > -1; 28 | }); 29 | }, 30 | }, 31 | mutations: { 32 | setItems(state, payload) { 33 | state.items = convertKeysToCamelCase(payload); 34 | }, 35 | 36 | setSelectedTeamId(state, payload) { 37 | state.selectedTeamId = payload; 38 | }, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /resources/js/modules/reports/views/Home.vue: -------------------------------------------------------------------------------- 1 | 11 | -------------------------------------------------------------------------------- /resources/js/modules/settings/routes/index.js: -------------------------------------------------------------------------------- 1 | import Home from '../views/Home.vue'; 2 | import Members from '../views/Members.vue'; 3 | import Workspaces from '../views/Workspaces.vue'; 4 | import Teams from '../views/Teams.vue'; 5 | import Labels from '../views/Labels.vue'; 6 | import BacklogLabels from '../views/BacklogLabels.vue'; 7 | 8 | export default [ 9 | { 10 | path: '/settings/workspaces', 11 | name: 'settings.workspaces', 12 | component: Workspaces, 13 | meta: { 14 | title: 'Workspaces', 15 | }, 16 | }, 17 | { 18 | path: '/settings/members', 19 | name: 'settings.members', 20 | component: Members, 21 | meta: { 22 | title: 'Membros', 23 | }, 24 | }, 25 | { 26 | path: '/settings/teams', 27 | name: 'settings.teams', 28 | component: Teams, 29 | meta: { 30 | title: 'Times', 31 | }, 32 | }, 33 | { 34 | path: '/settings/labels', 35 | name: 'settings.labels', 36 | component: Labels, 37 | meta: { 38 | title: 'Categorias', 39 | }, 40 | }, 41 | { 42 | path: '/settings/backlog-labels', 43 | name: 'settings.backlogLabels', 44 | component: BacklogLabels, 45 | meta: { 46 | title: 'Categorias de backlog', 47 | }, 48 | }, 49 | { 50 | path: '/settings', 51 | name: 'settings', 52 | component: Home, 53 | meta: { 54 | title: 'Configurações', 55 | }, 56 | }, 57 | ]; 58 | -------------------------------------------------------------------------------- /resources/js/modules/settings/services/backlogLabels.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getBacklogLabels = () => http.get('/backlog-labels'); 4 | 5 | export const createBacklogLabel = (payload) => http.post('/backlog-labels', payload); 6 | 7 | export const updateBacklogLabel = ({ id, ...params }) => http.put(`/backlog-labels/${id}`, params); 8 | 9 | export const deleteBacklogLabel = (id) => http.delete(`/backlog-labels/${id}`); 10 | -------------------------------------------------------------------------------- /resources/js/modules/settings/services/labels.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getLabels = () => http.get('/labels'); 4 | 5 | export const createLabel = (payload) => http.post('/labels', payload); 6 | 7 | export const updateLabel = ({ id, ...params }) => http.put(`/labels/${id}`, params); 8 | 9 | export const deleteLabel = (id) => http.delete(`/labels/${id}`); 10 | 11 | export const getLabelsByWorkspaceId = (workspaceId) => http.get(`/labels/workspace/${workspaceId}`); 12 | -------------------------------------------------------------------------------- /resources/js/modules/settings/services/members.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getMembers = () => http.get('/members'); 4 | 5 | export const createMember = (payload) => http.post('/members', payload); 6 | 7 | export const updateMember = ({ id, ...params }) => http.put(`/members/${id}`, params); 8 | 9 | export const deleteMember = (id) => http.delete(`/members/${id}`); 10 | 11 | export const resendWelcomeMail = (userId) => http.post('/users/resend-welcome-mail', { user_id: userId }); 12 | -------------------------------------------------------------------------------- /resources/js/modules/settings/services/teams.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getTeams = () => http.get('/teams'); 4 | 5 | export const createTeam = (payload) => http.post('/teams', payload); 6 | 7 | export const updateTeam = ({ id, ...params }) => http.put(`/teams/${id}`, params); 8 | 9 | export const deleteTeam = (id) => http.delete(`/teams/${id}`); 10 | -------------------------------------------------------------------------------- /resources/js/modules/settings/services/workspaces.js: -------------------------------------------------------------------------------- 1 | import http from '../../../http'; 2 | 3 | export const getWorkspaces = () => http.get('/workspaces'); 4 | 5 | export const getWorkspacesIncludeInactive = () => http.get('/workspaces', { params: { with_inactive: true } }); 6 | 7 | export const createWorkspace = (payload) => http.post('/workspaces', payload); 8 | 9 | export const updateWorkspace = ({ id, ...params }) => http.put(`/workspaces/${id}`, params); 10 | 11 | export const deleteWorkspace = (id) => http.delete(`/workspaces/${id}`); 12 | -------------------------------------------------------------------------------- /resources/js/modules/settings/store/backlogLabels.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getBacklogLabels, 6 | createBacklogLabel, 7 | updateBacklogLabel, 8 | deleteBacklogLabel, 9 | } from '../services/backlogLabels'; 10 | 11 | const modules = [ 12 | { getBacklogLabels }, 13 | { createBacklogLabel }, 14 | { updateBacklogLabel }, 15 | { deleteBacklogLabel }, 16 | ]; 17 | 18 | export default { 19 | namespaced: true, 20 | modules: { 21 | ...modules.reduce((acc, module) => ({ 22 | ...acc, 23 | ...makeRequestStore(module), 24 | }), {}), 25 | }, 26 | state: { 27 | items: [], 28 | }, 29 | mutations: { 30 | setItems(state, payload) { 31 | state.items = convertKeysToCamelCase(payload); 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /resources/js/modules/settings/store/index.js: -------------------------------------------------------------------------------- 1 | import workspaces from './workspaces'; 2 | import members from './members'; 3 | import teams from './teams'; 4 | import labels from './labels'; 5 | import backlogLabels from './backlogLabels'; 6 | 7 | export default { 8 | workspaces, 9 | members, 10 | teams, 11 | labels, 12 | backlogLabels, 13 | }; 14 | -------------------------------------------------------------------------------- /resources/js/modules/settings/store/labels.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getLabels, 6 | createLabel, 7 | updateLabel, 8 | deleteLabel, 9 | getLabelsByWorkspaceId, 10 | } from '../services/labels'; 11 | 12 | const modules = [ 13 | { getLabels }, 14 | { createLabel }, 15 | { updateLabel }, 16 | { deleteLabel }, 17 | { getLabelsByWorkspaceId }, 18 | ]; 19 | 20 | export default { 21 | namespaced: true, 22 | modules: { 23 | ...modules.reduce((acc, module) => ({ 24 | ...acc, 25 | ...makeRequestStore(module), 26 | }), {}), 27 | }, 28 | state: { 29 | items: [], 30 | selectedWorkspaceId: null, 31 | }, 32 | getters: { 33 | itemsByWorkspace(state, _, __, rootGetters) { 34 | return state.items.filter(({ workspaceId }) => { 35 | return workspaceId === rootGetters['workspaces/currentWorkspace']?.id; 36 | }); 37 | }, 38 | }, 39 | mutations: { 40 | setItems(state, payload) { 41 | state.items = convertKeysToCamelCase(payload); 42 | }, 43 | setSelectedWorkspaceId(state, payload) { 44 | state.selectedWorkspaceId = payload; 45 | }, 46 | }, 47 | }; 48 | -------------------------------------------------------------------------------- /resources/js/modules/settings/store/members.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getMembers, 6 | createMember, 7 | updateMember, 8 | deleteMember, 9 | resendWelcomeMail, 10 | } from '../services/members'; 11 | 12 | const modules = [ 13 | { getMembers }, 14 | { createMember }, 15 | { updateMember }, 16 | { deleteMember }, 17 | { resendWelcomeMail }, 18 | ]; 19 | 20 | export default { 21 | namespaced: true, 22 | modules: { 23 | ...modules.reduce((acc, module) => ({ 24 | ...acc, 25 | ...makeRequestStore(module), 26 | }), {}), 27 | }, 28 | state: { 29 | items: [], 30 | }, 31 | getters: { 32 | itemsByWorkspace(state, _, __, rootGetters) { 33 | return state.items.filter(({ workspaceIds }) => { 34 | return workspaceIds.indexOf(rootGetters['workspaces/currentWorkspace']?.id) > -1; 35 | }); 36 | }, 37 | }, 38 | mutations: { 39 | setItems(state, payload) { 40 | state.items = convertKeysToCamelCase(payload); 41 | }, 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /resources/js/modules/settings/store/teams.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getTeams, 6 | createTeam, 7 | updateTeam, 8 | deleteTeam, 9 | } from '../services/teams'; 10 | 11 | const modules = [ 12 | { getTeams }, 13 | { createTeam }, 14 | { updateTeam }, 15 | { deleteTeam }, 16 | ]; 17 | 18 | export default { 19 | namespaced: true, 20 | modules: { 21 | ...modules.reduce((acc, module) => ({ 22 | ...acc, 23 | ...makeRequestStore(module), 24 | }), {}), 25 | }, 26 | state: { 27 | items: [], 28 | }, 29 | getters: { 30 | itemsByWorkspace(state, _, __, rootGetters) { 31 | return state.items.filter(({ workspaceId }) => { 32 | return workspaceId?.indexOf(rootGetters['workspaces/currentWorkspace']?.id) > -1; 33 | }); 34 | }, 35 | }, 36 | mutations: { 37 | setItems(state, payload) { 38 | state.items = convertKeysToCamelCase(payload); 39 | }, 40 | }, 41 | } 42 | -------------------------------------------------------------------------------- /resources/js/modules/settings/store/workspaces.js: -------------------------------------------------------------------------------- 1 | import makeRequestStore from '../../../core/utils/makeRequestStore'; 2 | import convertKeysToCamelCase from '../../../core/utils/convertKeysToCamelCase'; 3 | 4 | import { 5 | getWorkspaces, 6 | getWorkspacesIncludeInactive, 7 | createWorkspace, 8 | updateWorkspace, 9 | deleteWorkspace, 10 | } from '../services/workspaces'; 11 | 12 | const modules = [ 13 | { getWorkspaces }, 14 | { getWorkspacesIncludeInactive }, 15 | { createWorkspace }, 16 | { updateWorkspace }, 17 | { deleteWorkspace }, 18 | ]; 19 | 20 | export default { 21 | namespaced: true, 22 | modules: { 23 | ...modules.reduce((acc, module) => ({ 24 | ...acc, 25 | ...makeRequestStore(module), 26 | }), {}), 27 | }, 28 | state: { 29 | items: [], 30 | selectedWorkspace: null, 31 | }, 32 | getters: { 33 | currentWorkspace(state) { 34 | //TODO converKeysToCamelCase deeper 35 | let copy = _.clone(state.selectedWorkspace); 36 | if(!copy) return copy; 37 | copy.settings = convertKeysToCamelCase(copy.settings); 38 | return copy; 39 | }, 40 | }, 41 | mutations: { 42 | setItems(state, payload) { 43 | state.items = convertKeysToCamelCase(payload); 44 | }, 45 | setSelectedWorkspace(state, workspace) { 46 | state.selectedWorkspace = workspace; 47 | }, 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /resources/js/modules/settings/views/Home.vue: -------------------------------------------------------------------------------- 1 | 39 | -------------------------------------------------------------------------------- /resources/js/vuetify.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import Vuetify from 'vuetify'; 3 | import 'vuetify/dist/vuetify.min.css'; 4 | 5 | Vue.use(Vuetify); 6 | 7 | const opts = { 8 | icons: { 9 | iconfont: 'mdiSvg', // 'mdi' || 'mdiSvg' || 'md' || 'fa' || 'fa4' || 'faSvg' 10 | }, 11 | theme: { 12 | themes: { 13 | light: { 14 | primary: '#399AE7', 15 | }, 16 | dark: { 17 | primary: '#399AE7', 18 | }, 19 | }, 20 | }, 21 | }; 22 | 23 | export default new Vuetify(opts); 24 | -------------------------------------------------------------------------------- /resources/lang/en/auth.php: -------------------------------------------------------------------------------- 1 | 'These credentials do not match our records.', 17 | 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/lang/en/passwords.php: -------------------------------------------------------------------------------- 1 | 'Passwords must be at least eight characters and match the confirmation.', 17 | 'reset' => 'Your password has been reset!', 18 | 'sent' => 'We have e-mailed your password reset link!', 19 | 'token' => 'This password reset token is invalid.', 20 | 'user' => "We can't find a user with that e-mail address.", 21 | 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | // Body 2 | $body-bg: #f8fafc; 3 | 4 | // Typography 5 | $font-family-sans-serif: 'Nunito', sans-serif; 6 | $font-size-base: 0.9rem; 7 | $line-height-base: 1.6; 8 | 9 | // Colors 10 | $blue: #3490dc; 11 | $indigo: #6574cd; 12 | $purple: #9561e2; 13 | $pink: #f66d9b; 14 | $red: #e3342f; 15 | $orange: #f6993f; 16 | $yellow: #ffed4a; 17 | $green: #38c172; 18 | $teal: #4dc0b5; 19 | $cyan: #6cb2eb; 20 | -------------------------------------------------------------------------------- /resources/sass/app.scss: -------------------------------------------------------------------------------- 1 | .task-card { 2 | box-shadow: 0 3px 1px -2px rgba(0,0,0,.05), 3 | 0 2px 2px 0 rgba(0,0,0,.05), 4 | 0 1px 5px 0 rgba(0,0,0,.05) !important; 5 | max-width: 250px; 6 | min-width: 250px; 7 | } 8 | 9 | .v-tabs-items { 10 | background-color: transparent!important; 11 | } 12 | 13 | .v-expansion-panel-content__wrap { 14 | padding: 0!important; 15 | } 16 | 17 | .v-expansion-panel { 18 | background: transparent!important; 19 | } 20 | 21 | .v-expansion-panel-header { 22 | padding: 0!important; 23 | } 24 | 25 | .v-expansion-panel--active>.v-expansion-panel-header { 26 | min-height: 48px!important; 27 | } 28 | 29 | .v-expansion-panel--active+.v-expansion-panel, .v-expansion-panel--active:not(:first-child) { 30 | margin-top: 0px!important; 31 | } 32 | 33 | .swal2-popup { 34 | font-family: Poppins, sans-serif !important; 35 | } 36 | 37 | .v-data-table tbody tr.v-data-table__expanded__content { 38 | box-shadow: unset !important; 39 | } -------------------------------------------------------------------------------- /resources/sass/home.scss: -------------------------------------------------------------------------------- 1 | 2 | // Variables 3 | @import '_variables'; 4 | 5 | // Bootstrap 6 | @import '~bootstrap/scss/bootstrap'; 7 | -------------------------------------------------------------------------------- /resources/views/auth/verify.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.app') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
8 |
{{ __('Verify Your Email Address') }}
9 | 10 |
11 | @if (session('resent')) 12 | 15 | @endif 16 | 17 | {{ __('Before proceeding, please check your email for a verification link.') }} 18 | {{ __('If you did not receive the email') }}, 19 |
20 | @csrf 21 | . 22 |
23 |
24 |
25 |
26 |
27 |
28 | @endsection 29 | -------------------------------------------------------------------------------- /resources/views/index.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | @php 4 | $addons = auth()->user()->member->company['addons'] ?? []; 5 | @endphp 6 | 7 | @yield('title') 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | @foreach($addons as $addon) 16 | @if(isset($addon['styles'])) 17 | @foreach($addon['styles'] as $style) 18 | 19 | @endforeach 20 | @endif 21 | @endforeach 22 | 23 | 24 |
25 | 26 | 27 | @foreach($addons as $addon) 28 | @if(isset($addon['scripts'])) 29 | @foreach($addon['scripts'] as $script) 30 | 31 | @endforeach 32 | @endif 33 | @endforeach 34 | 35 | 36 | -------------------------------------------------------------------------------- /resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | {{ config('app.name', 'Laravel') }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | 25 | 26 | 27 |
28 |
29 | 30 |
31 | @yield('content') 32 |
33 | 36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /resources/views/vendor/emails/welcome.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 |
3 |

4 | Eae {{ $name }}, 5 |

6 |
7 |

8 | Seu email foi cadastrado no {{ config('app.name') }}
9 | Clique no botão abaixo para definir sua senha para acessar a plataforma. 10 |

11 |
12 | @component('mail::button', ['url' => $url]) 13 | Cadastrar senha 14 | @endcomponent 15 | 16 |
17 |

18 | Atenciosamente, Sysvale Team. 19 |

20 |
21 | @endcomponent 22 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/button.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/footer.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | {{ $slot }} 5 |

6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/header.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ $slot }} 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/message.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::layout') 2 | {{-- Header --}} 3 | @slot('header') 4 | @component('mail::header', ['url' => config('app.url')]) 5 | Trelássio 6 | @endcomponent 7 | @endslot 8 | 9 | {{-- Body --}} 10 | {{ $slot }} 11 | 12 | {{-- Subcopy --}} 13 | @isset($subcopy) 14 | @slot('subcopy') 15 | @component('mail::subcopy') 16 | {{ $subcopy }} 17 | @endcomponent 18 | @endslot 19 | @endisset 20 | 21 | {{-- Footer --}} 22 | @slot('footer') 23 | @component('mail::footer') 24 | © {{ date('Y') }} Trelássio. Todos os direitos reservados. 25 | @endcomponent 26 | @endslot 27 | @endcomponent 28 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/panel.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/promotion.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/promotion/button.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 |
4 | 5 | 6 | 9 | 10 |
7 | {{ $slot }} 8 |
11 |
14 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/subcopy.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/table.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ Illuminate\Mail\Markdown::parse($slot) }} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/button.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }}: {{ $url }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/footer.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/header.blade.php: -------------------------------------------------------------------------------- 1 | [{{ $slot }}]({{ $url }}) 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/layout.blade.php: -------------------------------------------------------------------------------- 1 | {!! strip_tags($header) !!} 2 | 3 | {!! strip_tags($slot) !!} 4 | @isset($subcopy) 5 | 6 | {!! strip_tags($subcopy) !!} 7 | @endisset 8 | 9 | {!! strip_tags($footer) !!} 10 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/message.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::layout') 2 | {{-- Header --}} 3 | @slot('header') 4 | @component('mail::header', ['url' => config('app.url')]) 5 | {{ config('app.name') }} 6 | @endcomponent 7 | @endslot 8 | 9 | {{-- Body --}} 10 | {{ $slot }} 11 | 12 | {{-- Subcopy --}} 13 | @isset($subcopy) 14 | @slot('subcopy') 15 | @component('mail::subcopy') 16 | {{ $subcopy }} 17 | @endcomponent 18 | @endslot 19 | @endisset 20 | 21 | {{-- Footer --}} 22 | @slot('footer') 23 | @component('mail::footer') 24 | © {{ date('Y') }} Sysvale. Todos os direitos reservados. 25 | @endcomponent 26 | @endslot 27 | @endcomponent 28 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/panel.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/promotion.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/promotion/button.blade.php: -------------------------------------------------------------------------------- 1 | [{{ $slot }}]({{ $url }}) 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/subcopy.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/text/table.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 2 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | get('/user', function (Request $request) { 19 | return $request->user(); 20 | }); 21 | 22 | Route::post('cards', [CardController::class, 'store']) 23 | ->middleware('client') 24 | ->name('api.cards.store'); 25 | 26 | Route::post('cards/batch', [CardController::class, 'handleBatch']) 27 | ->middleware('client') 28 | ->name('api.cards.batch'); 29 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 16 | }); 17 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 8 | */ 9 | 10 | $uri = urldecode( 11 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 12 | ); 13 | 14 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 15 | // built-in PHP web server. This provides a convenient way to test a Laravel 16 | // application without having installed a "real" web server software here. 17 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 18 | return false; 19 | } 20 | 21 | require_once __DIR__.'/public/index.php'; 22 | -------------------------------------------------------------------------------- /storage/app/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !public/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/app/public/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/.gitignore: -------------------------------------------------------------------------------- 1 | config.php 2 | routes.php 3 | schedule-* 4 | compiled.php 5 | services.json 6 | events.scanned.php 7 | routes.scanned.php 8 | down 9 | -------------------------------------------------------------------------------- /storage/framework/cache/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !data/ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /storage/framework/cache/data/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/sessions/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/testing/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/framework/views/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /storage/logs/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/CreatesApplication.php: -------------------------------------------------------------------------------- 1 | make(Kernel::class)->bootstrap(); 19 | 20 | return $app; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tests/Feature/App/Console/Commands/CompanyClientCommandTest.php: -------------------------------------------------------------------------------- 1 | create(); 15 | 16 | $this->artisan('passport:company', ['company_id' => $company->id])->assertExitCode(0); 17 | 18 | $this->assertDatabaseHas('company_clients', ['company_id' => $company->id]); 19 | 20 | $company_client = CompanyClient::whereCompanyId($company->id)->first(); 21 | $this->assertTrue($company->is($company_client->company)); 22 | $this->assertNotNull($company_client->client_id); 23 | $this->assertInstanceOf(Client::class, $company_client->client); 24 | } 25 | 26 | public function testDoNotCreateNewClientForCompanyThatAlreadyHasClient(): void 27 | { 28 | $company_client = CompanyClient::create([ 29 | 'company_id' => factory(Company::class)->create()->id, 30 | 'client_id' => Client::create([ 31 | 'id' => 'client_id', 32 | 'secret' => 'client_secret', 33 | ])->id, 34 | ]); 35 | 36 | $this->artisan('passport:company', ['company_id' => $company_client->company_id]) 37 | ->assertExitCode(0); 38 | 39 | $this->assertEquals(1, CompanyClient::whereCompanyId($company_client->company_id)->count()); 40 | } 41 | 42 | public function testShowClientIdAndClientSecret(): void 43 | { 44 | $client = Client::create([ 45 | 'secret' => 'client_secret', 46 | ]); 47 | 48 | $company_client = CompanyClient::create([ 49 | 'company_id' => factory(Company::class)->create()->id, 50 | 'client_id' => $client->id, 51 | ]); 52 | 53 | $this->artisan('passport:company', ['company_id' => $company_client->company_id]) 54 | ->expectsOutput('Client ID: ' . $client->id) 55 | ->expectsOutput('Client Secret: client_secret') 56 | ->assertExitCode(0); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tests/Feature/App/Http/Controllers/MemberControllerTest.php: -------------------------------------------------------------------------------- 1 | user = factory(User::class)->state('with-member-company')->create(); 16 | $this->actingAs($this->user); 17 | } 18 | 19 | public function testIfAuthenticatedMemberCanCreateANewMember() 20 | { 21 | $member = factory(Member::class)->state('with-company')->make(); 22 | $data = $member->toArray(); 23 | 24 | $data = array_add($data, 'email', 'teste@email.com'); 25 | $data = array_add($data, 'team_ids', [1]); 26 | 27 | $response = $this->post('members', $data); 28 | 29 | $this->assertEquals(2, Member::all()->count()); 30 | $this->assertEquals(201, $response->status()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Feature/App/Http/Controllers/TeamControllerTest.php: -------------------------------------------------------------------------------- 1 | user = factory(User::class)->state('with-member-company')->create(); 16 | factory(Team::class, 3)->state('with-company')->make(); 17 | 18 | $this->actingAs($this->user); 19 | } 20 | 21 | public function testIfAuthenticatedMemberCanCreateANewTeam() 22 | { 23 | $workspace = factory(Team::class)->state('with-company')->make(); 24 | $data = $workspace->toArray(); 25 | 26 | $response = $this->post('teams', $data); 27 | 28 | $this->assertEquals(1, Team::all()->count()); 29 | $this->assertEquals(201, $response->status()); 30 | } 31 | 32 | public function testIfIsGettingOnlyAuthenticatedMemberCompanyTeam() 33 | { 34 | factory(Team::class, 1)->create([ 35 | 'company_id' => $this->user->member->company_id, 36 | ]); 37 | 38 | $response = $this->get('teams'); 39 | 40 | $this->assertCount(1, json_decode($response->getContent())); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Feature/App/Http/Controllers/WorkspaceControllerTest.php: -------------------------------------------------------------------------------- 1 | user = factory(User::class)->state('with-member-company')->create(); 18 | factory(Workspace::class, 2)->state('with-company')->create(); 19 | 20 | $this->actingAs($this->user); 21 | } 22 | 23 | public function testIfAuthenticatedMemberCanCreateANewWorkspace() 24 | { 25 | $this->actingAs($this->user); 26 | 27 | $workspace = factory(Workspace::class)->state('with-company')->make(); 28 | $data = $workspace->toArray(); 29 | 30 | $response = $this->post('workspaces', $data); 31 | 32 | $this->assertEquals(1, Workspace::all()->count()); 33 | $this->assertEquals(201, $response->status()); 34 | } 35 | 36 | public function testIfAuthenticatedMemberCanNotAccessAWorkspaceInDifferentCompany() 37 | { 38 | $response = $this->get('workspaces'); 39 | $this->assertCount(0, json_decode($response->getContent())); 40 | } 41 | 42 | public function testIfIsGettingOnlyAuthenticatedMemberWorkspace() 43 | { 44 | factory(Workspace::class, 2)->create([ 45 | 'company_id' => $this->user->member->company_id, 46 | ]); 47 | 48 | $response = $this->get('workspaces'); 49 | 50 | $this->assertCount(2, json_decode($response->getContent())); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/Feature/App/Observers/CardObserverTest.php: -------------------------------------------------------------------------------- 1 | state('with-member-company')->create(); 21 | Auth::login($user); 22 | request()->merge( 23 | app(ResolveCompanyTenant::class)->handle(request(), fn ($request) => $request)->all() 24 | ); 25 | 26 | $card = factory(Card::class)->create(); 27 | 28 | $this->assertEquals($user->id, $card->user_id); 29 | $this->assertTrue($user->member->company->is($card->company)); 30 | } 31 | 32 | public function testLinkCompanyAndNullUserWhenCreatingCardUsingClient(): void 33 | { 34 | $company = factory(Company::class)->create(); 35 | $client = Client::create(); 36 | CompanyClient::create([ 37 | 'company_id' => $company->id, 38 | 'client_id' => $client->id, 39 | ]); 40 | 41 | Passport::actingAsClient($client); 42 | $this->mock(Parser::class, function ($mock) { 43 | $mock->shouldReceive('parse->claims->get')->andReturn('access_token'); 44 | }); 45 | request()->headers->add(['Authorization' => 'Bearer access_token']); 46 | request()->merge( 47 | app(ResolveCompanyTenant::class)->handle(request(), fn ($request) => $request)->all() 48 | ); 49 | 50 | $card = factory(Card::class)->create(); 51 | 52 | $this->assertNull($card->user_id); 53 | $this->assertEquals($company->id, $card->company_id); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | clearDB('mongodb'); 20 | 21 | parent::tearDown(); 22 | } 23 | 24 | private function clearDB($connection): void 25 | { 26 | foreach (DB::connection($connection)->listCollections() as $coll) { 27 | DB::connection($connection) 28 | ->table($coll->getName()) 29 | ->delete(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Unit/ExampleTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | /* 4 | |-------------------------------------------------------------------------- 5 | | Mix Asset Management 6 | |-------------------------------------------------------------------------- 7 | | 8 | | Mix provides a clean, fluent API for defining some Webpack build steps 9 | | for your Laravel application. By default, we are compiling the Sass 10 | | file for the application as well as bundling up all the JS files. 11 | | 12 | */ 13 | 14 | module.exports = { 15 | rules: [ 16 | { 17 | test: /\.s(c|a)ss$/, 18 | use: [ 19 | 'vue-style-loader', 20 | 'css-loader', 21 | { 22 | loader: 'sass-loader', 23 | // Requires sass-loader@^7.0.0 24 | options: { 25 | implementation: require('sass'), 26 | fiber: require('fibers'), 27 | indentedSyntax: true // optional 28 | }, 29 | // Requires sass-loader@^8.0.0 30 | options: { 31 | implementation: require('sass'), 32 | sassOptions: { 33 | fiber: require('fibers'), 34 | indentedSyntax: true // optional 35 | }, 36 | }, 37 | }, 38 | ], 39 | }, 40 | ], 41 | } 42 | 43 | mix.babelConfig({ 44 | plugins: ['@babel/plugin-syntax-dynamic-import'], 45 | }); 46 | 47 | mix.js('resources/js/modules/board/app.js', 'public/js/app.min.js') 48 | .sass('resources/sass/app.scss', 'public/css') 49 | .sass('resources/sass/home.scss', 'public/css'); 50 | 51 | mix.version(); 52 | --------------------------------------------------------------------------------