├── .env.example
├── .gitattributes
├── .gitignore
├── app
├── Auth
│ └── TwoFactor
│ │ └── Authenticatable.php
├── Billing
│ └── EmailInvoiceNotifier.php
├── Console
│ ├── Commands
│ │ └── Inspire.php
│ └── Kernel.php
├── Contracts
│ ├── Auth
│ │ └── TwoFactor
│ │ │ ├── Authenticatable.php
│ │ │ └── Provider.php
│ ├── Billing
│ │ └── InvoiceNotifier.php
│ └── Repositories
│ │ ├── TeamRepository.php
│ │ └── UserRepository.php
├── Events
│ ├── Event.php
│ ├── Team
│ │ ├── Created.php
│ │ └── Deleting.php
│ └── User
│ │ ├── Event.php
│ │ ├── JoinedTeam.php
│ │ ├── ProfileUpdated.php
│ │ ├── Registered.php
│ │ ├── RemovedFromTeam.php
│ │ ├── Subscribed.php
│ │ ├── SubscriptionCancelled.php
│ │ ├── SubscriptionPlanChanged.php
│ │ └── SubscriptionResumed.php
├── Exceptions
│ └── Handler.php
├── Http
│ ├── Controllers
│ │ ├── API
│ │ │ ├── InvitationController.php
│ │ │ ├── SubscriptionController.php
│ │ │ ├── TeamController.php
│ │ │ └── UserController.php
│ │ ├── Auth
│ │ │ ├── AuthController.php
│ │ │ └── PasswordController.php
│ │ ├── Controller.php
│ │ ├── HomeController.php
│ │ ├── Settings
│ │ │ ├── DashboardController.php
│ │ │ ├── InvitationController.php
│ │ │ ├── ProfileController.php
│ │ │ ├── SecurityController.php
│ │ │ ├── SubscriptionController.php
│ │ │ └── TeamController.php
│ │ ├── Stripe
│ │ │ └── WebhookController.php
│ │ ├── TermsController.php
│ │ └── WelcomeController.php
│ ├── Kernel.php
│ ├── Middleware
│ │ ├── Authenticate.php
│ │ ├── EncryptCookies.php
│ │ ├── RedirectIfAuthenticated.php
│ │ └── VerifyCsrfToken.php
│ ├── Requests
│ │ └── Request.php
│ └── routes.php
├── InteractsWithSparkHooks.php
├── Jobs
│ └── Job.php
├── Listeners
│ └── .gitkeep
├── Policies
│ └── .gitkeep
├── Providers
│ ├── AppServiceProvider.php
│ ├── AuthServiceProvider.php
│ ├── EventServiceProvider.php
│ ├── RouteServiceProvider.php
│ └── SparkServiceProvider.php
├── Repositories
│ ├── TeamRepository.php
│ └── UserRepository.php
├── Services
│ └── Auth
│ │ └── TwoFactor
│ │ └── Authy.php
├── Spark.php
├── Subscriptions
│ ├── Coupon.php
│ ├── Plan.php
│ └── Plans.php
├── Team.php
├── Teams
│ ├── CanJoinTeams.php
│ ├── Invitation.php
│ └── Team.php
├── User.php
└── Ux
│ └── Settings
│ ├── DashboardTabs.php
│ ├── Tab.php
│ ├── Tabs.php
│ └── TeamTabs.php
├── artisan
├── bootstrap
├── app.php
├── autoload.php
└── cache
│ └── .gitignore
├── composer.json
├── config
├── app.php
├── auth.php
├── broadcasting.php
├── cache.php
├── compile.php
├── database.php
├── filesystems.php
├── mail.php
├── queue.php
├── services.php
├── session.php
└── view.php
├── database
├── .gitignore
├── factories
│ └── ModelFactory.php
├── migrations
│ ├── .gitkeep
│ ├── 2014_10_12_000000_create_users_table.php
│ ├── 2014_10_12_100000_create_password_resets_table.php
│ ├── 2014_10_12_100000_create_subscriptions_table.php
│ └── 2014_10_12_200000_create_teams_tables.php
└── seeds
│ ├── .gitkeep
│ └── DatabaseSeeder.php
├── gulpfile.js
├── package.json
├── phpunit.xml
├── public
├── .htaccess
├── build
│ ├── css
│ │ ├── app-d5481a85bb.css
│ │ └── app.css.map
│ ├── js
│ │ ├── app-4377a2b0f7.js
│ │ └── app.js.map
│ └── rev-manifest.json
├── css
│ ├── app.css
│ └── app.css.map
├── favicon.ico
├── index.php
├── js
│ ├── app.js
│ └── app.js.map
├── robots.txt
└── web.config
├── readme.md
├── resources
├── assets
│ ├── js
│ │ ├── .gitignore
│ │ ├── app.js
│ │ ├── auth
│ │ │ └── registration
│ │ │ │ ├── simple.js
│ │ │ │ └── subscription.js
│ │ ├── common
│ │ │ └── errors.js
│ │ ├── core
│ │ │ ├── bootstrap.js
│ │ │ └── components.js
│ │ ├── forms
│ │ │ ├── bootstrap.js
│ │ │ ├── components.js
│ │ │ ├── errors.js
│ │ │ ├── http.js
│ │ │ └── instance.js
│ │ ├── nav.js
│ │ ├── package.json
│ │ ├── settings
│ │ │ ├── dashboard.js
│ │ │ ├── dashboard
│ │ │ │ ├── profile
│ │ │ │ │ └── basics.js
│ │ │ │ ├── security
│ │ │ │ │ ├── password.js
│ │ │ │ │ └── two-factor.js
│ │ │ │ ├── subscription.js
│ │ │ │ └── teams.js
│ │ │ ├── team.js
│ │ │ └── team
│ │ │ │ ├── membership.js
│ │ │ │ ├── membership
│ │ │ │ └── edit-team-member.js
│ │ │ │ └── owner
│ │ │ │ └── basics.js
│ │ └── spark.js
│ └── sass
│ │ ├── app.scss
│ │ ├── essentials.scss
│ │ └── themes
│ │ └── spark
│ │ ├── components
│ │ ├── settings.scss
│ │ ├── splash.scss
│ │ ├── subscription.scss
│ │ └── terms.scss
│ │ ├── elements
│ │ ├── alerts.scss
│ │ ├── buttons.scss
│ │ ├── forms.scss
│ │ ├── modals.scss
│ │ ├── nav.scss
│ │ ├── panels.scss
│ │ ├── tables.scss
│ │ └── tooltips.scss
│ │ ├── spark.scss
│ │ └── variables.scss
├── lang
│ └── en
│ │ ├── auth.php
│ │ ├── pagination.php
│ │ ├── passwords.php
│ │ └── validation.php
└── views
│ ├── auth
│ ├── login.blade.php
│ ├── password
│ │ ├── email.blade.php
│ │ └── reset.blade.php
│ ├── registration
│ │ ├── simple.blade.php
│ │ ├── simple
│ │ │ └── basics.blade.php
│ │ ├── subscription.blade.php
│ │ └── subscription
│ │ │ ├── basics.blade.php
│ │ │ ├── billing.blade.php
│ │ │ ├── coupon.blade.php
│ │ │ ├── inspiration.blade.php
│ │ │ ├── invitation.blade.php
│ │ │ └── plans
│ │ │ ├── plan.blade.php
│ │ │ ├── selected.blade.php
│ │ │ └── selector.blade.php
│ └── token.blade.php
│ ├── common
│ ├── error-alert.blade.php
│ └── footer.blade.php
│ ├── emails
│ ├── auth
│ │ └── password
│ │ │ └── email.blade.php
│ ├── billing
│ │ └── invoice.blade.php
│ └── team
│ │ └── invitations
│ │ ├── existing.blade.php
│ │ └── new.blade.php
│ ├── errors
│ └── 503.blade.php
│ ├── home.blade.php
│ ├── layouts
│ └── app.blade.php
│ ├── nav
│ ├── authenticated.blade.php
│ ├── guest.blade.php
│ └── settings.blade.php
│ ├── scripts
│ └── globals.blade.php
│ ├── settings
│ ├── dashboard.blade.php
│ ├── tabs
│ │ ├── profile.blade.php
│ │ ├── profile
│ │ │ └── basics.blade.php
│ │ ├── security.blade.php
│ │ ├── security
│ │ │ ├── password.blade.php
│ │ │ └── two-factor.blade.php
│ │ ├── subscription.blade.php
│ │ ├── subscription
│ │ │ ├── cancel.blade.php
│ │ │ ├── card.blade.php
│ │ │ ├── change.blade.php
│ │ │ ├── coupon.blade.php
│ │ │ ├── invoices
│ │ │ │ ├── history.blade.php
│ │ │ │ └── vat.blade.php
│ │ │ ├── modals
│ │ │ │ ├── cancel.blade.php
│ │ │ │ ├── change.blade.php
│ │ │ │ └── change
│ │ │ │ │ └── plan.blade.php
│ │ │ ├── resume.blade.php
│ │ │ ├── subscribe.blade.php
│ │ │ └── subscribe
│ │ │ │ ├── plan.blade.php
│ │ │ │ └── selector.blade.php
│ │ └── teams.blade.php
│ ├── team.blade.php
│ └── team
│ │ └── tabs
│ │ ├── membership.blade.php
│ │ ├── membership
│ │ └── modals
│ │ │ └── edit-team-member.blade.php
│ │ ├── owner.blade.php
│ │ └── owner
│ │ └── basics.blade.php
│ ├── terms.blade.php
│ ├── vendor
│ └── .gitkeep
│ └── welcome.blade.php
├── server.php
├── storage
├── app
│ ├── .gitignore
│ └── public
│ │ └── .gitignore
├── framework
│ ├── .gitignore
│ ├── cache
│ │ └── .gitignore
│ ├── sessions
│ │ └── .gitignore
│ └── views
│ │ └── .gitignore
└── logs
│ └── .gitignore
├── terms.md
└── tests
├── ExampleTest.php
└── TestCase.php
/.env.example:
--------------------------------------------------------------------------------
1 | APP_ENV=local
2 | APP_DEBUG=true
3 | APP_KEY=SomeRandomString
4 | APP_URL=http://localhost
5 |
6 | DB_CONNECTION=mysql
7 | DB_HOST=127.0.0.1
8 | DB_PORT=3306
9 | DB_DATABASE=homestead
10 | DB_USERNAME=homestead
11 | DB_PASSWORD=secret
12 |
13 | CACHE_DRIVER=file
14 | SESSION_DRIVER=file
15 | QUEUE_DRIVER=sync
16 |
17 | REDIS_HOST=127.0.0.1
18 | REDIS_PASSWORD=null
19 | REDIS_PORT=6379
20 |
21 | MAIL_DRIVER=smtp
22 | MAIL_HOST=mailtrap.io
23 | MAIL_PORT=2525
24 | MAIL_USERNAME=null
25 | MAIL_PASSWORD=null
26 | MAIL_ENCRYPTION=null
27 |
28 | AUTHY_KEY=
29 |
30 | STRIPE_KEY=
31 | STRIPE_SECRET=
32 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.css linguist-vendored
3 | *.less linguist-vendored
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /node_modules
3 | /public/storage
4 | Homestead.yaml
5 | Homestead.json
6 | .env
7 |
--------------------------------------------------------------------------------
/app/Auth/TwoFactor/Authenticatable.php:
--------------------------------------------------------------------------------
1 | email;
19 | }
20 |
21 | /**
22 | * Get the country code used for two-factor authentication.
23 | *
24 | * @return string
25 | */
26 | public function getAuthCountryCode()
27 | {
28 | return $this->phone_country_code;
29 | }
30 |
31 | /**
32 | * Get the phone number used for two-factor authentication.
33 | *
34 | * @return string
35 | */
36 | public function getAuthPhoneNumber()
37 | {
38 | return $this->phone_number;
39 | }
40 |
41 | /**
42 | * Set the country code and phone number used for two-factor authentication.
43 | *
44 | * @param string $countryCode
45 | * @param string $phoneNumber
46 | * @return void
47 | */
48 | public function setAuthPhoneInformation($countryCode, $phoneNumber)
49 | {
50 | $this->phone_country_code = $countryCode;
51 |
52 | $this->phone_number = $phoneNumber;
53 | }
54 |
55 | /**
56 | * Get the two-factor provider options in array format.
57 | *
58 | * @return array
59 | */
60 | public function getTwoFactorAuthProviderOptions()
61 | {
62 | return json_decode($this->two_factor_options, true) ?: [];
63 | }
64 |
65 | /**
66 | * Set the two-factor provider options in array format.
67 | *
68 | * @param array $options
69 | * @return void
70 | */
71 | public function setTwoFactorAuthProviderOptions(array $options)
72 | {
73 | $this->two_factor_options = json_encode($options);
74 | }
75 |
76 | /**
77 | * Determine if the user is using two-factor authentication.
78 | *
79 | * @return bool
80 | */
81 | public function getUsingTwoFactorAuthAttribute()
82 | {
83 | $options = $this->getTwoFactorAuthProviderOptions();
84 |
85 | return isset($options['id']);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/Billing/EmailInvoiceNotifier.php:
--------------------------------------------------------------------------------
1 | 'Vendor',
25 | 'product' => 'Product',
26 | 'vat' => new HtmlString(nl2br(e($user->extra_billing_info))),
27 | ], Spark::generateInvoicesWith());
28 |
29 | $data = compact('user', 'invoice', 'invoiceData');
30 |
31 | Mail::send('emails.billing.invoice', $data, function ($message) use ($user, $invoice, $invoiceData) {
32 | $message->to($user->email, $user->name)
33 | ->subject('Your '.$invoiceData['product'].' Invoice')
34 | ->attachData($invoice->pdf($invoiceData), 'invoice.pdf');
35 | });
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/Console/Commands/Inspire.php:
--------------------------------------------------------------------------------
1 | comment(PHP_EOL.Inspiring::quote().PHP_EOL);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/Console/Kernel.php:
--------------------------------------------------------------------------------
1 | command('inspire')
28 | // ->hourly();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Contracts/Auth/TwoFactor/Authenticatable.php:
--------------------------------------------------------------------------------
1 | team = $team;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Events/Team/Deleting.php:
--------------------------------------------------------------------------------
1 | team = $team;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Events/User/Event.php:
--------------------------------------------------------------------------------
1 | user = $user;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Events/User/JoinedTeam.php:
--------------------------------------------------------------------------------
1 | user = $user;
38 | $this->team = $team;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Events/User/ProfileUpdated.php:
--------------------------------------------------------------------------------
1 | user = $user;
38 | $this->team = $team;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/Events/User/Subscribed.php:
--------------------------------------------------------------------------------
1 | teams = $teams;
27 |
28 | $this->middleware('auth', ['except' => [
29 | 'getInvitation',
30 | ]]);
31 | }
32 |
33 | /**
34 | * Get all of the pending invitations for the user.
35 | *
36 | * @param \Illuminate\Http\Request $request
37 | * @return \Illuminate\Http\Response
38 | */
39 | public function getPendingInvitationsForUser(Request $request)
40 | {
41 | return $this->teams->getPendingInvitationsForUser($request->user());
42 | }
43 |
44 | /**
45 | * Get the invitation for the given code.
46 | *
47 | * Used to display coupon during registration.
48 | *
49 | * @param string $code
50 | * @return \Illuminate\Http\Response
51 | */
52 | public function getInvitation($code)
53 | {
54 | $model = config('auth.providers.users.model');
55 |
56 | $model = get_class((new $model)->invitations()
57 | ->getQuery()->getModel());
58 |
59 | $invitation = (new $model)->with('team.owner')
60 | ->where('token', $code)->firstOrFail();
61 |
62 | if ($invitation->isExpired()) {
63 | $invitation->delete();
64 |
65 | abort(404);
66 | }
67 |
68 | $invitation->team->setVisible(['name', 'owner']);
69 |
70 | $invitation->team->owner->setVisible(['name']);
71 |
72 | return $invitation;
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/Http/Controllers/API/SubscriptionController.php:
--------------------------------------------------------------------------------
1 | middleware('auth', ['only' => [
25 | 'getCouponForUser',
26 | ]]);
27 | }
28 |
29 | /**
30 | * Get all of the plans defined for the application.
31 | *
32 | * @return \Illuminate\Http\Response
33 | */
34 | public function getPlans()
35 | {
36 | return response()->json(Spark::plans());
37 | }
38 |
39 | /**
40 | * Get the coupon for a given code.
41 | *
42 | * Used for the registration page.
43 | *
44 | * @param string $code
45 | * @return \Illuminate\Http\Response
46 | */
47 | public function getCoupon($code)
48 | {
49 | Stripe::setApiKey(config('services.stripe.secret'));
50 |
51 | if (count(Spark::plans()) === 0) {
52 | abort(404);
53 | }
54 |
55 | try {
56 | return response()->json(
57 | Coupon::fromStripeCoupon(StripeCoupon::retrieve($code))
58 | );
59 | } catch (Exception $e) {
60 | abort(404);
61 | }
62 | }
63 |
64 | /**
65 | * Get the current coupon for the authenticated user.
66 | *
67 | * Used to display current discount on settings -> subscription tab.
68 | *
69 | * @return \Illuminate\Http\Response
70 | */
71 | public function getCouponForUser()
72 | {
73 | Stripe::setApiKey(config('services.stripe.secret'));
74 |
75 | if (count(Spark::plans()) === 0) {
76 | abort(404);
77 | }
78 |
79 | try {
80 | $customer = StripeCustomer::retrieve(Auth::user()->stripe_id);
81 |
82 | if ($customer->discount) {
83 | return response()->json(
84 | Coupon::fromStripeCoupon(
85 | StripeCoupon::retrieve($customer->discount->coupon->id)
86 | )
87 | );
88 | } else {
89 | abort(404);
90 | }
91 | } catch (Exception $e) {
92 | abort(404);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/Http/Controllers/API/TeamController.php:
--------------------------------------------------------------------------------
1 | teams = $teams;
30 |
31 | $this->middleware('auth', ['except' => [
32 | 'getInvitation',
33 | ]]);
34 | }
35 |
36 | /**
37 | * Get the team for the given ID.
38 | *
39 | * @param \Illuminate\Http\Request $request
40 | * @param int $teamId
41 | * @return \Illuminate\Http\Response
42 | */
43 | public function getTeam(Request $request, $teamId)
44 | {
45 | return $this->teams->getTeam($request->user(), $teamId);
46 | }
47 |
48 | /**
49 | * Get all of the teams for the user.
50 | *
51 | * @param \Illuminate\Http\Request $request
52 | * @return \Illuminate\Http\Response
53 | */
54 | public function getAllTeamsForUser(Request $request)
55 | {
56 | return $this->teams->getAllTeamsForUser($request->user());
57 | }
58 |
59 | /**
60 | * Get all of the available roles that may be assigned to team members.
61 | *
62 | * @return \Illuminate\Http\Response
63 | */
64 | public function getTeamRoles()
65 | {
66 | $roles = [];
67 |
68 | foreach (Spark::roles() as $key => $value) {
69 | $roles[] = ['value' => $key, 'text' => $value];
70 | }
71 |
72 | return response()->json($roles);
73 | }
74 |
75 | /**
76 | * Get all of the pending invitations for the user.
77 | *
78 | * @param \Illuminate\Http\Request $request
79 | * @return \Illuminate\Http\Response
80 | */
81 | public function getPendingInvitationsForUser(Request $request)
82 | {
83 | return $this->teams->getPendingInvitationsForUser($request->user());
84 | }
85 |
86 | /**
87 | * Get the invitation for the given code.
88 | *
89 | * @param string $code
90 | * @return \Illuminate\Http\Response
91 | */
92 | public function getInvitation($code)
93 | {
94 | $model = config('auth.providers.users.model');
95 |
96 | $model = get_class((new $model)->invitations()
97 | ->getQuery()->getModel());
98 |
99 | $invitation = (new $model)->with('team.owner')
100 | ->where('token', $code)->firstOrFail();
101 |
102 | if ($invitation->isExpired()) {
103 | $invitation->delete();
104 |
105 | abort(404);
106 | }
107 |
108 | $invitation->team->setVisible(['name', 'owner']);
109 |
110 | $invitation->team->owner->setVisible(['name']);
111 |
112 | return $invitation;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/app/Http/Controllers/API/UserController.php:
--------------------------------------------------------------------------------
1 | users = $users;
28 |
29 | $this->middleware('auth');
30 | }
31 |
32 | /**
33 | * Get the current user of the application.
34 | *
35 | * @return \Illuminate\Http\Response
36 | */
37 | public function getCurrentUser()
38 | {
39 | return $this->users->getCurrentUser();
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Auth/PasswordController.php:
--------------------------------------------------------------------------------
1 | middleware('guest');
31 | }
32 |
33 | /**
34 | * Display the form to request a password reset link.
35 | *
36 | * @return \Illuminate\Http\Response
37 | */
38 | public function getEmail()
39 | {
40 | return view('auth.password.email');
41 | }
42 |
43 | /**
44 | * Display the password reset view for the given token.
45 | *
46 | * @param string $token
47 | * @return \Illuminate\Http\Response
48 | */
49 | public function getReset($token = null)
50 | {
51 | if (is_null($token)) {
52 | throw new NotFoundHttpException;
53 | }
54 |
55 | return view('auth.password.reset')->with('token', $token);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | middleware('auth');
17 | }
18 |
19 | /**
20 | * Show the terms of service for the application.
21 | *
22 | * @return \Illuminate\Http\Response
23 | */
24 | public function show()
25 | {
26 | return view('home');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Settings/DashboardController.php:
--------------------------------------------------------------------------------
1 | users = $users;
30 |
31 | $this->middleware('auth');
32 | }
33 |
34 | /**
35 | * Show the settings dashboard.
36 | *
37 | * @param \Illuminate\Http\Request $request
38 | * @return \Illuminate\Http\Response
39 | */
40 | public function show(Request $request)
41 | {
42 | $data = [
43 | 'activeTab' => $request->get('tab', Spark::firstSettingsTabKey()),
44 | 'invoices' => [],
45 | 'user' => $this->users->getCurrentUser(),
46 | ];
47 |
48 | if (Auth::user()->stripe_id) {
49 | $data['invoices'] = Cache::remember('spark:invoices:'.Auth::id(), 30, function () {
50 | return Auth::user()->invoices();
51 | });
52 | }
53 |
54 | return view('settings.dashboard', $data);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Settings/ProfileController.php:
--------------------------------------------------------------------------------
1 | users = $users;
36 |
37 | $this->middleware('auth');
38 | }
39 |
40 | /**
41 | * Update the user's profile information.
42 | *
43 | * @param \Illuminate\Http\Request $request
44 | * @return \Illuminate\Http\Response
45 | */
46 | public function updateUserProfile(Request $request)
47 | {
48 | $this->validateUserProfile($request);
49 |
50 | $originalEmail = Auth::user()->email;
51 |
52 | if (Spark::$updateProfilesWith) {
53 | $this->callCustomUpdater(Spark::$updateProfilesWith, $request);
54 | } else {
55 | Auth::user()->fill($request->all())->save();
56 | }
57 |
58 | if (Auth::user()->stripe_id && $originalEmail !== Auth::user()->email) {
59 | $this->updateStripeEmailAddress();
60 | }
61 |
62 | event(new ProfileUpdated(Auth::user()));
63 |
64 | return $this->users->getCurrentUser();
65 | }
66 |
67 | /**
68 | * Validate the incoming request to update the user's profile.
69 | *
70 | * @param \Illuminate\Http\Request $request
71 | * @return void
72 | */
73 | protected function validateUserProfile(Request $request)
74 | {
75 | if (Spark::$validateProfileUpdatesWith) {
76 | $this->callCustomValidator(
77 | Spark::$validateProfileUpdatesWith, $request
78 | );
79 | } else {
80 | $this->validate($request, [
81 | 'name' => 'required|max:255',
82 | 'email' => 'required|email|unique:users,email,'.Auth::id(),
83 | ]);
84 | }
85 | }
86 |
87 | /**
88 | * Update the user's e-mail address on Stripe.
89 | *
90 | * @return void
91 | */
92 | protected function updateStripeEmailAddress()
93 | {
94 | $customer = Auth::user()->asStripeCustomer();
95 |
96 | $customer->email = Auth::user()->email;
97 |
98 | $customer->save();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/app/Http/Controllers/Stripe/WebhookController.php:
--------------------------------------------------------------------------------
1 | where(
24 | 'stripe_id', $payload['data']['object']['customer']
25 | )->first();
26 |
27 | if (is_null($user)) {
28 | return;
29 | }
30 |
31 | app(InvoiceNotifier::class)->notify(
32 | $user, $user->findInvoice($payload['data']['object']['id'])
33 | );
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/Http/Controllers/TermsController.php:
--------------------------------------------------------------------------------
1 | text(file_get_contents(base_path('terms.md')));
18 |
19 | return view('terms', compact('terms'));
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/Http/Controllers/WelcomeController.php:
--------------------------------------------------------------------------------
1 | [
27 | \App\Http\Middleware\EncryptCookies::class,
28 | \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
29 | \Illuminate\Session\Middleware\StartSession::class,
30 | \Illuminate\View\Middleware\ShareErrorsFromSession::class,
31 | \App\Http\Middleware\VerifyCsrfToken::class,
32 | ],
33 |
34 | 'api' => [
35 | 'throttle:60,1',
36 | ],
37 | ];
38 |
39 | /**
40 | * The application's route middleware.
41 | *
42 | * These middleware may be assigned to groups or used individually.
43 | *
44 | * @var array
45 | */
46 | protected $routeMiddleware = [
47 | 'auth' => \App\Http\Middleware\Authenticate::class,
48 | 'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
49 | 'can' => \Illuminate\Foundation\Http\Middleware\Authorize::class,
50 | 'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
51 | 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
52 | ];
53 | }
54 |
--------------------------------------------------------------------------------
/app/Http/Middleware/Authenticate.php:
--------------------------------------------------------------------------------
1 | auth = $auth;
26 | }
27 |
28 | /**
29 | * Handle an incoming request.
30 | *
31 | * @param \Illuminate\Http\Request $request
32 | * @param \Closure $next
33 | * @return mixed
34 | */
35 | public function handle($request, Closure $next)
36 | {
37 | if ($this->auth->guest()) {
38 | if ($request->ajax()) {
39 | return response('Unauthorized.', 401);
40 | } else {
41 | return redirect()->guest('/login');
42 | }
43 | }
44 |
45 | return $next($request);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/Http/Middleware/EncryptCookies.php:
--------------------------------------------------------------------------------
1 | check()) {
21 | return redirect('/');
22 | }
23 |
24 | return $next($request);
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Http/Middleware/VerifyCsrfToken.php:
--------------------------------------------------------------------------------
1 | getCustomValidator($callback, $request, $arguments);
23 | } else {
24 | $validator = $callback;
25 | }
26 |
27 | if ($validator->fails()) {
28 | $this->throwValidationException($request, $validator);
29 | }
30 | }
31 |
32 | /**
33 | * Get the custom validator based on the given callback.
34 | *
35 | * @param callable|string $callback
36 | * @param \Illuminate\Http\Request $request
37 | * @param array $arguments
38 | * @return \Illuminate\ContractsValidation\Validator
39 | */
40 | protected function getCustomValidator($callback, Request $request, array $arguments = [])
41 | {
42 | if (is_string($callback)) {
43 | list($class, $method) = explode('@', $callback);
44 |
45 | $callback = [app($class), $method];
46 | }
47 |
48 | $validator = call_user_func_array($callback, array_merge([$request], $arguments));
49 |
50 | return $validator instanceof ValidatorContract
51 | ? $validator
52 | : Validator::make($request->all(), $validator);
53 | }
54 |
55 | /**
56 | * Call a custom Spark updater callback.
57 | *
58 | * @param callable|string $callback
59 | * @param \Illuminate\Http\Request $request
60 | * @param array $arguments
61 | * @return mixed
62 | */
63 | public function callCustomUpdater($callback, Request $request, array $arguments = [])
64 | {
65 | if (is_string($callback)) {
66 | list($class, $method) = explode('@', $callback);
67 |
68 | $callback = [app($class), $method];
69 | }
70 |
71 | return call_user_func_array($callback, array_merge([$request], $arguments));
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/Jobs/Job.php:
--------------------------------------------------------------------------------
1 | 'App\Policies\ModelPolicy',
17 | ];
18 |
19 | /**
20 | * Register any application authentication / authorization services.
21 | *
22 | * @param \Illuminate\Contracts\Auth\Access\Gate $gate
23 | * @return void
24 | */
25 | public function boot(GateContract $gate)
26 | {
27 | $this->registerPolicies($gate);
28 |
29 | //
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/Providers/EventServiceProvider.php:
--------------------------------------------------------------------------------
1 | [
17 | 'App\Listeners\EventListener',
18 | ],
19 | ];
20 |
21 | /**
22 | * Register any other events for your application.
23 | *
24 | * @param \Illuminate\Contracts\Events\Dispatcher $events
25 | * @return void
26 | */
27 | public function boot(DispatcherContract $events)
28 | {
29 | parent::boot($events);
30 |
31 | //
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/app/Providers/RouteServiceProvider.php:
--------------------------------------------------------------------------------
1 | mapWebRoutes($router);
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 | * @param \Illuminate\Routing\Router $router
51 | * @return void
52 | */
53 | protected function mapWebRoutes(Router $router)
54 | {
55 | $router->group([
56 | 'namespace' => $this->namespace, 'middleware' => 'web',
57 | ], function ($router) {
58 | require app_path('Http/routes.php');
59 | });
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/Providers/SparkServiceProvider.php:
--------------------------------------------------------------------------------
1 | app->booted(function () {
28 | $this->defineRoutes();
29 | });
30 | }
31 |
32 | /**
33 | * Define the Spark routes.
34 | *
35 | * @return void
36 | */
37 | protected function defineRoutes()
38 | {
39 | if (! $this->app->routesAreCached()) {
40 | $router = app('router');
41 |
42 | $router->group(['namespace' => 'App\Http\Controllers'], function ($router) {
43 | require __DIR__.'/../Http/routes.php';
44 | });
45 | }
46 | }
47 |
48 | /**
49 | * Register any application services.
50 | *
51 | * @return void
52 | */
53 | public function register()
54 | {
55 | $this->defineServices();
56 | }
57 |
58 | /**
59 | * Bind the Spark services into the container.
60 | *
61 | * @return void
62 | */
63 | protected function defineServices()
64 | {
65 | $services = [
66 | RegistrarContract::class => Registrar::class,
67 | InvoiceNotifier::class => EmailInvoiceNotifier::class,
68 | UserRepositoryContract::class => UserRepository::class,
69 | TeamRepositoryContract::class => TeamRepository::class,
70 | ];
71 |
72 | foreach ($services as $key => $value) {
73 | $this->app->bindIf($key, $value);
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/Repositories/TeamRepository.php:
--------------------------------------------------------------------------------
1 | $data['name']]);
23 |
24 | $team->owner_id = $user->id;
25 |
26 | $team->save();
27 |
28 | $team = $user->teams()->attach(
29 | $team, ['role' => 'owner']
30 | );
31 |
32 | return $team;
33 | }
34 |
35 | /**
36 | * Get the team for the given ID.
37 | *
38 | * @param \Illuminate\Contracts\Auth\Authenticatable $user
39 | * @param string $teamId
40 | * @return \Illuminate\Database\Eloquent\Model
41 | */
42 | public function getTeam($user, $teamId)
43 | {
44 | return $user->teams()->with('users', 'invitations')->findOrFail($teamId);
45 | }
46 |
47 | /**
48 | * Get all of the teams for the user.
49 | *
50 | * @param \Illuminate\Contracts\Auth\Authenticatable $user
51 | * @return \Illuminate\Database\Eloquent\Collection
52 | */
53 | public function getAllTeamsForUser($user)
54 | {
55 | $teams = $user->teams()->with('owner')->get();
56 |
57 | foreach ($teams as $team) {
58 | $team->owner->setVisible(['name']);
59 | }
60 |
61 | return $teams;
62 | }
63 |
64 | /**
65 | * Get all of the pending invitations for the user.
66 | *
67 | * @param \Illuminate\Contracts\Auth\Authenticatable $user
68 | * @return \Illuminate\Database\Eloquent\Collection
69 | */
70 | public function getPendingInvitationsForUser($user)
71 | {
72 | $invitations = $user->invitations()->with('team.owner')->get();
73 |
74 | foreach ($invitations as $invite) {
75 | $invite->setVisible(['id', 'team']);
76 |
77 | $invite->team->setVisible(['name', 'owner']);
78 |
79 | $invite->team->owner->setVisible(['name']);
80 | }
81 |
82 | return $invitations;
83 | }
84 |
85 | /**
86 | * Attach a user to a given team based on their invitation.
87 | *
88 | * @param string $invitationId
89 | * @param \Illuminate\Contracts\Auth\Authenticatable $user
90 | * @return void
91 | */
92 | public function attachUserToTeamByInvitation($invitationId, $user)
93 | {
94 | $userModel = get_class($user);
95 |
96 | $inviteModel = get_class((new $userModel)->invitations()->getQuery()->getModel());
97 |
98 | $invitation = (new $inviteModel)->where('token', $invitationId)->first();
99 |
100 | if ($invitation) {
101 | $user->joinTeamById($invitation->team->id);
102 |
103 | $user->switchToTeam($invitation->team);
104 |
105 | $invitation->delete();
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/app/Services/Auth/TwoFactor/Authy.php:
--------------------------------------------------------------------------------
1 | getTwoFactorAuthProviderOptions()['id']);
21 | }
22 |
23 | /**
24 | * Register the given user with the provider.
25 | *
26 | * @param \App\Contracts\Auth\TwoFactor\Authenticatable $user
27 | * @return void
28 | */
29 | public function register(TwoFactorAuthenticatable $user)
30 | {
31 | $key = config('services.authy.key');
32 |
33 | $response = json_decode((new HttpClient)->post('https://api.authy.com/protected/json/users/new?api_key='.$key, [
34 | 'form_params' => [
35 | 'user' => [
36 | 'email' => $user->getEmailForTwoFactorAuth(),
37 | 'cellphone' => preg_replace('/[^0-9]/', '', $user->getAuthPhoneNumber()),
38 | 'country_code' => $user->getAuthCountryCode(),
39 | ],
40 | ],
41 | ])->getBody(), true);
42 |
43 | $user->setTwoFactorAuthProviderOptions([
44 | 'id' => $response['user']['id'],
45 | ]);
46 | }
47 |
48 | /**
49 | * Determine if the given token is valid for the given user.
50 | *
51 | * @param \App\Contracts\Auth\TwoFactor\Authenticatable $user
52 | * @param string $token
53 | * @return bool
54 | */
55 | public function tokenIsValid(TwoFactorAuthenticatable $user, $token)
56 | {
57 | try {
58 | $key = config('services.authy.key');
59 |
60 | $options = $user->getTwoFactorAuthProviderOptions();
61 |
62 | $response = json_decode((new HttpClient)->get(
63 | 'https://api.authy.com/protected/json/verify/'.$token.'/'.$options['id'].'?force=true&api_key='.$key
64 | )->getBody(), true);
65 |
66 | return $response['token'] === 'is valid';
67 | } catch (Exception $e) {
68 | return false;
69 | }
70 | }
71 |
72 | /**
73 | * Delete the given user from the provider.
74 | *
75 | * @param \App\Contracts\Auth\TwoFactor\Authenticatable $user
76 | * @return bool
77 | */
78 | public function delete(TwoFactorAuthenticatable $user)
79 | {
80 | $key = config('services.authy.key');
81 |
82 | $options = $user->getTwoFactorAuthProviderOptions();
83 |
84 | (new HttpClient)->post(
85 | 'https://api.authy.com/protected/json/users/delete/'.$options['id'].'?api_key='.$key
86 | );
87 |
88 | $user->setTwoFactorAuthProviderOptions([]);
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/Team.php:
--------------------------------------------------------------------------------
1 | belongsTo(Spark::model('teams', Team::class), 'team_id');
38 | }
39 |
40 | /**
41 | * Determine if the coupon is expired.
42 | *
43 | * @return bool
44 | */
45 | public function isExpired()
46 | {
47 | return Carbon::now()->subWeek()->gte($this->created_at);
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/app/Teams/Team.php:
--------------------------------------------------------------------------------
1 | belongsToMany(
32 | config('auth.providers.users.model'), 'user_teams', 'team_id', 'user_id'
33 | )->withPivot('role');
34 | }
35 |
36 | /**
37 | * Get the owner of the team.
38 | */
39 | public function owner()
40 | {
41 | return $this->belongsTo(config('auth.providers.users.model'), 'owner_id');
42 | }
43 |
44 | /**
45 | * Get all of the pending invitations for the team.
46 | */
47 | public function invitations()
48 | {
49 | return $this->hasMany(Invitation::class)->orderBy('created_at', 'desc');
50 | }
51 |
52 | /**
53 | * Invite a user to the team by e-mail address.
54 | *
55 | * @param string $email
56 | * @return \App\Teams\Invitation
57 | */
58 | public function inviteUserByEmail($email)
59 | {
60 | $model = config('auth.providers.users.model');
61 |
62 | $invitedUser = (new $model)->where('email', $email)->first();
63 |
64 | $invitation = $this->invitations()
65 | ->where('email', $email)->first();
66 |
67 | if (! $invitation) {
68 | $invitation = $this->invitations()->create([
69 | 'user_id' => $invitedUser ? $invitedUser->id : null,
70 | 'email' => $email,
71 | 'token' => str_random(40),
72 | ]);
73 | }
74 |
75 | $email = $invitation->user_id
76 | ? 'emails.team.invitations.existing'
77 | : 'emails.team.invitations.new';
78 |
79 | Mail::send($email, compact('invitation'), function ($m) use ($invitation) {
80 | $m->to($invitation->email)->subject('New Invitation!');
81 | });
82 |
83 | return $invitation;
84 | }
85 |
86 | /**
87 | * Remove a user from the team by their ID.
88 | *
89 | * @param int $userId
90 | * @return void
91 | */
92 | public function removeUserById($userId)
93 | {
94 | $this->users()->detach([$userId]);
95 |
96 | $userModel = config('auth.providers.users.model');
97 |
98 | $removedUser = (new $userModel)->find($userId);
99 |
100 | if ($removedUser) {
101 | $removedUser->refreshCurrentTeam();
102 | }
103 |
104 | event(new RemovedFromTeam($removedUser, $this));
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/User.php:
--------------------------------------------------------------------------------
1 | paid()) > 0 || $force;
51 | });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/Ux/Settings/Tab.php:
--------------------------------------------------------------------------------
1 | name = $name;
54 | $this->view = $view;
55 | $this->icon = $icon;
56 | $this->displayable = $displayable;
57 | $this->key = str_slug($this->name);
58 | }
59 |
60 | /**
61 | * Determine if the tab should be displayed.
62 | *
63 | * @return bool
64 | */
65 | public function displayable()
66 | {
67 | if ($this->displayable) {
68 | return call_user_func_array($this->displayable, func_get_args());
69 | }
70 |
71 | return true;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/app/Ux/Settings/Tabs.php:
--------------------------------------------------------------------------------
1 | tabs = $tabs;
25 | }
26 |
27 | /**
28 | * Define the settings tabs configuration.
29 | *
30 | * @param callable $callback
31 | * @return $this
32 | */
33 | public function configure(callable $callback)
34 | {
35 | $this->tabs = array_filter(call_user_func($callback, $this));
36 |
37 | return $this;
38 | }
39 |
40 | /**
41 | * Create a new custom tab instance.
42 | *
43 | * @param string $name
44 | * @param string $view
45 | * @param string $icon
46 | * @return \App\Ux\Settings\Tab
47 | */
48 | public function make($name, $view, $icon, callable $displayable = null)
49 | {
50 | return new Tab($name, $view, $icon, $displayable);
51 | }
52 |
53 | /**
54 | * Get an array of the displayable tabs.
55 | *
56 | * @param dynamic
57 | * @return array
58 | */
59 | public function displayable()
60 | {
61 | $arguments = func_get_args();
62 |
63 | return array_values(array_filter($this->tabs, function ($tab) use ($arguments) {
64 | return call_user_func_array([$tab, 'displayable'], $arguments);
65 | }));
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/Ux/Settings/TeamTabs.php:
--------------------------------------------------------------------------------
1 | ownsTeam($team);
16 | });
17 | }
18 |
19 | /**
20 | * Get the tab configuration for the "Membership" tab.
21 | *
22 | * @return \App\Ux\Settings\Tab
23 | */
24 | public function membership()
25 | {
26 | return new Tab('Membership', 'settings.team.tabs.membership', 'fa-users');
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/artisan:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env php
2 | make(Illuminate\Contracts\Console\Kernel::class);
32 |
33 | $status = $kernel->handle(
34 | $input = new Symfony\Component\Console\Input\ArgvInput,
35 | new Symfony\Component\Console\Output\ConsoleOutput
36 | );
37 |
38 | /*
39 | |--------------------------------------------------------------------------
40 | | Shutdown The Application
41 | |--------------------------------------------------------------------------
42 | |
43 | | Once Artisan has finished running. We will fire off the shutdown events
44 | | so that any final work may be done by the application before we shut
45 | | down the process. This is the last thing to happen to the request.
46 | |
47 | */
48 |
49 | $kernel->terminate($input, $status);
50 |
51 | exit($status);
52 |
--------------------------------------------------------------------------------
/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/autoload.php:
--------------------------------------------------------------------------------
1 | =5.5.9",
9 | "laravel/framework": "5.2.*",
10 | "laravel/cashier": "~6.0",
11 | "erusev/parsedown": "~1.0",
12 | "guzzlehttp/guzzle": "~6.0",
13 | "dompdf/dompdf": "^0.6.2"
14 | },
15 | "require-dev": {
16 | "fzaninotto/faker": "~1.4",
17 | "mockery/mockery": "0.9.*",
18 | "phpunit/phpunit": "~4.0",
19 | "symfony/css-selector": "2.8.*|3.0.*",
20 | "symfony/dom-crawler": "2.8.*|3.0.*"
21 | },
22 | "autoload": {
23 | "classmap": [
24 | "database"
25 | ],
26 | "psr-4": {
27 | "App\\": "app/"
28 | }
29 | },
30 | "autoload-dev": {
31 | "classmap": [
32 | "tests/TestCase.php"
33 | ]
34 | },
35 | "scripts": {
36 | "post-root-package-install": [
37 | "php -r \"copy('.env.example', '.env');\""
38 | ],
39 | "post-create-project-cmd": [
40 | "php artisan key:generate"
41 | ],
42 | "post-install-cmd": [
43 | "Illuminate\\Foundation\\ComposerScripts::postInstall",
44 | "php artisan optimize"
45 | ],
46 | "post-update-cmd": [
47 | "Illuminate\\Foundation\\ComposerScripts::postUpdate",
48 | "php artisan optimize"
49 | ]
50 | },
51 | "config": {
52 | "preferred-install": "dist"
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/config/broadcasting.php:
--------------------------------------------------------------------------------
1 | env('BROADCAST_DRIVER', 'pusher'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Broadcast Connections
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may define all of the broadcast connections that will be used
24 | | to broadcast events to other systems or over websockets. Samples of
25 | | each available type of connection are provided inside this array.
26 | |
27 | */
28 |
29 | 'connections' => [
30 |
31 | 'pusher' => [
32 | 'driver' => 'pusher',
33 | 'key' => env('PUSHER_KEY'),
34 | 'secret' => env('PUSHER_SECRET'),
35 | 'app_id' => env('PUSHER_APP_ID'),
36 | 'options' => [
37 | //
38 | ],
39 | ],
40 |
41 | 'redis' => [
42 | 'driver' => 'redis',
43 | 'connection' => 'default',
44 | ],
45 |
46 | 'log' => [
47 | 'driver' => 'log',
48 | ],
49 |
50 | ],
51 |
52 | ];
53 |
--------------------------------------------------------------------------------
/config/cache.php:
--------------------------------------------------------------------------------
1 | env('CACHE_DRIVER', 'file'),
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Cache Stores
21 | |--------------------------------------------------------------------------
22 | |
23 | | Here you may define all of the cache "stores" for your application as
24 | | well as their drivers. You may even define multiple stores for the
25 | | same cache driver to group types of items stored in your caches.
26 | |
27 | */
28 |
29 | 'stores' => [
30 |
31 | 'apc' => [
32 | 'driver' => 'apc',
33 | ],
34 |
35 | 'array' => [
36 | 'driver' => 'array',
37 | ],
38 |
39 | 'database' => [
40 | 'driver' => 'database',
41 | 'table' => 'cache',
42 | 'connection' => null,
43 | ],
44 |
45 | 'file' => [
46 | 'driver' => 'file',
47 | 'path' => storage_path('framework/cache'),
48 | ],
49 |
50 | 'memcached' => [
51 | 'driver' => 'memcached',
52 | 'servers' => [
53 | [
54 | 'host' => env('MEMCACHED_HOST', '127.0.0.1'),
55 | 'port' => env('MEMCACHED_PORT', 11211),
56 | 'weight' => 100,
57 | ],
58 | ],
59 | ],
60 |
61 | 'redis' => [
62 | 'driver' => 'redis',
63 | 'connection' => 'default',
64 | ],
65 |
66 | ],
67 |
68 | /*
69 | |--------------------------------------------------------------------------
70 | | Cache Key Prefix
71 | |--------------------------------------------------------------------------
72 | |
73 | | When utilizing a RAM based store such as APC or Memcached, there might
74 | | be other applications utilizing the same cache. So, we'll specify a
75 | | value to get prefixed to all our keys so we can avoid collisions.
76 | |
77 | */
78 |
79 | 'prefix' => 'laravel',
80 |
81 | ];
82 |
--------------------------------------------------------------------------------
/config/compile.php:
--------------------------------------------------------------------------------
1 | [
17 | //
18 | ],
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Compiled File Providers
23 | |--------------------------------------------------------------------------
24 | |
25 | | Here you may list service providers which define a "compiles" function
26 | | that returns additional files that should be compiled, providing an
27 | | easy way to get common files from any packages you are utilizing.
28 | |
29 | */
30 |
31 | 'providers' => [
32 | //
33 | ],
34 |
35 | ];
36 |
--------------------------------------------------------------------------------
/config/filesystems.php:
--------------------------------------------------------------------------------
1 | 'local',
19 |
20 | /*
21 | |--------------------------------------------------------------------------
22 | | Default Cloud Filesystem Disk
23 | |--------------------------------------------------------------------------
24 | |
25 | | Many applications store files both locally and in the cloud. For this
26 | | reason, you may specify a default "cloud" driver here. This driver
27 | | will be bound as the Cloud disk implementation in the container.
28 | |
29 | */
30 |
31 | 'cloud' => 's3',
32 |
33 | /*
34 | |--------------------------------------------------------------------------
35 | | Filesystem Disks
36 | |--------------------------------------------------------------------------
37 | |
38 | | Here you may configure as many filesystem "disks" as you wish, and you
39 | | may even configure multiple disks of the same driver. Defaults have
40 | | been setup for each driver as an example of the required options.
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 | 'visibility' => 'public',
55 | ],
56 |
57 | 's3' => [
58 | 'driver' => 's3',
59 | 'key' => 'your-key',
60 | 'secret' => 'your-secret',
61 | 'region' => 'your-region',
62 | 'bucket' => 'your-bucket',
63 | ],
64 |
65 | ],
66 |
67 | ];
68 |
--------------------------------------------------------------------------------
/config/queue.php:
--------------------------------------------------------------------------------
1 | env('QUEUE_DRIVER', '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 | 'expire' => 60,
42 | ],
43 |
44 | 'beanstalkd' => [
45 | 'driver' => 'beanstalkd',
46 | 'host' => 'localhost',
47 | 'queue' => 'default',
48 | 'ttr' => 60,
49 | ],
50 |
51 | 'sqs' => [
52 | 'driver' => 'sqs',
53 | 'key' => 'your-public-key',
54 | 'secret' => 'your-secret-key',
55 | 'prefix' => 'https://sqs.us-east-1.amazonaws.com/your-account-id',
56 | 'queue' => 'your-queue-name',
57 | 'region' => 'us-east-1',
58 | ],
59 |
60 | 'redis' => [
61 | 'driver' => 'redis',
62 | 'connection' => 'default',
63 | 'queue' => 'default',
64 | 'expire' => 60,
65 | ],
66 |
67 | ],
68 |
69 | /*
70 | |--------------------------------------------------------------------------
71 | | Failed Queue Jobs
72 | |--------------------------------------------------------------------------
73 | |
74 | | These options configure the behavior of failed queue job logging so you
75 | | can control which database and table are used to store the jobs that
76 | | have failed. You may change them to any database / table you wish.
77 | |
78 | */
79 |
80 | 'failed' => [
81 | 'database' => env('DB_CONNECTION', 'mysql'),
82 | 'table' => 'failed_jobs',
83 | ],
84 |
85 | ];
86 |
--------------------------------------------------------------------------------
/config/services.php:
--------------------------------------------------------------------------------
1 | [
18 | 'domain' => env('MAILGUN_DOMAIN'),
19 | 'secret' => env('MAILGUN_SECRET'),
20 | ],
21 |
22 | 'ses' => [
23 | 'key' => env('SES_KEY'),
24 | 'secret' => env('SES_SECRET'),
25 | 'region' => 'us-east-1',
26 | ],
27 |
28 | 'sparkpost' => [
29 | 'secret' => env('SPARKPOST_SECRET'),
30 | ],
31 |
32 | 'stripe' => [
33 | 'model' => App\User::class,
34 | 'key' => env('STRIPE_KEY'),
35 | 'secret' => env('STRIPE_SECRET'),
36 | ],
37 |
38 | ];
39 |
--------------------------------------------------------------------------------
/config/view.php:
--------------------------------------------------------------------------------
1 | [
17 | realpath(base_path('resources/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' => realpath(storage_path('framework/views')),
32 |
33 | ];
34 |
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | *.sqlite
2 |
--------------------------------------------------------------------------------
/database/factories/ModelFactory.php:
--------------------------------------------------------------------------------
1 | define(App\User::class, function (Faker\Generator $faker) {
15 | return [
16 | 'name' => $faker->name,
17 | 'email' => $faker->safeEmail,
18 | 'password' => bcrypt(str_random(10)),
19 | 'remember_token' => str_random(10),
20 | ];
21 | });
22 |
--------------------------------------------------------------------------------
/database/migrations/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_000000_create_users_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
17 | $table->string('name');
18 | $table->string('email')->unique();
19 | $table->string('password', 60);
20 | $table->rememberToken();
21 |
22 | // Two-Factor Authentication Columns...
23 | $table->string('phone_country_code')->nullable();
24 | $table->string('phone_number')->nullable();
25 | $table->text('two_factor_options')->nullable();
26 |
27 | // Team Columns...
28 | $table->integer('current_team_id')->nullable();
29 |
30 | // Cashier Columns...
31 | $table->string('stripe_id')->nullable();
32 | $table->string('card_brand', 25)->nullable();
33 | $table->string('card_last_four', 4)->nullable();
34 | $table->text('extra_billing_info')->nullable();
35 |
36 | $table->timestamps();
37 | });
38 | }
39 |
40 | /**
41 | * Reverse the migrations.
42 | *
43 | * @return void
44 | */
45 | public function down()
46 | {
47 | Schema::drop('users');
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_100000_create_password_resets_table.php:
--------------------------------------------------------------------------------
1 | string('email')->index();
17 | $table->string('token')->index();
18 | $table->timestamp('created_at');
19 | });
20 | }
21 |
22 | /**
23 | * Reverse the migrations.
24 | *
25 | * @return void
26 | */
27 | public function down()
28 | {
29 | Schema::drop('password_resets');
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_100000_create_subscriptions_table.php:
--------------------------------------------------------------------------------
1 | increments('id');
17 | $table->integer('user_id')->index();
18 | $table->string('name');
19 | $table->string('stripe_id');
20 | $table->string('stripe_plan');
21 | $table->integer('quantity');
22 | $table->timestamp('trial_ends_at')->nullable();
23 | $table->timestamp('ends_at')->nullable();
24 | $table->timestamps();
25 | });
26 | }
27 |
28 | /**
29 | * Reverse the migrations.
30 | *
31 | * @return void
32 | */
33 | public function down()
34 | {
35 | Schema::drop('subscriptions');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/database/migrations/2014_10_12_200000_create_teams_tables.php:
--------------------------------------------------------------------------------
1 | increments('id');
18 | $table->integer('owner_id')->index()->nullable();
19 | $table->string('name');
20 | $table->timestamps();
21 | });
22 |
23 | // Create User Teams Intermediate Table...
24 | Schema::create('user_teams', function (Blueprint $table) {
25 | $table->integer('team_id');
26 | $table->integer('user_id');
27 | $table->string('role', 25);
28 |
29 | $table->unique(['team_id', 'user_id']);
30 | });
31 |
32 | // Create Invitations Table...
33 | Schema::create('invitations', function (Blueprint $table) {
34 | $table->increments('id');
35 | $table->integer('team_id')->index();
36 | $table->integer('user_id')->nullable()->index();
37 | $table->string('email');
38 | $table->string('token', 40)->unique();
39 | $table->timestamps();
40 | });
41 | }
42 |
43 | /**
44 | * Reverse the migrations.
45 | *
46 | * @return void
47 | */
48 | public function down()
49 | {
50 | Schema::drop('teams');
51 | Schema::drop('user_teams');
52 | Schema::drop('invitations');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/database/seeds/.gitkeep:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/database/seeds/DatabaseSeeder.php:
--------------------------------------------------------------------------------
1 | call(UsersTableSeeder::class);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var elixir = require('laravel-elixir');
2 |
3 | /*
4 | |--------------------------------------------------------------------------
5 | | Elixir Asset Management
6 | |--------------------------------------------------------------------------
7 | |
8 | | Elixir provides a clean, fluent API for defining some basic Gulp tasks
9 | | for your Laravel application. By default, we are compiling the Sass
10 | | file for our application, as well as publishing vendor resources.
11 | |
12 | */
13 |
14 | elixir(function(mix) {
15 | mix.sass('app.scss')
16 | .browserify('app.js')
17 | .version(['css/app.css', 'js/app.js']);
18 | });
19 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "devDependencies": {
4 | "gulp": "^3.9.1"
5 | },
6 | "dependencies": {
7 | "laravel-elixir": "^5.0.0",
8 | "jquery": "^2.1.4",
9 | "bootstrap-sass": "^3.0.0",
10 | "moment": "^2.10.6",
11 | "promise": "^7.0.4",
12 | "underscore": "^1.8.3",
13 | "vue": "^1.0.0",
14 | "vue-resource": "^0.1.11"
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
Date | 11 |Amount | 12 |Receipt | 13 |
---|---|---|
20 | {{ $invoice->date()->format('Y-m-d') }} 21 | | 22 |23 | {{ $invoice->total() }} 24 | | 25 |26 | 27 | Download 28 | 29 | | 30 |