├── .editorconfig ├── .env.example ├── .gitattributes ├── .github ├── dependabot.yml └── workflows │ └── actions.yml ├── .gitignore ├── .scrutinizer.yml ├── LICENSE.md ├── Procfile ├── README.md ├── api-docs.json ├── app ├── Classes │ ├── ImageRenderer.php │ └── Markdown.php ├── Console │ ├── Commands │ │ ├── AddToken.php │ │ ├── EncryptOrgPasswords.php │ │ ├── InstallAppCommand.php │ │ ├── JoinOrg.php │ │ ├── RemindUsers.php │ │ └── UpdateOrg.php │ └── Kernel.php ├── Exceptions │ └── Handler.php ├── Http │ ├── Controllers │ │ ├── Api │ │ │ ├── HomeController.php │ │ │ ├── OrgController.php │ │ │ └── UserController.php │ │ ├── Auth │ │ │ ├── LoginController.php │ │ │ └── RegisterController.php │ │ ├── AutoJoinerController.php │ │ ├── Controller.php │ │ ├── DashboardController.php │ │ ├── DeveloperController.php │ │ ├── GithubController.php │ │ ├── JoinController.php │ │ ├── LoginController.php │ │ ├── OrgController.php │ │ └── TeamController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── Authenticate.php │ │ ├── EncryptCookies.php │ │ ├── HttpsMiddleware.php │ │ ├── RedirectIfAuthenticated.php │ │ ├── TrimStrings.php │ │ ├── TrustProxies.php │ │ └── VerifyCsrfToken.php │ └── Requests │ │ ├── CustomMessageRequest.php │ │ ├── JoinOrgRequest.php │ │ └── SetTeamRequest.php ├── Mail │ ├── RemindUsers.php │ └── WelcomeUser.php ├── Org.php ├── Policies │ └── OrgPolicy.php ├── Providers │ ├── AppServiceProvider.php │ ├── AuthServiceProvider.php │ ├── BroadcastServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── Team.php ├── Traits │ └── CaptchaTrait.php ├── User.php └── helpers.php ├── artisan ├── bootstrap ├── app.php └── cache │ └── .gitignore ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── broadcasting.php ├── cache.php ├── database.php ├── debug-server.php ├── filesystems.php ├── github.php ├── hashing.php ├── logging.php ├── mail.php ├── queue.php ├── recaptcha.php ├── sentry.php ├── services.php ├── session.php ├── tinker.php ├── toastr.php ├── trustedproxy.php └── view.php ├── database ├── .gitignore ├── factories │ └── UserFactory.php ├── migrations │ ├── 2014_10_12_000000_create_users_table.php │ ├── 2017_01_13_185020_create_oauth_identities_table.php │ ├── 2017_01_14_125908_create_orgs_table.php │ ├── 2017_02_13_175831_add_api_token_to_users_table.php │ ├── 2017_02_22_171006_add_pretty_name_to_orgs_table.php │ ├── 2017_03_07_192256_add_team_to_orgs_table.php │ ├── 2017_03_07_192847_create_teams_table.php │ ├── 2017_04_23_100458_add_custom_message_to_orgs_table.php │ └── 2017_04_28_163341_delete_oauth_identities_table.php └── seeds │ └── DatabaseSeeder.php ├── docs └── images │ ├── README.md │ └── orgmanager.png ├── package.json ├── phpunit.xml ├── public ├── .htaccess ├── css │ ├── 404.min.css │ └── 503.min.css ├── index.php ├── js │ ├── 404.min.js │ └── 503.min.js ├── robots.txt └── web.config ├── renovate.json ├── resources ├── js │ ├── app.js │ ├── components │ │ └── PasswordPanel.vue │ └── lib │ │ └── axios.js ├── lang │ └── en │ │ ├── alerts.php │ │ ├── empty.php │ │ ├── home.php │ │ ├── join.php │ │ ├── organizations.php │ │ └── pagination.php ├── less │ ├── new.less │ └── resets.less └── views │ ├── developer.blade.php │ ├── empty.blade.php │ ├── errors │ ├── 401.blade.php │ ├── 403.blade.php │ ├── 404.blade.php │ ├── 419.blade.php │ ├── 429.blade.php │ ├── 500.blade.php │ ├── 503.blade.php │ ├── illustrated-layout.blade.php │ ├── layout.blade.php │ └── minimal.blade.php │ ├── join.blade.php │ ├── landing.blade.php │ ├── layouts │ ├── app.blade.php │ ├── code │ │ ├── footer.blade.php │ │ └── head.blade.php │ └── new.blade.php │ ├── mail │ ├── reminder.blade.php │ └── welcome.blade.php │ ├── orgs.blade.php │ ├── settings.blade.php │ ├── teams.blade.php │ ├── token.blade.php │ └── vendor │ ├── 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 │ ├── notifications │ └── email.blade.php │ └── pagination │ ├── bootstrap-4.blade.php │ ├── default.blade.php │ ├── semantic-ui.blade.php │ ├── simple-bootstrap-4.blade.php │ └── simple-default.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 ├── tailwind.js ├── tests ├── CreatesApplication.php ├── Feature │ ├── ApiTest.php │ ├── DataTest.php │ ├── ExampleTest.php │ ├── JoinTest.php │ ├── SettingsTest.php │ └── StatusTest.php ├── TestCase.php └── Unit │ ├── ExampleTest.php │ ├── HelpersTest.php │ ├── JoinTest.php │ └── MarkdownTest.php ├── webpack.mix.js └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | trim_trailing_whitespace = false 2 | 3 | [*.yml] 4 | indent_size = 2 -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_KEY= 3 | APP_DEBUG=true 4 | APP_LOG_LEVEL=debug 5 | APP_URL=http://localhost 6 | 7 | DB_CONNECTION=mysql 8 | DB_HOST=127.0.0.1 9 | DB_PORT=3306 10 | DB_DATABASE=homestead 11 | DB_USERNAME=homestead 12 | DB_PASSWORD=secret 13 | 14 | BROADCAST_DRIVER=log 15 | CACHE_DRIVER=file 16 | QUEUE_CONNECTION=sync 17 | SESSION_DRIVER=file 18 | SESSION_LIFETIME=120 19 | 20 | REDIS_HOST=127.0.0.1 21 | REDIS_PASSWORD=null 22 | REDIS_PORT=6379 23 | 24 | MAIL_DRIVER=smtp 25 | MAIL_HOST=smtp.mailtrap.io 26 | MAIL_PORT=2525 27 | MAIL_USERNAME=null 28 | MAIL_PASSWORD=null 29 | MAIL_ENCRYPTION=null 30 | MAIL_FROM_ADDRESS= 31 | MAIL_FROM_NAME= 32 | 33 | PUSHER_APP_ID= 34 | PUSHER_APP_KEY= 35 | PUSHER_APP_SECRET= 36 | 37 | GITHUB_ID= 38 | GITHUB_SECRET= 39 | GITHUB_CALLBACK= 40 | 41 | GITHUB_AUTOJOINER_SECRET= 42 | 43 | RECAPTCHA_PUBLIC_KEY= 44 | RECAPTCHA_PRIVATE_KEY= 45 | 46 | SENTRY_DSN= -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.scss linguist-vendored 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "21:00" 8 | open-pull-requests-limit: 10 9 | versioning-strategy: increase 10 | ignore: 11 | - dependency-name: filp/whoops 12 | versions: 13 | - 2.10.0 14 | - 2.11.0 15 | - 2.12.0 16 | - 2.9.2 17 | - dependency-name: phpunit/phpunit 18 | versions: 19 | - 9.5.1 20 | - 9.5.2 21 | - 9.5.3 22 | -------------------------------------------------------------------------------- /.github/workflows/actions.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ development, master ] 6 | pull_request: 7 | branches: [ development, master ] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 2 # For codecov commit SHA detector 16 | 17 | - name: Cache node modules 18 | uses: actions/cache@v2 19 | id: cache-node 20 | with: 21 | path: node_modules 22 | key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }} 23 | restore-keys: | 24 | ${{ runner.os }}-yarn 25 | 26 | - name: Cache vendor directory 27 | uses: actions/cache@v2 28 | id: cache-php 29 | with: 30 | path: vendor 31 | key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }} 32 | restore-keys: | 33 | ${{ runner.os }}-composer 34 | 35 | - name: Setup PHP 36 | uses: shivammathur/setup-php@v2 37 | with: 38 | php-version: '7.3' 39 | 40 | - name: Setup Node.js 41 | uses: actions/setup-node@v1 42 | with: 43 | node-version: 12.x 44 | 45 | - name: Installing PHP dependencies 46 | if: steps.cache-php.outputs.cache-hit != 'true' 47 | run: | 48 | composer install --no-interaction --no-scripts 49 | 50 | - name: Installing Dependencies 51 | run: yarn 52 | 53 | - name: Running Tests 54 | run: vendor/bin/phpunit --coverage-text --coverage-clover=coverage.xml 55 | 56 | - run: bash <(curl -s https://codecov.io/bash) 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/storage 3 | /public/css/* 4 | /public/js/* 5 | /public/mix-manifest.json 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .phpunit.result.cache 10 | /.tmp 11 | Homestead.json 12 | Homestead.yaml 13 | .env.* 14 | !.env.example 15 | npm-debug.log 16 | yarn-error.log 17 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | tools: 2 | external_code_coverage: 3 | runs: 2 4 | timeout: 1000 5 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: vendor/bin/heroku-php-apache2 public/ 2 | -------------------------------------------------------------------------------- /app/Classes/ImageRenderer.php: -------------------------------------------------------------------------------- 1 | config = $configuration; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /app/Classes/Markdown.php: -------------------------------------------------------------------------------- 1 | addInlineRenderer(Image::class, new ImageRenderer()); 16 | 17 | $config = ['html_input' => 'escape']; 18 | 19 | $converter = new CommonMarkConverter($config, $environment); 20 | 21 | return $converter->convertToHtml($markdown); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/Console/Commands/AddToken.php: -------------------------------------------------------------------------------- 1 | get(); 42 | $total = User::where('api_token', '')->count(); 43 | if ($this->confirm('Do you want to add tokens for '.$total.' users?')) { 44 | $this->output->progressStart($total); 45 | foreach ($users as $user) { 46 | $user->api_token = str_random(60); 47 | $user->save(); 48 | $this->output->progressAdvance(); 49 | } 50 | $this->output->progressFinish(); 51 | $this->info('Successfully generated tokens for '.$total.' users.'); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Console/Commands/EncryptOrgPasswords.php: -------------------------------------------------------------------------------- 1 | get(); 42 | $total = Org::whereNotNull('password')->count(); 43 | if ($total == 0) { 44 | $this->error('There aren\'t any password-protected organizations.'); 45 | } else { 46 | if ($this->confirm('Do you want to add encrypt '.$total.' passwords?')) { 47 | $this->output->progressStart($total); 48 | foreach ($orgs as $org) { 49 | $org->password = bcrypt($org->password); 50 | $org->save(); 51 | $this->output->progressAdvance(); 52 | } 53 | $this->output->progressFinish(); 54 | $this->info('Successfully encrypted '.$total.' passwords.'); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Console/Commands/InstallAppCommand.php: -------------------------------------------------------------------------------- 1 | call('key:generate'); 31 | $this->call('config:cache'); 32 | $this->call('route:cache'); 33 | $this->call('vendor:publish'); 34 | $this->call('migrate', ['--force' => true]); 35 | $this->call('db:seed', ['--force' => true]); 36 | $this->call('cache:clear'); 37 | $this->call('storage:link'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Console/Commands/JoinOrg.php: -------------------------------------------------------------------------------- 1 | argument('org')); 43 | Github::authenticate($org->user->token, null, 'http_token'); 44 | if (config('app.env') != 'testing') { 45 | if ($this->isMember($org, $this->argument('username'))) { 46 | $this->error($this->argument('username').' is already a member of '.$org->name); 47 | 48 | return; 49 | } 50 | if (isset($org->team)) { 51 | Github::api('teams')->addMember($org->team->id, $this->argument('username')); 52 | } else { 53 | Github::api('organization')->members()->add($org->name, $this->argument('username')); 54 | } 55 | } 56 | $org->invitecount++; 57 | $org->save(); 58 | $this->info($this->argument('username').' was invited to '.$org->name); 59 | } 60 | 61 | protected function isMember(Org $org, $username): bool 62 | { 63 | Github::authenticate($org->user->token, null, 'http_token'); 64 | try { 65 | Github::api('organization')->members()->show($org->name, $username); 66 | } catch (Github\Exception\RuntimeException $e) { 67 | return false; 68 | } 69 | 70 | return true; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/Console/Commands/RemindUsers.php: -------------------------------------------------------------------------------- 1 | =', Carbon::now()->subWeek())->get(); 45 | if ($this->option('force') || $this->confirm('Do you want to send emails to '.$users->count().' users?', true)) { 46 | $this->output->progressStart($users->count()); 47 | foreach ($users as $user) { 48 | if (count($user->orgs) == 0) { 49 | Mail::to($user->email)->send(new Reminder($user)); 50 | } 51 | $this->output->progressAdvance(); 52 | } 53 | $this->output->progressFinish(); 54 | $this->info('Successfully sent emails for '.$users->count().' users.'); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/Console/Commands/UpdateOrg.php: -------------------------------------------------------------------------------- 1 | argument('org')); 43 | Github::authenticate($org->user->token, null, 'http_token'); 44 | $orgdata = Github::api('organization')->show($org->name); 45 | $org->name = $orgdata['login']; 46 | if (isset($orgdata['name'])) { 47 | $org->pretty_name = $orgdata['name']; 48 | } 49 | $org->description = $orgdata['description']; 50 | $org->avatar = 'https://avatars.githubusercontent.com/'.$orgdata['login']; 51 | $org->save(); 52 | $this->info($org->name.' updated successfully'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('orgmanager:remind-users', ['--force']) 29 | ->weekly() 30 | ->evenInMaintenanceMode(); 31 | } 32 | 33 | /** 34 | * Register the commands for the application. 35 | * 36 | * @return void 37 | */ 38 | protected function commands() 39 | { 40 | $this->load(__DIR__.'/Commands'); 41 | 42 | require base_path('routes/console.php'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/Exceptions/Handler.php: -------------------------------------------------------------------------------- 1 | bound('sentry') && $this->shouldReport($exception)) { 41 | app('sentry')->captureException($exception); 42 | } 43 | 44 | parent::report($exception); 45 | } 46 | 47 | /** 48 | * Render an exception into an HTTP response. 49 | * 50 | * @param \Illuminate\Http\Request $request 51 | * @param \Exception $exception 52 | * 53 | * @return \Illuminate\Http\Response 54 | */ 55 | public function render($request, Exception $exception) 56 | { 57 | return parent::render($request, $exception); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/HomeController.php: -------------------------------------------------------------------------------- 1 | user = url('api/user'); 15 | $endpoints->user_orgs = url('api/user/orgs'); 16 | $endpoints->org = url('api/org/{id}'); 17 | $endpoints->org_password = url('api/org/{id}'); 18 | $endpoints->update_org = url('api/org/{id}'); 19 | $endpoints->join = url('api/join/{id}'); 20 | $endpoints->stats = url('api/stats'); 21 | $endpoints->docs = url('http://docs.orgmanager.miguelpiedrafita.com'); 22 | 23 | return response()->json($endpoints); 24 | } 25 | 26 | public function org() 27 | { 28 | $endpoints = (object) []; 29 | $endpoints->org = url('api/org/{id}'); 30 | $endpoints->docs = url('http://docs.orgmanager.miguelpiedrafita.com'); 31 | 32 | return response()->json($endpoints); 33 | } 34 | 35 | public function stats() 36 | { 37 | $stats = (object) []; 38 | $stats->users = User::count(); 39 | $stats->orgs = Org::count(); 40 | $stats->invites = Org::sum('invitecount'); 41 | $stats->version = config('app.orgmanager.version'); 42 | 43 | return response()->json($stats); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/OrgController.php: -------------------------------------------------------------------------------- 1 | authorize('update', $org); 20 | if (! $request->has('password')) { 21 | abort(400); 22 | } 23 | $org->password = $request->input('password'); 24 | $org->save(); 25 | 26 | return $org->makeVisible('password')->makeHidden('user')->toArray(); 27 | } 28 | 29 | public function update(Org $org) 30 | { 31 | $this->authorize('update', $org); 32 | Artisan::call('orgmanager:updateorg', [ 33 | 'org' => $org->id, 34 | ]); 35 | 36 | return response(null, 204); 37 | } 38 | 39 | public function delete(Org $org) 40 | { 41 | $this->authorize('delete', $org); 42 | $org->delete(); 43 | 44 | return response(null, 204); 45 | } 46 | 47 | public function join(Request $request, Org $org) 48 | { 49 | // CONSIDER: Allow users to join other users organizations with their tokens? 50 | // (15/02/2017) Miguel Piedrafita 51 | $this->authorize('join', $org); 52 | if (! $request->has('username')) { 53 | abort(400); 54 | } 55 | Artisan::call('orgmanager:joinorg', [ 56 | 'org' => $org->id, 57 | 'username' => $request->input('username'), 58 | ]); 59 | 60 | return response(null, 204); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/Http/Controllers/Api/UserController.php: -------------------------------------------------------------------------------- 1 | orgs; 18 | } 19 | 20 | public function token() 21 | { 22 | $user = Auth::user(); 23 | $token = str_random(60); 24 | $user->api_token = $token; 25 | $user->save(); 26 | 27 | return response()->json($token); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/LoginController.php: -------------------------------------------------------------------------------- 1 | middleware('guest')->except('logout'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/RegisterController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 40 | } 41 | 42 | /** 43 | * Get a validator for an incoming registration request. 44 | * 45 | * @param array $data 46 | * 47 | * @return \Illuminate\Contracts\Validation\Validator 48 | */ 49 | protected function validator(array $data) 50 | { 51 | return Validator::make($data, [ 52 | 'name' => ['required', 'max:255'], 53 | 'email' => ['required', 'email', 'max:255', ':users'], 54 | 'password' => ['required', 'min:6', 'confirmed'], 55 | ]); 56 | } 57 | 58 | /** 59 | * Create a new user instance after a valid registration. 60 | * 61 | * @param array $data 62 | * 63 | * @return \App\User 64 | */ 65 | protected function create(array $data) 66 | { 67 | return User::create([ 68 | 'name' => $data['name'], 69 | 'email' => $data['email'], 70 | 'password' => bcrypt($data['password']), 71 | ]); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/Http/Controllers/AutoJoinerController.php: -------------------------------------------------------------------------------- 1 | requestSignatureIsValid(), 403); 14 | 15 | if ($request->header('X-Github-Event') != 'pull_request') { 16 | return 'Not a Pull Request'; 17 | } 18 | $org = Org::findOrFail($this->getOrgId($request)); 19 | if ($request->action != 'closed' || $request->pull_request['merged_at'] == null) { 20 | return 'Pull Request was not merged'; 21 | } 22 | Artisan::call('orgmanager:joinorg', [ 23 | 'org' => $org->id, 24 | 'username' => ($request->pull_request['user'])['login'], 25 | ]); 26 | 27 | return Artisan::output(); 28 | } 29 | 30 | protected function requestSignatureIsValid() : bool 31 | { 32 | $gitHubSignature = request()->header('X-Hub-Signature'); 33 | [$usedAlgorithm, $gitHubHash] = explode('=', $gitHubSignature, 2); 34 | if ($usedAlgorithm !== config('auth.github_hmac', 'sha1')) { 35 | return 'Wrong HMAC algorithm'; 36 | } 37 | $payload = file_get_contents('php://input'); 38 | $calculatedHash = hash_hmac($usedAlgorithm, $payload, config('auth.github_secret')); 39 | 40 | return hash_equals($calculatedHash, $gitHubHash); 41 | } 42 | 43 | protected function getOrgId(Request $request) : int 44 | { 45 | return ((($request->pull_request['base'])['repo'])['owner'])['id']; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 13 | } 14 | 15 | public function index() 16 | { 17 | $orgs = Org::where('userid', '=', Auth::id())->get(); 18 | 19 | return count($orgs) == 0 ? view('empty') : view('orgs')->withOrgs($orgs); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Controllers/DeveloperController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 12 | } 13 | 14 | public function deleteToken() 15 | { 16 | $user = Auth::user(); 17 | $user->api_token = str_random(60); 18 | $user->save(); 19 | 20 | return redirect()->back()->withSuccess('Your token has been regenerated successfully.'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/Http/Controllers/GithubController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 15 | } 16 | 17 | public function syncOrgs() 18 | { 19 | $this->listOrgs(); 20 | $this->checkPerm(); 21 | 22 | return redirect('dashboard')->withSuccess(trans('alerts.alldb')); 23 | } 24 | 25 | public function syncOrg(Org $org) 26 | { 27 | Artisan::call('orgmanager:updateorg', [ 28 | 'org' => $org->id, 29 | ]); 30 | $this->checkPerm(); 31 | 32 | return redirect()->route('org', $org)->withSuccess(trans('alerts.sync')); 33 | } 34 | 35 | public function listOrgs() 36 | { 37 | Github::authenticate(Auth::user()->token, null, 'http_token'); 38 | $orgs = Github::api('user')->orgs(); 39 | $this->storeOrgs($orgs); 40 | } 41 | 42 | public function storeOrgs($orgs) 43 | { 44 | foreach ($orgs as $organization) { 45 | if (Org::where('id', '=', $organization['id'])->exists()) { 46 | continue; 47 | } 48 | if (Org::find($organization['id']) == null) { 49 | $this->saveNewOrg($organization); 50 | } 51 | } 52 | } 53 | 54 | public function checkPerm() 55 | { 56 | Github::authenticate(Auth::user()->token, null, 'http_token'); 57 | $orgs = Org::where('userid', '=', Auth::id())->get(); 58 | foreach ($orgs as $organization) { 59 | if ($organization->role == 'admin') { 60 | continue; 61 | } 62 | $membership = GitHub::api('organizations')->members()->member($organization->name, $organization->user->github_username); 63 | $organization->role = $membership['role']; 64 | if ($membership['role'] == 'admin') { 65 | $organization->save(); 66 | } else { 67 | $organization->delete(); 68 | } 69 | } 70 | } 71 | 72 | /** 73 | * @param $organization 74 | */ 75 | private function saveNewOrg($organization) 76 | { 77 | $org = new Org(); 78 | $org->id = $organization['id']; 79 | $org->name = $organization['login']; 80 | $org->url = $organization['url']; 81 | $org->description = $organization['description']; 82 | $org->avatar = 'https://avatars.githubusercontent.com/'.$organization['login']; 83 | $org->userid = Auth::id(); 84 | $org->save(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/Http/Controllers/JoinController.php: -------------------------------------------------------------------------------- 1 | with('org', $org); 21 | } 22 | 23 | public function inviteUser(JoinOrgRequest $request, Org $org) 24 | { 25 | $validation = $this->validateRequest($request, $org); 26 | if ($validation) { 27 | return $validation; 28 | } 29 | 30 | return Socialite::driver('github')->setScopes([])->redirectUrl(route('join.callback', $org))->redirect(); 31 | } 32 | 33 | public function callback(Request $request, Org $org) 34 | { 35 | try { 36 | $user = Socialite::driver('github')->user(); 37 | } catch (InvalidStateException $e) { 38 | return redirect('join/'.$org->id)->withErrors('Something went wrong when authenticating with GitHub. Please try again later or open an issue.'); 39 | } 40 | if ($this->isMember($org, $user = $user->getNickname())) { 41 | return redirect('join/'.$org->id)->withErrors(trans('alerts.member')); 42 | } 43 | 44 | Artisan::call('orgmanager:joinorg', [ 45 | 'org' => $org->id, 46 | 'username' => $user, 47 | ]); 48 | 49 | return redirect(url("https://github.com/orgs/$org->name/invitation/")); 50 | } 51 | 52 | public function redirect($name) 53 | { 54 | $org = Org::where('name', $name)->firstOrFail(); 55 | 56 | return redirect('join/'.$org->id); 57 | } 58 | 59 | protected function isMember(Org $org, $username) 60 | { 61 | Github::authenticate($org->user->token, null, 'http_token'); 62 | try { 63 | Github::api('organization')->members()->show($org->name, $username); 64 | } catch (Github\Exception\RuntimeException $e) { 65 | return false; 66 | } 67 | 68 | return true; 69 | } 70 | 71 | protected function validateRequest(Request $request, Org $org) 72 | { 73 | if (! $this->captchaCheck($request)) { 74 | return redirect('join/'.$org->id)->withErrors('You need to prove you are not a robot!'); 75 | } 76 | if ($org->password && trim($org->password) != '') { 77 | if (! $request->has('org_password')) { 78 | return redirect('join/'.$org->id)->withErrors(trans('alerts.passwd1')); 79 | } 80 | if (! password_verify($request->org_password, $org->password)) { 81 | return redirect('join/'.$org->id)->withErrors(trans('alerts.passwd2')); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/Http/Controllers/LoginController.php: -------------------------------------------------------------------------------- 1 | middleware('guest', ['except' => 'logoutUser']); 17 | } 18 | 19 | public function authorizeUser() 20 | { 21 | return Socialite::driver('github')->scopes(['admin:org'])->redirect(); 22 | } 23 | 24 | public function loginUser() 25 | { 26 | $github = Socialite::driver('github')->user(); 27 | $user = User::where('github_username', '=', $github->getNickname())->first(); 28 | if ($user === null) { 29 | $user = User::create([ 30 | 'name' => $github->getName(), 31 | 'email' => $github->getEmail(), 32 | 'github_username' => $github->getNickname(), 33 | 'token' => $github->token, 34 | 'api_token' => str_random(60), 35 | ]); 36 | // Mail::to($user->email)->send(new WelcomeUser()); 37 | Auth::login($user); 38 | 39 | return redirect()->intended('dashboard')->withSuccess(trans('alerts.loggedin')); 40 | } 41 | $user->update([ 42 | 'name' => $github->getName(), 43 | 'email' => $github->getEmail(), 44 | 'github_username' => $github->getNickname(), 45 | 'token' => $github->token, 46 | ]); 47 | Auth::login($user); 48 | 49 | return redirect()->intended('dashboard')->withSuccess(trans('alerts.loggedin')); 50 | } 51 | 52 | public function logoutUser() 53 | { 54 | Auth::logout(); 55 | 56 | return redirect('')->withSuccess('Sucessfully logged out!'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/Http/Controllers/OrgController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 17 | } 18 | 19 | public function index(Org $org) 20 | { 21 | $this->authorize('update', $org); 22 | 23 | return view('settings', compact('org')); 24 | } 25 | 26 | public function password(Org $org) 27 | { 28 | $this->authorize('update', $org); 29 | 30 | request()->validate([ 31 | 'password' => 'required|string' 32 | ]); 33 | 34 | return tap($org, function($org) { 35 | $org->update(['password' => Hash::make(request('password'))]); 36 | }); 37 | } 38 | 39 | public function removePassword(Org $org) 40 | { 41 | $this->authorize('update', $org); 42 | 43 | return tap($org, function($org) { 44 | $org->update(['password' => null]); 45 | }); 46 | } 47 | 48 | public function update(Org $org) 49 | { 50 | $this->authorize('update', $org); 51 | Artisan::call('orgmanager:updateorg', [ 52 | 'org' => $org->id, 53 | ]); 54 | 55 | return redirect('org/'.$org->id)->withSuccess('The organization was successfully updated.'); 56 | } 57 | 58 | public function delete(Org $org) 59 | { 60 | $this->authorize('delete', $org); 61 | $org->delete(); 62 | 63 | return redirect('dashboard')->withSuccess('The organization was successfully deleted.'); 64 | } 65 | 66 | public function message(CustomMessageRequest $request, Org $org) 67 | { 68 | $this->authorize('update', $org); 69 | 70 | $org->custom_message = $request->input('message'); 71 | 72 | $org->save(); 73 | 74 | return redirect('org/'.$org->id)->withSuccess('The message was successfully updated.'); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/Http/Controllers/TeamController.php: -------------------------------------------------------------------------------- 1 | authorize('update', $org); 15 | 16 | return view('teams', compact('org')); 17 | } 18 | 19 | public function syncTeams(Org $org) 20 | { 21 | $this->authorize('update', $org); 22 | Github::authenticate($org->user->token, null, 'http_token'); 23 | $teams = Github::api('teams')->all($org->name); 24 | foreach ($teams as $data) { 25 | if (! Team::where('id', '=', $data['id'])->exists()) { 26 | $team = new Team(); 27 | $team->id = $data['id']; 28 | $team->org_id = $org->id; 29 | $team->description = $data['description']; 30 | $team->privacy = $data['privacy']; 31 | $team->name = $data['name']; 32 | $team->permission = $data['permission']; 33 | $team->save(); 34 | } else { 35 | $team = Team::findOrFail($data['id']); 36 | $team->description = $data['description']; 37 | $team->privacy = $data['privacy']; 38 | $team->name = $data['name']; 39 | $team->permission = $data['permission']; 40 | $team->save(); 41 | } 42 | } 43 | 44 | return redirect('org/'.$org->id.'/teams'); 45 | } 46 | 47 | public function setTeam(SetTeamRequest $request, Org $org) 48 | { 49 | $this->authorize('update', $org); 50 | $team = Team::findOrFail($request->input('team_id')); 51 | if ($team->org_id != $org->id) { 52 | return redirect()->back()->withInput()->withErrors(['The specified team doesn\'t belong to this organization.']); 53 | } 54 | $org->team_id = $team->id; 55 | $org->save(); 56 | 57 | return redirect('org/'.$org->id.'/teams'); 58 | } 59 | 60 | public function deleteTeams(Org $org) 61 | { 62 | $this->authorize('update', $org); 63 | foreach ($org->teams as $team) { 64 | $team->delete(); 65 | } 66 | $org->team_id = null; 67 | $org->save(); 68 | 69 | return redirect('org/'.$org->id); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | [ 31 | \App\Http\Middleware\EncryptCookies::class, 32 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class, 33 | \Illuminate\Session\Middleware\StartSession::class, 34 | // \Illuminate\Session\Middleware\AuthenticateSession::class, 35 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 36 | \App\Http\Middleware\VerifyCsrfToken::class, 37 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 38 | \App\Http\Middleware\HttpsMiddleware::class, 39 | ], 40 | 41 | 'api' => [ 42 | 'throttle:60,1', 43 | 'bindings', 44 | 'auth:api', 45 | ], 46 | ]; 47 | 48 | /** 49 | * The application's route middleware. 50 | * 51 | * These middleware may be assigned to groups or used individually. 52 | * 53 | * @var array 54 | */ 55 | protected $routeMiddleware = [ 56 | 'auth' => \App\Http\Middleware\Authenticate::class, 57 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class, 58 | 'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class, 59 | 'can' => \Illuminate\Auth\Middleware\Authorize::class, 60 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class, 61 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 62 | ]; 63 | 64 | /** 65 | * The priority-sorted list of middleware. 66 | * 67 | * This forces non-global middleware to always be in the given order. 68 | * 69 | * @var array 70 | */ 71 | protected $middlewarePriority = [ 72 | \Illuminate\Session\Middleware\StartSession::class, 73 | \Illuminate\View\Middleware\ShareErrorsFromSession::class, 74 | \App\Http\Middleware\Authenticate::class, 75 | \Illuminate\Session\Middleware\AuthenticateSession::class, 76 | \Illuminate\Routing\Middleware\SubstituteBindings::class, 77 | \Illuminate\Auth\Middleware\Authorize::class, 78 | ]; 79 | } 80 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 18 | return route('login'); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/Http/Middleware/EncryptCookies.php: -------------------------------------------------------------------------------- 1 | secure() && env('APP_ENV') === 'prod') { 20 | return redirect()->secure($request->getRequestUri()); 21 | } 22 | 23 | return $next($request); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | check()) { 22 | return redirect()->route('dashboard'); 23 | } 24 | 25 | return $next($request); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/Http/Middleware/TrimStrings.php: -------------------------------------------------------------------------------- 1 | sanitize(); 27 | 28 | return [ 29 | 'message' => 'required', 30 | ]; 31 | } 32 | 33 | public function sanitize() 34 | { 35 | $input = $this->all(); 36 | $message = $input['message']; 37 | if (! empty($message)) { 38 | $message = preg_replace('#(.*?)#is', '', $message); 39 | $message = htmlspecialchars(strip_tags($message)); 40 | $message = str_replace('\'', '"', $message); 41 | $input['message'] = $message; 42 | } 43 | $this->replace($input); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/Http/Requests/JoinOrgRequest.php: -------------------------------------------------------------------------------- 1 | 'required', 28 | ]; 29 | } 30 | 31 | public function messages() 32 | { 33 | return [ 34 | 'g-recaptcha-response.required' => 'You need to prove you are not a robot.', 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/Http/Requests/SetTeamRequest.php: -------------------------------------------------------------------------------- 1 | 'required|filled|integer|exists:teams,id', 28 | ]; 29 | } 30 | 31 | public function messages() 32 | { 33 | return [ 34 | 'team_id.required' => 'You need to select a team.', 35 | 'team_id.integer' => 'The Team ID must be a number.', 36 | 'team_id.exists' => "The selected team doesn't exist.", 37 | ]; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/Mail/RemindUsers.php: -------------------------------------------------------------------------------- 1 | user = $user; 22 | } 23 | 24 | /** 25 | * Build the message. 26 | * 27 | * @return $this 28 | */ 29 | public function build() 30 | { 31 | return $this->view('mail.reminder'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Mail/WelcomeUser.php: -------------------------------------------------------------------------------- 1 | subject('Welcome to '.config('app.name')) 31 | ->view('mail.welcome'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /app/Org.php: -------------------------------------------------------------------------------- 1 | belongsTo(User::class, 'userid', 'id'); 20 | } 21 | 22 | public function teams() 23 | { 24 | return $this->hasMany(Team::class); 25 | } 26 | 27 | public function team() 28 | { 29 | return $this->belongsTo(Team::class); 30 | } 31 | 32 | public function hasPassword() 33 | { 34 | return ! is_null($this->password); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/Policies/OrgPolicy.php: -------------------------------------------------------------------------------- 1 | id === $org->user->id; 44 | } 45 | 46 | /** 47 | * Determine whether the user can delete the org. 48 | * 49 | * @param \App\User $user 50 | * @param \App\Org $org 51 | * 52 | * @return bool 53 | */ 54 | public function delete(User $user, Org $org) 55 | { 56 | return $user->id === $org->user->id; 57 | } 58 | 59 | /** 60 | * Determine whether the user can join the org. 61 | * 62 | * @param \App\User $user 63 | * @param \App\Org $org 64 | * 65 | * @return bool 66 | */ 67 | public function join(User $user, Org $org) 68 | { 69 | return $user->id === $org->user->id; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | OrgPolicy::class, 18 | ]; 19 | 20 | /** 21 | * Register any authentication / authorization services. 22 | * 23 | * @return void 24 | */ 25 | public function boot() 26 | { 27 | $this->registerPolicies(); 28 | 29 | // 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/Providers/BroadcastServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'App\Listeners\EventListener', 18 | ], 19 | ]; 20 | 21 | /** 22 | * Register any events for your application. 23 | * 24 | * @return void 25 | */ 26 | public function boot() 27 | { 28 | parent::boot(); 29 | 30 | // 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapApiRoutes(); 39 | 40 | $this->mapWebRoutes(); 41 | 42 | // 43 | } 44 | 45 | /** 46 | * Define the "web" routes for the application. 47 | * 48 | * These routes all receive session state, CSRF protection, etc. 49 | * 50 | * @return void 51 | */ 52 | protected function mapWebRoutes() 53 | { 54 | Route::middleware('web') 55 | ->namespace($this->namespace) 56 | ->group(base_path('routes/web.php')); 57 | } 58 | 59 | /** 60 | * Define the "api" routes for the application. 61 | * 62 | * These routes are typically stateless. 63 | * 64 | * @return void 65 | */ 66 | protected function mapApiRoutes() 67 | { 68 | Route::prefix('api') 69 | ->middleware('api') 70 | ->namespace($this->namespace) 71 | ->group(base_path('routes/api.php')); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/Team.php: -------------------------------------------------------------------------------- 1 | belongsTo(Org::class); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/Traits/CaptchaTrait.php: -------------------------------------------------------------------------------- 1 | ip(); 13 | $secret = config('recaptcha.secret'); 14 | 15 | $recaptcha = new ReCaptcha($secret); 16 | $resp = $recaptcha->verify($request->input('g-recaptcha-response'), $remoteip); 17 | if ($resp->isSuccess()) { 18 | return 1; 19 | } else { 20 | return 0; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/User.php: -------------------------------------------------------------------------------- 1 | hasMany(Org::class, 'userid'); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/helpers.php: -------------------------------------------------------------------------------- 1 | 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 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "orgmanager/orgmanager", 3 | "type": "project", 4 | "description": "Supercharge you GitHub organizations!", 5 | "keywords": [ 6 | "orgmanager", 7 | "github", 8 | "organizations", 9 | "supercharge", 10 | "invite", 11 | "invites", 12 | "php", 13 | "laravel" 14 | ], 15 | "license": "MPL-2.0", 16 | "require": { 17 | "php": "~7.3.0", 18 | "fideloper/proxy": "~4.4", 19 | "google/recaptcha": "~1.1", 20 | "graham-campbell/github": "^8.9", 21 | "guzzlehttp/guzzle": "^6.2", 22 | "knplabs/github-api": "^2.3", 23 | "laravel/framework": "^5.8.0", 24 | "laravel/socialite": "^4.4", 25 | "laravel/tinker": "^1.0", 26 | "league/commonmark": "^0.18.0", 27 | "orgmanager/orgmanager-logos": "^1.0", 28 | "php-http/cache-plugin": "^1.7", 29 | "php-http/guzzle6-adapter": "^1.1", 30 | "sentry/sentry-laravel": "^0.8.0" 31 | }, 32 | "require-dev": { 33 | "beyondcode/laravel-dump-server": "^1.0", 34 | "filp/whoops": "~2.14", 35 | "fzaninotto/faker": "~1.9", 36 | "mockery/mockery": "^1.5", 37 | "phpunit/phpunit": "~8.0" 38 | }, 39 | "config": { 40 | "optimize-autoloader": true, 41 | "preferred-install": "dist", 42 | "sort-packages": true 43 | }, 44 | "extra": { 45 | "laravel": { 46 | "dont-discover": [] 47 | } 48 | }, 49 | "autoload": { 50 | "psr-4": { 51 | "App\\": "app/" 52 | }, 53 | "classmap": [ 54 | "database/seeds", 55 | "database/factories" 56 | ], 57 | "files": [ 58 | "app/helpers.php" 59 | ] 60 | }, 61 | "autoload-dev": { 62 | "psr-4": { 63 | "Tests\\": "tests/" 64 | } 65 | }, 66 | "scripts": { 67 | "post-root-package-install": [ 68 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 69 | ], 70 | "post-create-project-cmd": [ 71 | "@php artisan key:generate --ansi" 72 | ], 73 | "post-install-cmd": [ 74 | "@php artisan migrate --force" 75 | ], 76 | "post-autoload-dump": [ 77 | "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump", 78 | "@php artisan package:discover --ansi" 79 | ], 80 | "test": "vendor/bin/phpunit" 81 | }, 82 | "minimum-stability": "dev", 83 | "prefer-stable": true 84 | } 85 | -------------------------------------------------------------------------------- /config/auth.php: -------------------------------------------------------------------------------- 1 | [ 17 | 'guard' => 'web', 18 | 'passwords' => 'users', 19 | ], 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Authentication Guards 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Next, you may define every authentication guard for your application. 27 | | Of course, a great default configuration has been defined for you 28 | | here which uses session storage and the Eloquent user provider. 29 | | 30 | | All authentication drivers have a user provider. This defines how the 31 | | users are actually retrieved out of your database or other storage 32 | | mechanisms used by this application to persist your user's data. 33 | | 34 | | Supported: "session", "token" 35 | | 36 | */ 37 | 38 | 'guards' => [ 39 | 'web' => [ 40 | 'driver' => 'session', 41 | 'provider' => 'users', 42 | ], 43 | 44 | 'api' => [ 45 | 'driver' => 'token', 46 | 'provider' => 'users', 47 | 'hash' => false, 48 | ], 49 | ], 50 | 51 | /* 52 | |-------------------------------------------------------------------------- 53 | | User Providers 54 | |-------------------------------------------------------------------------- 55 | | 56 | | All authentication drivers have a user provider. This defines how the 57 | | users are actually retrieved out of your database or other storage 58 | | mechanisms used by this application to persist your user's data. 59 | | 60 | | If you have multiple user tables or models you may configure multiple 61 | | sources which represent each model / table. These sources may then 62 | | be assigned to any extra authentication guards you have defined. 63 | | 64 | | Supported: "database", "eloquent" 65 | | 66 | */ 67 | 68 | 'providers' => [ 69 | 'users' => [ 70 | 'driver' => 'eloquent', 71 | 'model' => App\User::class, 72 | ], 73 | 74 | // 'users' => [ 75 | // 'driver' => 'database', 76 | // 'table' => 'users', 77 | // ], 78 | ], 79 | 80 | /* 81 | |-------------------------------------------------------------------------- 82 | | Resetting Passwords 83 | |-------------------------------------------------------------------------- 84 | | 85 | | You may specify multiple password reset configurations if you have more 86 | | than one user table or model in the application and you want to have 87 | | separate password reset settings based on the specific user types. 88 | | 89 | | The expire time is the number of minutes that the reset token should be 90 | | considered valid. This security feature keeps tokens short-lived so 91 | | they have less time to be guessed. You may change this as needed. 92 | | 93 | */ 94 | 95 | 'passwords' => [ 96 | 'users' => [ 97 | 'provider' => 'users', 98 | 'table' => 'password_resets', 99 | 'expire' => 60, 100 | ], 101 | ], 102 | 103 | /* 104 | |-------------------------------------------------------------------------- 105 | | GitHub Secret 106 | |-------------------------------------------------------------------------- 107 | | 108 | | You may specify a secret so we can check the data comes from GitHub 109 | | and prevent attacks. 110 | | 111 | */ 112 | 113 | 'github_secret' => env('GITHUB_AUTOJOINER_SECRET'), 114 | 115 | ]; 116 | -------------------------------------------------------------------------------- /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/cache.php: -------------------------------------------------------------------------------- 1 | env('CACHE_DRIVER', 'file'), 22 | 23 | /* 24 | |-------------------------------------------------------------------------- 25 | | Cache Stores 26 | |-------------------------------------------------------------------------- 27 | | 28 | | Here you may define all of the cache "stores" for your application as 29 | | well as their drivers. You may even define multiple stores for the 30 | | same cache driver to group types of items stored in your caches. 31 | | 32 | */ 33 | 34 | 'stores' => [ 35 | 36 | 'apc' => [ 37 | 'driver' => 'apc', 38 | ], 39 | 40 | 'array' => [ 41 | 'driver' => 'array', 42 | ], 43 | 44 | 'database' => [ 45 | 'driver' => 'database', 46 | 'table' => 'cache', 47 | 'connection' => null, 48 | ], 49 | 50 | 'file' => [ 51 | 'driver' => 'file', 52 | 'path' => storage_path('framework/cache/data'), 53 | ], 54 | 55 | 'memcached' => [ 56 | 'driver' => 'memcached', 57 | 'persistent_id' => env('MEMCACHED_PERSISTENT_ID'), 58 | 'sasl' => [ 59 | env('MEMCACHED_USERNAME'), 60 | env('MEMCACHED_PASSWORD'), 61 | ], 62 | 'options' => [ 63 | // Memcached::OPT_CONNECT_TIMEOUT => 2000, 64 | ], 65 | 'servers' => [ 66 | [ 67 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'), 68 | 'port' => env('MEMCACHED_PORT', 11211), 69 | 'weight' => 100, 70 | ], 71 | ], 72 | ], 73 | 74 | 'redis' => [ 75 | 'driver' => 'redis', 76 | 'connection' => 'cache', 77 | ], 78 | 79 | 'dynamodb' => [ 80 | 'driver' => 'dynamodb', 81 | 'key' => env('AWS_ACCESS_KEY_ID'), 82 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 83 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 84 | 'table' => env('DYNAMODB_CACHE_TABLE', 'cache'), 85 | 'endpoint' => env('DYNAMODB_ENDPOINT'), 86 | ], 87 | 88 | ], 89 | 90 | /* 91 | |-------------------------------------------------------------------------- 92 | | Cache Key Prefix 93 | |-------------------------------------------------------------------------- 94 | | 95 | | When utilizing a RAM based store such as APC or Memcached, there might 96 | | be other applications utilizing the same cache. So, we'll specify a 97 | | value to get prefixed to all our keys so we can avoid collisions. 98 | | 99 | */ 100 | 101 | 'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME', 'laravel'), '_').'_cache'), 102 | 103 | ]; 104 | -------------------------------------------------------------------------------- /config/debug-server.php: -------------------------------------------------------------------------------- 1 | 'tcp://127.0.0.1:9912', 8 | ]; 9 | -------------------------------------------------------------------------------- /config/filesystems.php: -------------------------------------------------------------------------------- 1 | env('FILESYSTEM_DRIVER', 'local'), 17 | 18 | /* 19 | |-------------------------------------------------------------------------- 20 | | Default Cloud Filesystem Disk 21 | |-------------------------------------------------------------------------- 22 | | 23 | | Many applications store files both locally and in the cloud. For this 24 | | reason, you may specify a default "cloud" driver here. This driver 25 | | will be bound as the Cloud disk implementation in the container. 26 | | 27 | */ 28 | 29 | 'cloud' => env('FILESYSTEM_CLOUD', 's3'), 30 | 31 | /* 32 | |-------------------------------------------------------------------------- 33 | | Filesystem Disks 34 | |-------------------------------------------------------------------------- 35 | | 36 | | Here you may configure as many filesystem "disks" as you wish, and you 37 | | may even configure multiple disks of the same driver. Defaults have 38 | | been setup for each driver as an example of the required options. 39 | | 40 | | Supported Drivers: "local", "ftp", "s3", "rackspace" 41 | | 42 | */ 43 | 44 | 'disks' => [ 45 | 46 | 'local' => [ 47 | 'driver' => 'local', 48 | 'root' => storage_path('app'), 49 | ], 50 | 51 | 'public' => [ 52 | 'driver' => 'local', 53 | 'root' => storage_path('app/public'), 54 | 'url' => env('APP_URL').'/storage', 55 | 'visibility' => 'public', 56 | ], 57 | 58 | 's3' => [ 59 | 'driver' => 's3', 60 | 'key' => env('AWS_KEY'), 61 | 'secret' => env('AWS_SECRET'), 62 | 'region' => env('AWS_REGION'), 63 | 'bucket' => env('AWS_BUCKET'), 64 | ], 65 | 66 | ], 67 | 68 | ]; 69 | -------------------------------------------------------------------------------- /config/github.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | return [ 13 | 14 | /* 15 | |-------------------------------------------------------------------------- 16 | | Default Connection Name 17 | |-------------------------------------------------------------------------- 18 | | 19 | | Here you may specify which of the connections below you wish to use as 20 | | your default connection for all work. Of course, you may use many 21 | | connections at once using the manager class. 22 | | 23 | */ 24 | 25 | 'default' => 'main', 26 | 27 | /* 28 | |-------------------------------------------------------------------------- 29 | | GitHub Connections 30 | |-------------------------------------------------------------------------- 31 | | 32 | | Here are each of the connections setup for your application. Example 33 | | configuration has been included, but you may add as many connections as 34 | | you would like. Note that the 3 supported authentication methods are: 35 | | "application", "password", and "token". 36 | | 37 | */ 38 | 39 | 'connections' => [ 40 | 41 | 'main' => [ 42 | 'token' => 'your-token', 43 | 'method' => 'token', 44 | // 'backoff' => false, 45 | // 'cache' => false, 46 | // 'version' => 'v3', 47 | // 'enterprise' => false, 48 | ], 49 | 50 | 'alternative' => [ 51 | 'clientId' => 'your-client-id', 52 | 'clientSecret' => 'your-client-secret', 53 | 'method' => 'application', 54 | // 'backoff' => false, 55 | // 'cache' => false, 56 | // 'version' => 'v3', 57 | // 'enterprise' => false, 58 | ], 59 | 60 | 'other' => [ 61 | 'username' => 'your-username', 62 | 'password' => 'your-password', 63 | 'method' => 'password', 64 | // 'backoff' => false, 65 | // 'cache' => false, 66 | // 'version' => 'v3', 67 | // 'enterprise' => false, 68 | ], 69 | 70 | ], 71 | 72 | ]; 73 | -------------------------------------------------------------------------------- /config/hashing.php: -------------------------------------------------------------------------------- 1 | 'bcrypt', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /config/logging.php: -------------------------------------------------------------------------------- 1 | env('LOG_CHANNEL', 'stack'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Log Channels 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may configure the log channels for your application. Out of 27 | | the box, Laravel uses the Monolog PHP logging library. This gives 28 | | you a variety of powerful log handlers / formatters to utilize. 29 | | 30 | | Available Drivers: "single", "daily", "slack", "syslog", 31 | | "errorlog", "custom", "stack" 32 | | 33 | */ 34 | 35 | 'channels' => [ 36 | 'stack' => [ 37 | 'driver' => 'stack', 38 | 'channels' => ['single'], 39 | 'ignore_exceptions' => false, 40 | ], 41 | 42 | 'single' => [ 43 | 'driver' => 'single', 44 | 'path' => storage_path('logs/laravel.log'), 45 | 'level' => 'debug', 46 | ], 47 | 48 | 'daily' => [ 49 | 'driver' => 'daily', 50 | 'path' => storage_path('logs/laravel.log'), 51 | 'level' => 'debug', 52 | 'days' => 14, 53 | ], 54 | 55 | 'slack' => [ 56 | 'driver' => 'slack', 57 | 'url' => env('LOG_SLACK_WEBHOOK_URL'), 58 | 'username' => 'Laravel Log', 59 | 'emoji' => ':boom:', 60 | 'level' => 'critical', 61 | ], 62 | 63 | 'papertrail' => [ 64 | 'driver' => 'monolog', 65 | 'level' => 'debug', 66 | 'handler' => SyslogUdpHandler::class, 67 | 'handler_with' => [ 68 | 'host' => env('PAPERTRAIL_URL'), 69 | 'port' => env('PAPERTRAIL_PORT'), 70 | ], 71 | ], 72 | 73 | 'stderr' => [ 74 | 'driver' => 'monolog', 75 | 'handler' => StreamHandler::class, 76 | 'formatter' => env('LOG_STDERR_FORMATTER'), 77 | 'with' => [ 78 | 'stream' => 'php://stderr', 79 | ], 80 | ], 81 | 82 | 'syslog' => [ 83 | 'driver' => 'syslog', 84 | 'level' => 'debug', 85 | ], 86 | 87 | 'errorlog' => [ 88 | 'driver' => 'errorlog', 89 | 'level' => 'debug', 90 | ], 91 | ], 92 | 93 | ]; 94 | -------------------------------------------------------------------------------- /config/mail.php: -------------------------------------------------------------------------------- 1 | env('MAIL_DRIVER', 'smtp'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | SMTP Host Address 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may provide the host address of the SMTP server used by your 27 | | applications. A default option is provided that is compatible with 28 | | the Mailgun mail service which will provide reliable deliveries. 29 | | 30 | */ 31 | 32 | 'host' => env('MAIL_HOST', 'smtp.mailgun.org'), 33 | 34 | /* 35 | |-------------------------------------------------------------------------- 36 | | SMTP Host Port 37 | |-------------------------------------------------------------------------- 38 | | 39 | | This is the SMTP port used by your application to deliver e-mails to 40 | | users of the application. Like the host we have set this value to 41 | | stay compatible with the Mailgun e-mail application by default. 42 | | 43 | */ 44 | 45 | 'port' => env('MAIL_PORT', 587), 46 | 47 | /* 48 | |-------------------------------------------------------------------------- 49 | | Global "From" Address 50 | |-------------------------------------------------------------------------- 51 | | 52 | | You may wish for all e-mails sent by your application to be sent from 53 | | the same address. Here, you may specify a name and address that is 54 | | used globally for all e-mails that are sent by your application. 55 | | 56 | */ 57 | 58 | 'from' => [ 59 | 'address' => env('MAIL_FROM_ADDRESS', 'hello@example.com'), 60 | 'name' => env('MAIL_FROM_NAME', 'Example'), 61 | ], 62 | 63 | /* 64 | |-------------------------------------------------------------------------- 65 | | E-Mail Encryption Protocol 66 | |-------------------------------------------------------------------------- 67 | | 68 | | Here you may specify the encryption protocol that should be used when 69 | | the application send e-mail messages. A sensible default using the 70 | | transport layer security protocol should provide great security. 71 | | 72 | */ 73 | 74 | 'encryption' => env('MAIL_ENCRYPTION', 'tls'), 75 | 76 | /* 77 | |-------------------------------------------------------------------------- 78 | | SMTP Server Username 79 | |-------------------------------------------------------------------------- 80 | | 81 | | If your SMTP server requires a username for authentication, you should 82 | | set it here. This will get used to authenticate with your server on 83 | | connection. You may also set the "password" value below this one. 84 | | 85 | */ 86 | 87 | 'username' => env('MAIL_USERNAME'), 88 | 89 | 'password' => env('MAIL_PASSWORD'), 90 | 91 | /* 92 | |-------------------------------------------------------------------------- 93 | | Sendmail System Path 94 | |-------------------------------------------------------------------------- 95 | | 96 | | When using the "sendmail" driver to send e-mails, we will need to know 97 | | the path to where Sendmail lives on this server. A default path has 98 | | been provided here, which will work well on most of your systems. 99 | | 100 | */ 101 | 102 | 'sendmail' => '/usr/sbin/sendmail -bs', 103 | 104 | /* 105 | |-------------------------------------------------------------------------- 106 | | Markdown Mail Settings 107 | |-------------------------------------------------------------------------- 108 | | 109 | | If you are using Markdown based email rendering, you may configure your 110 | | theme and component paths here, allowing you to customize the design 111 | | of the emails. Or, you may simply stick with the Laravel defaults! 112 | | 113 | */ 114 | 115 | 'markdown' => [ 116 | 'theme' => 'default', 117 | 118 | 'paths' => [ 119 | resource_path('views/vendor/mail'), 120 | ], 121 | ], 122 | 123 | /* 124 | |-------------------------------------------------------------------------- 125 | | Log Channel 126 | |-------------------------------------------------------------------------- 127 | | 128 | | If you are using the "log" driver, you may specify the logging channel 129 | | if you prefer to keep mail messages separate from other log entries 130 | | for simpler reading. Otherwise, the default channel will be used. 131 | | 132 | */ 133 | 'log_channel' => env('MAIL_LOG_CHANNEL'), 134 | 135 | ]; 136 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_CONNECTION', 'sync'), 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Queue Connections 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Here you may configure the connection information for each server that 26 | | is used by your application. A default configuration has been added 27 | | for each back-end shipped with Laravel. You are free to add more. 28 | | 29 | */ 30 | 31 | 'connections' => [ 32 | 33 | 'sync' => [ 34 | 'driver' => 'sync', 35 | ], 36 | 37 | 'database' => [ 38 | 'driver' => 'database', 39 | 'table' => 'jobs', 40 | 'queue' => 'default', 41 | 'retry_after' => 90, 42 | ], 43 | 44 | 'beanstalkd' => [ 45 | 'driver' => 'beanstalkd', 46 | 'host' => 'localhost', 47 | 'queue' => 'default', 48 | 'retry_after' => 90, 49 | 'block_for' => 0, 50 | ], 51 | 52 | 'sqs' => [ 53 | 'driver' => 'sqs', 54 | 'key' => env('AWS_ACCESS_KEY_ID'), 55 | 'secret' => env('AWS_SECRET_ACCESS_KEY'), 56 | 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id', 57 | 'queue' => 'your-queue-name', 58 | 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), 59 | ], 60 | 61 | 'redis' => [ 62 | 'driver' => 'redis', 63 | 'connection' => 'default', 64 | 'queue' => env('REDIS_QUEUE', 'default'), 65 | 'queue' => 'default', 66 | 'retry_after' => 90, 67 | ], 68 | 69 | ], 70 | 71 | /* 72 | |-------------------------------------------------------------------------- 73 | | Failed Queue Jobs 74 | |-------------------------------------------------------------------------- 75 | | 76 | | These options configure the behavior of failed queue job logging so you 77 | | can control which database and table are used to store the jobs that 78 | | have failed. You may change them to any database / table you wish. 79 | | 80 | */ 81 | 82 | 'failed' => [ 83 | 'database' => env('DB_CONNECTION', 'mysql'), 84 | 'table' => 'failed_jobs', 85 | ], 86 | 87 | ]; 88 | -------------------------------------------------------------------------------- /config/recaptcha.php: -------------------------------------------------------------------------------- 1 | env('RECAPTCHA_PUBLIC_KEY'), 5 | 'secret' => env('RECAPTCHA_PRIVATE_KEY'), 6 | ]; 7 | -------------------------------------------------------------------------------- /config/sentry.php: -------------------------------------------------------------------------------- 1 | env('SENTRY_DSN'), 5 | 6 | // capture release as git sha 7 | // 'release' => trim(exec('git log --pretty="%h" -n1 HEAD')), 8 | 9 | // Capture bindings on SQL queries 10 | 'breadcrumbs.sql_bindings' => true, 11 | 12 | // Capture default user context 13 | 'user_context' => true, 14 | ]; 15 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'client_id' => env('GITHUB_ID'), 19 | 'client_secret' => env('GITHUB_SECRET'), 20 | 'redirect' => env('GITHUB_CALLBACK'), 21 | ], 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /config/tinker.php: -------------------------------------------------------------------------------- 1 | [ 17 | // App\Console\Commands\ExampleCommand::class, 18 | ], 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Alias Blacklist 23 | |-------------------------------------------------------------------------- 24 | | 25 | | Typically, Tinker automatically aliases classes as you require them in 26 | | Tinker. However, you may wish to never alias certain classes, which 27 | | you may accomplish by listing the classes in the following array. 28 | | 29 | */ 30 | 31 | 'dont_alias' => [ 32 | 'App\Nova', 33 | ], 34 | 35 | ]; 36 | -------------------------------------------------------------------------------- /config/toastr.php: -------------------------------------------------------------------------------- 1 | ['positionClass' => 'toast-top-full-width'], 5 | ]; 6 | -------------------------------------------------------------------------------- /config/trustedproxy.php: -------------------------------------------------------------------------------- 1 | null, // [,], '*', ',' 19 | 20 | /* 21 | * To trust one or more specific proxies that connect 22 | * directly to your server, use an array or a string separated by comma of IP addresses: 23 | */ 24 | // 'proxies' => ['192.168.1.1'], 25 | // 'proxies' => '192.168.1.1, 192.168.1.2', 26 | 27 | /* 28 | * Or, to trust all proxies that connect 29 | * directly to your server, use a "*" 30 | */ 31 | // 'proxies' => '*', 32 | 33 | /* 34 | * Which headers to use to detect proxy related data (For, Host, Proto, Port) 35 | * 36 | * Options include: 37 | * 38 | * - Illuminate\Http\Request::HEADER_X_FORWARDED_ALL (use all x-forwarded-* headers to establish trust) 39 | * - Illuminate\Http\Request::HEADER_FORWARDED (use the FORWARDED header to establish trust) 40 | * - Illuminate\Http\Request::HEADER_X_FORWARDED_AWS_ELB (If you are using AWS Elastic Load Balancer) 41 | * 42 | * - 'HEADER_X_FORWARDED_ALL' (use all x-forwarded-* headers to establish trust) 43 | * - 'HEADER_FORWARDED' (use the FORWARDED header to establish trust) 44 | * - 'HEADER_X_FORWARDED_AWS_ELB' (If you are using AWS Elastic Load Balancer) 45 | * 46 | * @link https://symfony.com/doc/current/deployment/proxies.html 47 | */ 48 | 'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_ALL, 49 | 50 | ]; 51 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(App\User::class, function (Faker $faker) { 19 | return [ 20 | 'name' => $faker->name, 21 | 'email' => $faker->unique()->safeEmail, 22 | 'token' => Str::random(10), 23 | 'github_username' => $faker->userName, 24 | 'api_token' => Str::random(60), 25 | 'remember_token' => Str::random(10), 26 | ]; 27 | }); 28 | 29 | $factory->define(App\Org::class, function (Faker $faker) { 30 | return [ 31 | 'id' => $faker->unique()->randomNumber, 32 | 'name' => $faker->userName, 33 | 'url' => $faker->url, 34 | 'description' => $faker->text, 35 | 'avatar' => 'https://github.com/orgmanager.png', 36 | 'userid' => $faker->unique()->randomDigitNotNull, 37 | 'role' => 'admin', 38 | ]; 39 | }); 40 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_000000_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('name')->nullable(); 19 | $table->string('email')->unique(); 20 | $table->string('token'); 21 | $table->string('github_username'); 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/2017_01_13_185020_create_oauth_identities_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 12 | $table->integer('user_id')->unsigned(); 13 | $table->string('provider_user_id'); 14 | $table->string('provider'); 15 | $table->string('access_token'); 16 | $table->timestamps(); 17 | }); 18 | } 19 | 20 | public function down() 21 | { 22 | Schema::drop('oauth_identities'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /database/migrations/2017_01_14_125908_create_orgs_table.php: -------------------------------------------------------------------------------- 1 | integer('id')->unique(); 18 | $table->string('name'); 19 | $table->string('url'); 20 | $table->longText('description')->nullable(); 21 | $table->string('avatar'); 22 | $table->integer('userid'); 23 | $table->integer('invitecount')->default('0'); 24 | $table->string('role')->default('undefined'); 25 | $table->string('password')->nullable(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::dropIfExists('orgs'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /database/migrations/2017_02_13_175831_add_api_token_to_users_table.php: -------------------------------------------------------------------------------- 1 | string('api_token', 60)->after('github_username')->unique()->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('users', function (Blueprint $table) { 29 | $table->dropColumn('api_token'); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2017_02_22_171006_add_pretty_name_to_orgs_table.php: -------------------------------------------------------------------------------- 1 | string('pretty_name')->after('name')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('orgs', function (Blueprint $table) { 29 | $table->dropColumn('pretty_name'); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2017_03_07_192256_add_team_to_orgs_table.php: -------------------------------------------------------------------------------- 1 | integer('team_id')->after('userid')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('orgs', function (Blueprint $table) { 29 | $table->dropColumn('team_id'); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2017_03_07_192847_create_teams_table.php: -------------------------------------------------------------------------------- 1 | integer('id')->unique(); 18 | $table->integer('org_id'); 19 | $table->string('name'); 20 | $table->text('description')->nullable(); 21 | $table->string('privacy'); 22 | $table->string('permission'); 23 | }); 24 | } 25 | 26 | /** 27 | * Reverse the migrations. 28 | * 29 | * @return void 30 | */ 31 | public function down() 32 | { 33 | Schema::dropIfExists('teams'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/migrations/2017_04_23_100458_add_custom_message_to_orgs_table.php: -------------------------------------------------------------------------------- 1 | string('custom_message')->after('description')->nullable(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('orgs', function (Blueprint $table) { 29 | $table->dropColumn('custom_message'); 30 | }); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/2017_04_28_163341_delete_oauth_identities_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 28 | $table->integer('user_id')->unsigned(); 29 | $table->string('provider_user_id'); 30 | $table->string('provider'); 31 | $table->string('access_token'); 32 | $table->timestamps(); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call(UsersTableSeeder::class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /docs/images/README.md: -------------------------------------------------------------------------------- 1 | This folder contains image files for the documentation of the project. 2 | -------------------------------------------------------------------------------- /docs/images/orgmanager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fossasia/orgmanager/8c5dce950a5d74b3ce02820db4df02e7e9c9bd86/docs/images/orgmanager.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "dev": "npm run development", 5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 6 | "watch": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --watch --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 7 | "watch-poll": "npm run watch -- --watch-poll", 8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js", 9 | "prod": "npm run dev", 10 | "production": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js", 11 | "postinstall": "yarn run dev" 12 | }, 13 | "dependencies": { 14 | "axios": "^0.21.2", 15 | "sweetalert": "^2.1.2", 16 | "tailwindcss": "0.4.3", 17 | "vue": "^2.6.10" 18 | }, 19 | "devDependencies": { 20 | "cross-env": "5.2.0", 21 | "laravel-mix": "4.1.2", 22 | "less": "3.9.0", 23 | "less-loader": "5.0.0", 24 | "resolve-url-loader": "3.1.0", 25 | "sass": "^1.22.9", 26 | "sass-loader": "7.*", 27 | "vue-template-compiler": "^2.6.10" 28 | }, 29 | "engines": { 30 | "node": "12.x" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | ./tests/Feature 14 | 15 | 16 | 17 | ./tests/Unit 18 | 19 | 20 | 21 | 22 | ./app 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | Options -Indexes 2 | 3 | 4 | Options -MultiViews 5 | 6 | 7 | RewriteEngine On 8 | 9 | # Redirect Trailing Slashes If Not A Folder... 10 | RewriteCond %{REQUEST_FILENAME} !-d 11 | RewriteCond %{REQUEST_URI} (.+)/$ 12 | RewriteRule ^ %1 [L,R=301] 13 | 14 | # Handle Front Controller... 15 | RewriteCond %{REQUEST_FILENAME} !-d 16 | RewriteCond %{REQUEST_FILENAME} !-f 17 | RewriteRule ^ index.php [L] 18 | 19 | # Handle Authorization Header 20 | RewriteCond %{HTTP:Authorization} . 21 | RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] 22 | 23 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | define('LARAVEL_START', microtime(true)); 9 | 10 | /* 11 | |-------------------------------------------------------------------------- 12 | | Register The Auto Loader 13 | |-------------------------------------------------------------------------- 14 | | 15 | | Composer provides a convenient, automatically generated class loader for 16 | | our application. We just need to utilize it! We'll simply require it 17 | | into the script here so that we don't have to worry about manual 18 | | loading any of our classes later on. It feels great to relax. 19 | | 20 | */ 21 | 22 | require __DIR__.'/../vendor/autoload.php'; 23 | 24 | /* 25 | |-------------------------------------------------------------------------- 26 | | Turn On The Lights 27 | |-------------------------------------------------------------------------- 28 | | 29 | | We need to illuminate PHP development, so let us turn on the lights. 30 | | This bootstraps the framework and gets it ready for use, then it 31 | | will load up this application so that we can run it and send 32 | | the responses back to the browser and delight our users. 33 | | 34 | */ 35 | 36 | $app = require_once __DIR__.'/../bootstrap/app.php'; 37 | 38 | /* 39 | |-------------------------------------------------------------------------- 40 | | Run The Application 41 | |-------------------------------------------------------------------------- 42 | | 43 | | Once we have the application, we can handle the incoming request 44 | | through the kernel, and send the associated response back to 45 | | the client's browser allowing them to enjoy the creative 46 | | and wonderful application we have prepared for them. 47 | | 48 | */ 49 | 50 | $kernel = $app->make(Illuminate\Contracts\Http\Kernel::class); 51 | 52 | $response = $kernel->handle( 53 | $request = Illuminate\Http\Request::capture() 54 | ); 55 | 56 | $response->send(); 57 | 58 | $kernel->terminate($request, $response); 59 | -------------------------------------------------------------------------------- /public/js/404.min.js: -------------------------------------------------------------------------------- 1 | function loop(){window.requestAnimationFrame(loop),tick+=opts.hueSpeed,ctx.shadowBlur=0,ctx.fillStyle="rgba(32,35,45,alp)".replace("alp",opts.repaintAlpha),ctx.fillRect(0,0,w,h);for(var a=0;a=this.targetTime&&(this.time=0,this.targetTime=0,this.picked=!1),ctx.fillStyle=ctx.shadowColor=this.color.replace("alp",Math.sin(a*Math.PI)),ctx.fill()):(ctx.strokeStyle=ctx.shadowColor=opts.strokeColor,ctx.stroke())};for(var x=0;x 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /resources/js/app.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import swal from 'sweetalert' 3 | 4 | window.swal = swal 5 | 6 | Vue.component('password-panel', () => import('./components/PasswordPanel.vue' /* webpackChunkName: "password-panel" */ )) 7 | 8 | new Vue({ 9 | el: '#app' 10 | }) 11 | -------------------------------------------------------------------------------- /resources/js/components/PasswordPanel.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | -------------------------------------------------------------------------------- /resources/js/lib/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; 4 | 5 | let token = document.head.querySelector('meta[name="csrf-token"]'); 6 | 7 | if (token) { 8 | axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content; 9 | } else { 10 | console.error('CSRF token not found'); 11 | } 12 | 13 | export default axios -------------------------------------------------------------------------------- /resources/lang/en/alerts.php: -------------------------------------------------------------------------------- 1 | "We couldn't find that organization!", 16 | 'error' => 'Error', 17 | 'notauth' => "You tried to edit an organization you don't have access to!", 18 | 'authfail' => 'Auth Error', 19 | 'captcha' => "You need to prove you aren't a robot!", 20 | 'captchat' => 'ReCaptcha required', 21 | 'username' => 'You need to submit an username!', 22 | 'usernamet' => 'Username required', 23 | 'passwd1' => 'You need a password!', 24 | 'passwdt1' => 'Password required', 25 | 'passwd2' => 'Wrong Password!', 26 | 'passwdt2' => 'Wrong Password!', 27 | 'member' => 'You are already a member of this organization!', 28 | 'passwdchange' => "'s password was changed", 29 | 'changed' => 'Password Changed', 30 | 'notchanged' => "You didn't change anything!", 31 | 'alldb' => 'All your organizations were added to our database!', 32 | 'sync' => 'Sync successfull!', 33 | 'updated' => ' was updated!', 34 | 'loggedin' => 'Sucessfully logged in!', 35 | 'success' => 'Success!', 36 | 37 | ]; 38 | -------------------------------------------------------------------------------- /resources/lang/en/empty.php: -------------------------------------------------------------------------------- 1 | "Looks like there's nothing here...", 16 | 'sync1' => 'You can try to', 17 | 'sync2' => 'sync', 18 | 'sync3' => 'the data from GitHub.', 19 | 'makesure' => 'Make sure you authorized OrgManager to access your organization data.', 20 | 'problems' => 'Having problems?', 21 | 'issue' => 'Open an issue', 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /resources/lang/en/home.php: -------------------------------------------------------------------------------- 1 | 'OrgManager allows Github Organizations to share invite links for free!', 16 | 'logintext' => 'To start, please ', 17 | 'login' => 'login', 18 | 'help' => 'HELP ON GITHUB', 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /resources/lang/en/join.php: -------------------------------------------------------------------------------- 1 | 'Join', 16 | 'button' => 'Join with GitHub', 17 | 'passwd' => 'Enter the organization password to join', 18 | 'nopasswd' => 'Login with your GitHub account to join', 19 | 'uplace' => 'Your GitHub username', 20 | 'pplace' => 'Password', 21 | 'by' => 'Added by', 22 | 23 | ]; 24 | -------------------------------------------------------------------------------- /resources/lang/en/organizations.php: -------------------------------------------------------------------------------- 1 | 'Organizations', 16 | 'name' => 'Name', 17 | 'link' => 'Link', 18 | 'options' => 'Settings', 19 | 'passwdtext' => 'Protect invites!', 20 | 'haspasswdtext' => 'Change password', 21 | 'changepasswd' => 'Change password', 22 | ]; 23 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '« Previous', 17 | 'next' => 'Next »', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /resources/less/new.less: -------------------------------------------------------------------------------- 1 | @tailwind preflight; 2 | @import "resets"; 3 | 4 | /** 5 | * Here you would add any of your custom component classes; stuff that you'd 6 | * want loaded *before* the utilities so that the utilities could still 7 | * override them. 8 | * 9 | * Example: 10 | * 11 | * .btn { ... } 12 | * .form-input { ... } 13 | * 14 | * Or if using a preprocessor or `postcss-import`: 15 | * 16 | * @import "components/buttons"; 17 | * @import "components/forms"; 18 | */ 19 | 20 | /** 21 | * This injects all of Tailwind's utility classes, generated based on your 22 | * config file. 23 | * 24 | * If using `postcss-import`, you should import this line from it's own file: 25 | * 26 | * @import "./tailwind-utilities.css"; 27 | * 28 | * See: https://github.com/tailwindcss/tailwindcss/issues/53#issuecomment-341413622 29 | */ 30 | @tailwind utilities; 31 | 32 | /** 33 | * Here you would add any custom utilities you need that don't come out of the 34 | * box with Tailwind. 35 | * 36 | * Example : 37 | * 38 | * .bg-pattern-graph-paper { ... } 39 | * .skew-45 { ... } 40 | * 41 | * Or if using a preprocessor or `postcss-import`: 42 | * 43 | * @import "utilities/background-patterns"; 44 | * @import "utilities/skew-transforms"; 45 | */ 46 | 47 | .focus\:outline-none { 48 | &:focus { 49 | outline: none; 50 | } 51 | } 52 | 53 | .bg-pattern { 54 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='152' height='152' viewBox='0 0 152 152'%3E%3Cg fill-rule='evenodd'%3E%3Cg id='temple' fill='%23e8ecef' fill-opacity='0.9'%3E%3Cpath d='M152 150v2H0v-2h28v-8H8v-20H0v-2h8V80h42v20h20v42H30v8h90v-8H80v-42h20V80h42v40h8V30h-8v40h-42V50H80V8h40V0h2v8h20v20h8V0h2v150zm-2 0v-28h-8v20h-20v8h28zM82 30v18h18V30H82zm20 18h20v20h18V30h-20V10H82v18h20v20zm0 2v18h18V50h-18zm20-22h18V10h-18v18zm-54 92v-18H50v18h18zm-20-18H28V82H10v38h20v20h38v-18H48v-20zm0-2V82H30v18h18zm-20 22H10v18h18v-18zm54 0v18h38v-20h20V82h-18v20h-20v20H82zm18-20H82v18h18v-18zm2-2h18V82h-18v18zm20 40v-18h18v18h-18zM30 0h-2v8H8v20H0v2h8v40h42V50h20V8H30V0zm20 48h18V30H50v18zm18-20H48v20H28v20H10V30h20V10h38v18zM30 50h18v18H30V50zm-2-40H10v18h18V10z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E"); 55 | } 56 | 57 | .link-shadow { 58 | -webkit-box-shadow: 0 -5px 0 0 config("colors.green-lighter") inset; 59 | box-shadow: 0 -5px 0 0 config("colors.green-lighter") inset; 60 | 61 | &:hover { 62 | -webkit-box-shadow: inset 0 -50px 0 0 config("colors.green-lighter"); 63 | box-shadow: inset 0 -50px 0 0 config("colors.green-lighter"); 64 | } 65 | } 66 | 67 | .link-transition { 68 | -webkit-transition: -webkit-box-shadow 0.3s; 69 | transition: -webkit-box-shadow 0.3s; 70 | -o-transition: box-shadow 0.3s; 71 | transition: box-shadow 0.3s; 72 | transition: box-shadow 0.3s, -webkit-box-shadow 0.3s; 73 | } 74 | 75 | .transition { 76 | -webkit-animation: fadein 2s; 77 | -moz-animation: fadein 2s; 78 | -ms-animation: fadein 2s; 79 | -o-animation: fadein 2s; 80 | animation: fadein 2s; 81 | } 82 | 83 | @keyframes fadein { 84 | from { 85 | opacity: 0; 86 | } 87 | to { 88 | opacity: 1; 89 | } 90 | } 91 | 92 | @-moz-keyframes fadein { 93 | from { 94 | opacity: 0; 95 | } 96 | to { 97 | opacity: 1; 98 | } 99 | } 100 | 101 | @-webkit-keyframes fadein { 102 | from { 103 | opacity: 0; 104 | } 105 | to { 106 | opacity: 1; 107 | } 108 | } 109 | 110 | @-ms-keyframes fadein { 111 | from { 112 | opacity: 0; 113 | } 114 | to { 115 | opacity: 1; 116 | } 117 | } 118 | 119 | @-o-keyframes fadein { 120 | from { 121 | opacity: 0; 122 | } 123 | to { 124 | opacity: 1; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /resources/less/resets.less: -------------------------------------------------------------------------------- 1 | .cf-wrapper { 2 | @apply .m-0 !important; 3 | } 4 | 5 | [v-cloak] { 6 | @apply .hidden !important; 7 | } -------------------------------------------------------------------------------- /resources/views/developer.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.new') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
Want to integrate your application with OrgManager? Now you can!
8 |

Introducing the Orgmanager API, that allows you to retrieve details about organizations, your OrgManager installation or even invite users!

9 |
10 |
11 | @auth 12 | Get your API token 13 | @else 14 | Login and get your API token 15 | @endif 16 |
17 |
18 |
19 | @endsection 20 | -------------------------------------------------------------------------------- /resources/views/empty.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.new') 2 | 3 | @section('content') 4 |
5 |
6 | 7 | 8 | 9 |
10 |
11 |

Looks like there's nothing here

12 |

Try to sync the from your Github account, using the sync button below.

13 |

If your organizations aren't showing here after sync, check we’ve been given access to them.

14 |
15 |
16 |
17 | {{ csrf_field() }} 18 | 28 |
29 |
30 |
31 | @endsection 32 | -------------------------------------------------------------------------------- /resources/views/errors/401.blade.php: -------------------------------------------------------------------------------- 1 | @extends('errors::minimal') 2 | 3 | @section('title', __('Unauthorized')) 4 | @section('code', '401') 5 | @section('message', __('Unauthorized')) 6 | -------------------------------------------------------------------------------- /resources/views/errors/403.blade.php: -------------------------------------------------------------------------------- 1 | @extends('errors::minimal') 2 | 3 | @section('title', __('Forbidden')) 4 | @section('code', '403') 5 | @section('message', __($exception->getMessage() ?: 'Forbidden')) 6 | -------------------------------------------------------------------------------- /resources/views/errors/404.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ config('app.name') }} 6 | 7 | 8 | @include('layouts.code.head') 9 | 10 | 11 | 12 | 13 | @orgmanagerIcon 14 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
31 | 32 |
33 |
34 |

Oops! Error 404 not found.

35 |

The page you were looking for doesn't exist.

36 |
37 |
38 | 46 | 47 | @include('layouts.code.footer') 48 | 49 | 50 | -------------------------------------------------------------------------------- /resources/views/errors/419.blade.php: -------------------------------------------------------------------------------- 1 | @extends('errors::minimal') 2 | 3 | @section('title', __('Page Expired')) 4 | @section('code', '419') 5 | @section('message', __('Page Expired')) 6 | -------------------------------------------------------------------------------- /resources/views/errors/429.blade.php: -------------------------------------------------------------------------------- 1 | @extends('errors::minimal') 2 | 3 | @section('title', __('Too Many Requests')) 4 | @section('code', '429') 5 | @section('message', __('Too Many Requests')) 6 | -------------------------------------------------------------------------------- /resources/views/errors/500.blade.php: -------------------------------------------------------------------------------- 1 | @extends('errors::minimal') 2 | 3 | @section('title', __('Server Error')) 4 | @section('code', '500') 5 | @section('message', __('Server Error')) 6 | -------------------------------------------------------------------------------- /resources/views/errors/503.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{ config('app.name') }} 6 | 7 | 8 | @include('layouts.code.head') 9 | 10 | 11 |
12 |
13 | 14 | 15 |
16 |
17 | 18 |
19 | @orgmanagerIcon 20 |
21 |

Hey Guys!
22 | We're currently under manteniance

23 |

We enabled manteniance mode {{ $exception->wentDownAt }} 24 | for {{ $exception->getMessage() != null ? $exception->getMessage():'executing manteniance tasks' }}.

25 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | @include('layouts.code.footer') 44 | 45 | 46 | -------------------------------------------------------------------------------- /resources/views/errors/layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @yield('title') 8 | 9 | 10 | 11 | 12 | 13 | 14 | 47 | 48 | 49 |
50 |
51 |
52 | @yield('message') 53 |
54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /resources/views/errors/minimal.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | @yield('title') 8 | 9 | 10 | 11 | 12 | 13 | 14 | 50 | 51 | 52 |
53 |
54 | @yield('code') 55 |
56 | 57 |
58 | @yield('message') 59 |
60 |
61 | 62 | 63 | -------------------------------------------------------------------------------- /resources/views/join.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Join {{ $org->pretty_name ?? $org->name }} 9 | 10 | 11 | 12 | 13 | 18 | 19 | @include('layouts.code.head') 20 | 21 | 22 | 23 |
24 |
25 |
26 |
27 | 28 |
29 | 30 |

Join {{ $org->pretty_name ?? $org->name }}

31 | 32 | 33 | @if (optional($org->team)->exists) 34 | @if ($org->team->privacy == 'closed') 35 |

You will also join the {{ $org->team->name }} team

36 | @else 37 |

You will also join the private {{ $org->team->name }} team

38 | @endif 39 | @endif 40 | 41 | @if ($org->description) 42 |

{{ $org->description ?? '' }}

43 | @endif 44 |
45 |
46 |
47 | {{ csrf_field() }} 48 | @if ($org->hasPassword()) 49 |
50 |

Enter the organization password to join {{ $org->pretty_name ?? $org->name }}:

51 | 52 |
53 | @endif 54 |
55 | 61 |
62 |
63 |
64 |
65 |
66 | 67 | 68 | @if (count($errors) > 0) 69 | 72 | @endif 73 | @if (session('success')) 74 | 77 | @endif 78 | 79 | @include('layouts.code.footer') 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /resources/views/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ config('app.name', 'Laravel') }} 12 | 13 | 14 | 15 | 16 | @yield('header') 17 | 18 | 19 | @include('layouts.code.head') 20 | 21 | 22 |
23 | 85 | 86 | @yield('content') 87 |
88 | 89 | @if (count($errors) > 0) 90 | 93 | @endif 94 | @if (session('success')) 95 | 98 | @endif 99 | @yield('footer') 100 | @include('layouts.code.footer') 101 | 102 | 103 | -------------------------------------------------------------------------------- /resources/views/layouts/code/footer.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/layouts/code/head.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/layouts/new.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{ config('app.name') }} 12 | 13 | 14 | 15 | 16 | 17 | @yield('header') 18 | @include('layouts.code.head') 19 | 20 | 21 | 40 | 41 |
42 | @yield('content') 43 |
44 | 45 | 46 | @if (count($errors) > 0) 47 | 50 | @endif 51 | @if (session('success')) 52 | 55 | @endif 56 | @yield('footer') 57 | @include('layouts.code.footer') 58 | 59 | 60 | -------------------------------------------------------------------------------- /resources/views/orgs.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.new') 2 | 3 | @section('skip-nav-border', true) 4 | 5 | @section('content') 6 | 15 |
16 |
17 |

Your organizations

18 |
19 | {{ csrf_field() }} 20 | 30 |
31 |
32 |
33 | @foreach ($orgs as $org) 34 |
35 |
36 |
37 | {{ $org->name }} 38 |

{{ $org->pretty_name ?? $org->name }}

39 |
40 | 41 |
42 |
43 | 44 | 45 | 46 | Settings 47 |
48 |
49 |
50 |
51 |
52 |

Invite Link:

53 | {{ route('join', $org) }} 54 |
55 |
56 | 57 | 58 | 59 | @endforeach 60 |
61 |
62 | {{--
63 |

TIP: Don't see the organization you want? Double check we have access to it and then sync again

64 |
--}} 65 | @endsection 66 | -------------------------------------------------------------------------------- /resources/views/settings.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.new') 2 | 3 | @section('content') 4 |
5 |
6 |
7 | 8 |

{{ $org->pretty_name ?? $org->name }}'s settings

9 |
10 |
11 |
12 | @csrf 13 | 23 |
24 |
25 |
26 | 27 |
28 |
29 |

Team settings

30 |

OrgManager v3 introduces Teams, a new function that allows you to specify a team your invited users will go into. Please note that this feature is still in a beta version, so use the report widget if you find any bugs.

31 |
32 |
33 | {{ csrf_field() }} 34 | 42 |
43 |
44 |
45 |

Danger Zone

46 |
47 | @csrf 48 | @method('DELETE') 49 | 55 |
56 |
57 |
58 | @endsection 59 | -------------------------------------------------------------------------------- /resources/views/token.blade.php: -------------------------------------------------------------------------------- 1 | @extends('layouts.new') 2 | 3 | @section('content') 4 |
5 |
6 |
7 |
Your token
8 |

You'll find your token below. Remember to treat it like a password, as it gives anyone the ability to invite anyone to your organization. You can use the button below to reset it if you need to.

9 | 10 |
11 |
12 |
13 | {{ csrf_field() }} 14 | {{ method_field('DELETE') }} 15 | 16 |
17 |
18 |
19 |
20 | @endsection 21 | -------------------------------------------------------------------------------- /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 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/header.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{ $slot }} 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /resources/views/vendor/mail/html/layout.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 25 | 26 | 27 | 28 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /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 | {{ 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') }} {{ config('app.name') }}. @lang('All rights reserved.') 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') }} {{ config('app.name') }}. @lang('All rights reserved.') 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 | -------------------------------------------------------------------------------- /resources/views/vendor/notifications/email.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | {{-- Greeting --}} 3 | @if (! empty($greeting)) 4 | # {{ $greeting }} 5 | @else 6 | @if ($level === 'error') 7 | # @lang('Whoops!') 8 | @else 9 | # @lang('Hello!') 10 | @endif 11 | @endif 12 | 13 | {{-- Intro Lines --}} 14 | @foreach ($introLines as $line) 15 | {{ $line }} 16 | 17 | @endforeach 18 | 19 | {{-- Action Button --}} 20 | @isset($actionText) 21 | 31 | @component('mail::button', ['url' => $actionUrl, 'color' => $color]) 32 | {{ $actionText }} 33 | @endcomponent 34 | @endisset 35 | 36 | {{-- Outro Lines --}} 37 | @foreach ($outroLines as $line) 38 | {{ $line }} 39 | 40 | @endforeach 41 | 42 | {{-- Salutation --}} 43 | @if (! empty($salutation)) 44 | {{ $salutation }} 45 | @else 46 | @lang('Regards'),
47 | {{ config('app.name') }} 48 | @endif 49 | 50 | {{-- Subcopy --}} 51 | @isset($actionText) 52 | @slot('subcopy') 53 | @lang( 54 | "If you’re having trouble clicking the \":actionText\" button, copy and paste the URL below\n". 55 | 'into your web browser: [:actionURL](:actionURL)', 56 | [ 57 | 'actionText' => $actionText, 58 | 'actionURL' => $actionUrl, 59 | ] 60 | ) 61 | @endslot 62 | @endisset 63 | @endcomponent 64 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/bootstrap-4.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 | 44 | @endif 45 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/default.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 | 44 | @endif 45 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/semantic-ui.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 | 36 | @endif 37 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/simple-bootstrap-4.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 | 25 | @endif 26 | -------------------------------------------------------------------------------- /resources/views/vendor/pagination/simple-default.blade.php: -------------------------------------------------------------------------------- 1 | @if ($paginator->hasPages()) 2 | 17 | @endif 18 | -------------------------------------------------------------------------------- /routes/api.php: -------------------------------------------------------------------------------- 1 | 'Api'], function () { 15 | Route::any('', 'HomeController@index'); 16 | Route::get('user', 'UserController@index'); 17 | Route::get('token/regenerate', 'UserController@token'); 18 | Route::get('user/orgs', 'UserController@orgs'); 19 | Route::get('org', 'HomeController@org'); 20 | Route::get('org/{org?}', 'OrgController@index'); 21 | Route::post('org/{org?}', 'OrgController@password'); 22 | Route::put('org/{org?}', 'OrgController@update'); 23 | Route::delete('org/{org?}', 'OrgController@delete'); 24 | Route::post('join/{org?}', 'OrgController@join'); 25 | Route::get('stats', 'HomeController@stats'); 26 | }); 27 | -------------------------------------------------------------------------------- /routes/channels.php: -------------------------------------------------------------------------------- 1 | id === (int) $id; 16 | }); 17 | -------------------------------------------------------------------------------- /routes/console.php: -------------------------------------------------------------------------------- 1 | comment(Inspiring::quote()); 18 | })->describe('Display an inspiring quote'); 19 | -------------------------------------------------------------------------------- /routes/web.php: -------------------------------------------------------------------------------- 1 | middleware('guest')->name('landing'); 15 | 16 | Route::get('dashboard', 'DashboardController@index')->name('dashboard'); 17 | 18 | Route::get('org/{org}', 'OrgController@index')->name('org'); 19 | 20 | Route::post('org/{org}/password', 'OrgController@password')->name('org.password'); 21 | Route::delete('org/{org}/password', 'OrgController@removePassword')->name('org.password'); 22 | 23 | Route::put('org/{org}', 'OrgController@update')->name('org.update'); 24 | Route::delete('org/{org}', 'OrgController@delete')->name('org.delete'); 25 | Route::post('org/{org}/message', 'OrgController@message')->name('org.message'); 26 | Route::get('org/{org}/teams', 'TeamController@index')->name('org.teams'); 27 | Route::post('org/{org}/teams', 'TeamController@syncTeams')->name('org.teams.sync'); 28 | Route::put('org/{org}/teams', 'TeamController@setTeam')->name('org.teams.set'); 29 | Route::delete('org/{org}/teams', 'TeamController@deleteTeams')->name('org.teams.delete'); 30 | Route::post('sync', 'GithubController@syncOrgs')->name('sync'); 31 | Route::post('sync/{org}', 'GithubController@syncOrg')->name('sync.org'); 32 | Route::get('join/{org}', 'JoinController@index')->name('join'); 33 | Route::post('join/{org}', 'JoinController@inviteUser')->name('join.post'); 34 | Route::get('join/{org}/callback', 'JoinController@callback')->name('join.callback'); 35 | Route::get('o/{name}', 'JoinController@redirect')->name('redirect'); 36 | Route::view('developer', 'developer')->name('developer'); 37 | Route::view('token', 'token')->middleware('auth')->name('token'); 38 | Route::delete('token', 'DeveloperController@deleteToken')->name('token.delete'); 39 | 40 | // Auth routes 41 | Route::get('login', 'LoginController@authorizeUser')->name('login'); 42 | Route::get('callback', 'LoginController@loginUser')->name('callback'); 43 | Route::post('logout', 'LoginController@logoutUser')->name('logout'); 44 | 45 | // Autojoiner 46 | Route::post('autojoiner', 'AutoJoinerController@index')->name('autojoiner'); 47 | -------------------------------------------------------------------------------- /server.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | $uri = urldecode( 9 | parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) 10 | ); 11 | 12 | // This file allows us to emulate Apache's "mod_rewrite" functionality from the 13 | // built-in PHP web server. This provides a convenient way to test a Laravel 14 | // application without having installed a "real" web server software here. 15 | if ($uri !== '/' && file_exists(__DIR__.'/public'.$uri)) { 16 | return false; 17 | } 18 | 19 | require_once __DIR__.'/public/index.php'; 20 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /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/DataTest.php: -------------------------------------------------------------------------------- 1 | create(); 22 | factory(Org::class, 5)->create([ 23 | 'userid' => $user->id, 24 | ]); 25 | $response = $this->actingAs($user) 26 | ->get('dashboard'); 27 | 28 | $response->assertStatus(200) 29 | ->assertViewHas('orgs', Org::where('userid', '=', $user->id)->get()); 30 | } 31 | 32 | /** 33 | * Test organization settings page gets org. 34 | * 35 | * @return void 36 | */ 37 | public function testOrg() 38 | { 39 | $user = factory(User::class)->create(); 40 | $org = factory(Org::class)->create([ 41 | 'userid' => $user->id, 42 | ]); 43 | $response = $this->actingAs($user) 44 | ->get('org/'.$org->id); 45 | $response->assertStatus(200) 46 | ->assertViewHas('org'); 47 | } 48 | 49 | /** 50 | * Test teams page gets org. 51 | * 52 | * @return void 53 | */ 54 | public function testTeams() 55 | { 56 | $user = factory(User::class)->create(); 57 | $org = factory(Org::class)->create([ 58 | 'userid' => $user->id, 59 | ]); 60 | $response = $this->actingAs($user) 61 | ->get('org/'.$org->id.'/teams'); 62 | $response->assertStatus(200) 63 | ->assertViewHas('org'); 64 | } 65 | 66 | /** 67 | * Test join page gets org. 68 | * 69 | * @return void 70 | */ 71 | public function testJoin() 72 | { 73 | $user = factory(User::class)->create(); 74 | $org = factory(Org::class)->create([ 75 | 'userid' => $user->id, 76 | ]); 77 | $response = $this->actingAs($user) 78 | ->get('join/'.$org->id); 79 | $response->assertStatus(200) 80 | ->assertViewHas('org'); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/Feature/ExampleTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 20 | 21 | $response->assertStatus(200); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tests/Feature/JoinTest.php: -------------------------------------------------------------------------------- 1 | create(); 22 | $org = factory(Org::class)->create([ 23 | 'userid' => $user->id, 24 | ]); 25 | 26 | $response = $this->post('join/'.$org->id, ['github_username' => 'idonotexist9995964']); 27 | 28 | $response->assertStatus(302); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tests/Feature/SettingsTest.php: -------------------------------------------------------------------------------- 1 | create(); 22 | $org = factory(Org::class)->create([ 23 | 'userid' => $user->id, 24 | ]); 25 | $response = $this->actingAs($user) 26 | ->get('org/'.$org->id); 27 | 28 | $response->assertStatus(200) 29 | ->assertViewHas('org'); 30 | } 31 | 32 | /** 33 | * Test password can be changed. 34 | * 35 | * @return void 36 | */ 37 | // public function testPassword() 38 | // { 39 | // $user = factory(User::class)->create(); 40 | // $org = factory(Org::class)->create([ 41 | // 'userid' => $user->id, 42 | // ]); 43 | // $response = $this->actingAs($user) 44 | // ->post('org/'.$org->id, ['org_passwd' => 'password']); 45 | // $org->refresh(); 46 | // $response->assertRedirect('org/'.$org->id) 47 | // ->assertSessionHas('success', 'The organization password was successfully updated.'); 48 | // $this->assertTrue(password_verify('password', $org->password)); 49 | // } 50 | 51 | /** 52 | * Test organization can be deleted. 53 | * 54 | * @return void 55 | */ 56 | public function testDelete() 57 | { 58 | $user = factory(User::class)->create(); 59 | $org = factory(Org::class)->create([ 60 | 'userid' => $user->id, 61 | ]); 62 | $response = $this->actingAs($user) 63 | ->delete('org/'.$org->id); 64 | $response->assertRedirect('dashboard') 65 | ->assertSessionHas('success', 'The organization was successfully deleted.'); 66 | $this->assertFalse($org->exists()); 67 | } 68 | 69 | /** 70 | * Test custom message can be changed. 71 | * 72 | * @return void 73 | */ 74 | public function testCustomMessage() 75 | { 76 | $user = factory(User::class)->create(); 77 | $org = factory(Org::class)->create([ 78 | 'userid' => $user->id, 79 | ]); 80 | $response = $this->actingAs($user) 81 | ->post('org/'.$org->id.'/message', ['message' => '# Markdown test']); 82 | $org->refresh(); 83 | $response->assertRedirect('org/'.$org->id) 84 | ->assertSessionHas('success', 'The message was successfully updated.'); 85 | $this->assertEquals('# Markdown test', $org->custom_message); 86 | $response = $this->actingAs($user) 87 | ->post('org/'.$org->id.'/message', ['message' => 'Some text, \'']); 88 | $response->assertRedirect('org/'.$org->id) 89 | ->assertSessionHas('success', 'The message was successfully updated.'); 90 | $this->assertNotEquals('Some text, \'', $org->custom_message); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /tests/Feature/StatusTest.php: -------------------------------------------------------------------------------- 1 | get('/'); 22 | 23 | $response->assertStatus(200); 24 | } 25 | 26 | /** 27 | * Test the login page redirects to GitHub. 28 | * 29 | * @return void 30 | */ 31 | public function testLoginPage() 32 | { 33 | $response = $this->get('login'); 34 | parse_str(parse_url($response->headers->get('location'))['query'], $query); 35 | 36 | $response->assertRedirect('https://github.com/login/oauth/authorize?scope=user%3Aemail%2Cadmin%3Aorg&response_type=code&state='.$query['state']); 37 | } 38 | 39 | /** 40 | * Test the dashboard page redirects to login. 41 | * 42 | * @return void 43 | */ 44 | public function testDashboardAuth() 45 | { 46 | $response = $this->get('dashboard'); 47 | 48 | $response->assertRedirect('login'); 49 | } 50 | 51 | /** 52 | * Test the dashboard returns a 200 status code when logged in (OK). 53 | * 54 | * @return void 55 | */ 56 | public function testDashboard() 57 | { 58 | $user = factory(User::class)->create(); 59 | $response = $this->actingAs($user) 60 | ->get('dashboard'); 61 | 62 | $response->assertStatus(200); 63 | } 64 | 65 | /** 66 | * Test the /o/{org} page redirects to the /join/{id} page. 67 | * 68 | * @return void 69 | */ 70 | public function testJoinRedirect() 71 | { 72 | $org = factory(Org::class)->create(); 73 | $response = $this->get('o/'.$org->name); 74 | 75 | $response->assertRedirect('join/'.$org->id); 76 | } 77 | 78 | /** 79 | * Test the join page returns a 200 status code (OK). 80 | * 81 | * @return void 82 | */ 83 | public function testJoinPage() 84 | { 85 | $org = factory(Org::class)->create(); 86 | $response = $this->get('join/'.$org->id); 87 | 88 | $response->assertStatus(200); 89 | } 90 | 91 | /** 92 | * Test the developers page returns a 200 status code (OK). 93 | * 94 | * @return void 95 | */ 96 | public function testDevPage() 97 | { 98 | $user = factory(User::class)->create(); 99 | $response = $this->actingAs($user) 100 | ->get('developer'); 101 | 102 | $response->assertStatus(200); 103 | } 104 | 105 | /** 106 | * Test the token page returns a 200 status code (OK). 107 | * 108 | * @return void 109 | */ 110 | public function testTokenPage() 111 | { 112 | $user = factory(User::class)->create(); 113 | $response = $this->actingAs($user) 114 | ->get('token'); 115 | 116 | $response->assertStatus(200); 117 | } 118 | 119 | /** 120 | * Test the organization settings page returns a 200 status code (OK). 121 | * 122 | * @return void 123 | */ 124 | public function testOrgPage() 125 | { 126 | $user = factory(User::class)->create(); 127 | $org = factory(Org::class)->create([ 128 | 'userid' => $user->id, 129 | ]); 130 | $response = $this->actingAs($user) 131 | ->get('org/'.$org->id); 132 | $response->assertStatus(200); 133 | } 134 | 135 | /** 136 | * Test the teams page returns a 200 status code (OK). 137 | * 138 | * @return void 139 | */ 140 | public function testTeamsPage() 141 | { 142 | $user = factory(User::class)->create(); 143 | $org = factory(Org::class)->create([ 144 | 'userid' => $user->id, 145 | ]); 146 | $response = $this->actingAs($user) 147 | ->get('org/'.$org->id.'/teams'); 148 | $response->assertStatus(200); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /tests/TestCase.php: -------------------------------------------------------------------------------- 1 | assertTrue(true); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Unit/HelpersTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('

Test

'."\n", markdown('# Test')); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tests/Unit/JoinTest.php: -------------------------------------------------------------------------------- 1 | create(); 23 | $org = factory(Org::class)->create([ 24 | 'userid' => $user->id, 25 | ]); 26 | Github::shouldReceive('authenticate') 27 | ->once() 28 | ->with($org->user->token, null, 'http_token') 29 | ->andReturn(); 30 | $this->artisan('orgmanager:joinorg', ['org' => $org->id, 'username' => $user->github_username]) 31 | ->expectsOutput("$user->github_username was invited to $org->name"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tests/Unit/MarkdownTest.php: -------------------------------------------------------------------------------- 1 | assertNotEquals('

image

', Markdown::render($image)); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require("laravel-mix"); 2 | const tailwindcss = require("tailwindcss"); 3 | 4 | /* 5 | |-------------------------------------------------------------------------- 6 | | Mix Asset Management 7 | |-------------------------------------------------------------------------- 8 | | 9 | | Mix provides a clean, fluent API for defining some Webpack build steps 10 | | for your Laravel application. By default, we are compiling the Sass 11 | | file for the application as well as bundling up all the JS files. 12 | | 13 | */ 14 | 15 | mix 16 | .js("resources/js/app.js", "public/js") 17 | .less("resources/less/new.less", "public/css") 18 | .options({ 19 | postCss: [tailwindcss("./tailwind.js")] 20 | }) 21 | .webpackConfig({ 22 | output: { 23 | publicPath: '/', 24 | chunkFilename: 'js/chunks/[name].js?[chunkhash]', 25 | } 26 | }); 27 | 28 | if (mix.inProduction()) { 29 | mix.version(); 30 | } 31 | --------------------------------------------------------------------------------