├── .env.example ├── .gitattributes ├── .gitignore ├── LICENSE ├── Procfile ├── README.md ├── app.json ├── app ├── Commands │ └── Command.php ├── Console │ ├── Commands │ │ ├── .gitkeep │ │ ├── FollowAdminCommand.php │ │ └── Inspire.php │ └── Kernel.php ├── Events │ └── Event.php ├── Exceptions │ └── Handler.php ├── Handlers │ ├── Commands │ │ └── .gitkeep │ └── Events │ │ └── .gitkeep ├── Http │ ├── Controllers │ │ ├── .gitkeep │ │ ├── Auth │ │ │ ├── AuthController.php │ │ │ └── PasswordController.php │ │ ├── BaseController.php │ │ ├── Controller.php │ │ ├── FeedController.php │ │ ├── FollowController.php │ │ ├── HomeController.php │ │ ├── LandingController.php │ │ ├── PinController.php │ │ ├── ProfileController.php │ │ └── WelcomeController.php │ ├── Kernel.php │ ├── Middleware │ │ ├── Authenticate.php │ │ ├── RedirectIfAuthenticated.php │ │ └── VerifyCsrfToken.php │ ├── Requests │ │ └── Request.php │ └── routes.php ├── Models │ ├── Follow.php │ ├── Item.php │ ├── Pin.php │ └── User.php ├── Providers │ ├── AppServiceProvider.php │ ├── BusServiceProvider.php │ ├── ConfigServiceProvider.php │ ├── EventServiceProvider.php │ └── RouteServiceProvider.php ├── Services │ └── Registrar.php └── User.php ├── artisan ├── bootstrap ├── app.php └── autoload.php ├── composer.json ├── composer.lock ├── config ├── app.php ├── auth.php ├── cache.php ├── compile.php ├── database.php ├── filesystems.php ├── mail.php ├── queue.php ├── services.php ├── session.php └── view.php ├── database ├── .gitignore ├── migrations │ ├── .gitkeep │ ├── 2014_10_01_122413_create_users_table.php │ ├── 2014_10_01_134126_create_items_table.php │ ├── 2014_10_01_161014_create_pins_table.php │ ├── 2014_10_02_100110_create_follows_table.php │ ├── 2014_10_12_100000_create_password_resets_table.php │ ├── 2014_10_15_121152_add_deleted_at_to_follow_table.php │ └── 2014_10_15_121340_add_deleted_at_to_item_table.php └── seeds │ ├── .gitkeep │ ├── DatabaseSeeder.php │ ├── ItemTableSeeder.php │ ├── UserTableSeeder.php │ └── items.json ├── install ├── phpspec.yml ├── phpunit.xml ├── provision.sh ├── public ├── .htaccess ├── css │ ├── _feedly.scss │ ├── bootstrap │ │ ├── LICENSE │ │ ├── _alerts.scss │ │ ├── _badges.scss │ │ ├── _breadcrumbs.scss │ │ ├── _button-groups.scss │ │ ├── _buttons.scss │ │ ├── _carousel.scss │ │ ├── _close.scss │ │ ├── _code.scss │ │ ├── _component-animations.scss │ │ ├── _dropdowns.scss │ │ ├── _forms.scss │ │ ├── _glyphicons.scss │ │ ├── _grid.scss │ │ ├── _input-groups.scss │ │ ├── _jumbotron.scss │ │ ├── _labels.scss │ │ ├── _list-group.scss │ │ ├── _media.scss │ │ ├── _mixins.scss │ │ ├── _modals.scss │ │ ├── _navbar.scss │ │ ├── _navs.scss │ │ ├── _normalize.scss │ │ ├── _pager.scss │ │ ├── _pagination.scss │ │ ├── _panels.scss │ │ ├── _popovers.scss │ │ ├── _print.scss │ │ ├── _progress-bars.scss │ │ ├── _responsive-utilities.scss │ │ ├── _scaffolding.scss │ │ ├── _tables.scss │ │ ├── _theme.scss │ │ ├── _thumbnails.scss │ │ ├── _tooltip.scss │ │ ├── _type.scss │ │ ├── _utilities.scss │ │ ├── _variables.scss │ │ └── _wells.scss │ ├── style.css │ └── style.scss ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ └── glyphicons-halflings-regular.woff ├── index.php ├── items │ ├── 011.jpg │ ├── 144568575661705981_4ade117f9005.jpeg │ ├── 164471358120532732_a3ae9498ad97.jpg │ ├── 167111313523218252_a6046175bb32.jpeg │ ├── 184290639033145973_71e492dacec6.jpeg │ ├── 1addc6292e97f5f796cf368df5d4b322.jpg │ ├── 261379381_d8fde25053fa.jpg │ ├── 272236897059737539_c4806e0b14c4.jpg │ ├── 272236897059737539_c4806e0b14c4_1.jpg │ ├── 275088534757448505_4f85768f71ec.jpg │ ├── 281485446934954481_c04cfc76c018.png │ ├── 293704645_b4c6dab8ed99.jpg │ ├── 389230669370236857_335b7c698687.jpg │ ├── 6fb3f77d22ddc23305fcd1a2fd8c4e80.jpg │ ├── 6fb3f77d22ddc23305fcd1a2fd8c4e80_1.jpg │ ├── 70602b9d4f965eee1d0345d99622798f.jpg │ ├── 73f98aae32e30a729bdf1031a78db65a.jpg │ ├── e2110e4545f1cc11043659fdd9c07b43.jpg │ └── e315a43813c5533b9f47754406019e24.jpg ├── js │ ├── bootstrap.js │ └── bootstrap.min.js ├── packages │ └── .gitkeep └── robots.txt ├── resources ├── assets │ └── less │ │ ├── app.less │ │ └── bootstrap │ │ ├── alerts.less │ │ ├── badges.less │ │ ├── bootstrap.less │ │ ├── breadcrumbs.less │ │ ├── button-groups.less │ │ ├── buttons.less │ │ ├── carousel.less │ │ ├── close.less │ │ ├── code.less │ │ ├── component-animations.less │ │ ├── dropdowns.less │ │ ├── forms.less │ │ ├── glyphicons.less │ │ ├── grid.less │ │ ├── input-groups.less │ │ ├── jumbotron.less │ │ ├── labels.less │ │ ├── list-group.less │ │ ├── media.less │ │ ├── mixins.less │ │ ├── mixins │ │ ├── alerts.less │ │ ├── background-variant.less │ │ ├── border-radius.less │ │ ├── buttons.less │ │ ├── center-block.less │ │ ├── clearfix.less │ │ ├── forms.less │ │ ├── gradients.less │ │ ├── grid-framework.less │ │ ├── grid.less │ │ ├── hide-text.less │ │ ├── image.less │ │ ├── labels.less │ │ ├── list-group.less │ │ ├── nav-divider.less │ │ ├── nav-vertical-align.less │ │ ├── opacity.less │ │ ├── pagination.less │ │ ├── panels.less │ │ ├── progress-bar.less │ │ ├── reset-filter.less │ │ ├── resize.less │ │ ├── responsive-visibility.less │ │ ├── size.less │ │ ├── tab-focus.less │ │ ├── table-row.less │ │ ├── text-emphasis.less │ │ ├── text-overflow.less │ │ └── vendor-prefixes.less │ │ ├── modals.less │ │ ├── navbar.less │ │ ├── navs.less │ │ ├── normalize.less │ │ ├── pager.less │ │ ├── pagination.less │ │ ├── panels.less │ │ ├── popovers.less │ │ ├── print.less │ │ ├── progress-bars.less │ │ ├── responsive-embed.less │ │ ├── responsive-utilities.less │ │ ├── scaffolding.less │ │ ├── tables.less │ │ ├── theme.less │ │ ├── thumbnails.less │ │ ├── tooltip.less │ │ ├── type.less │ │ ├── utilities.less │ │ ├── variables.less │ │ └── wells.less ├── lang │ └── en │ │ ├── pagination.php │ │ ├── passwords.php │ │ └── validation.php └── views │ ├── _navigation.blade.php │ ├── _pin.blade.php │ ├── _pin_form.blade.php │ ├── _profile.blade.php │ ├── activity │ ├── follow.blade.php │ └── pin.blade.php │ ├── aggregated_activity │ ├── follow.blade.php │ └── pin.blade.php │ ├── aggregated_feed.blade.php │ ├── auth │ ├── login.blade.php │ ├── password.blade.php │ ├── register.blade.php │ └── reset.blade.php │ ├── emails │ ├── auth │ │ └── reminder.blade.php │ └── password.blade.php │ ├── errors │ └── 503.blade.php │ ├── flat_feed.blade.php │ ├── home.blade.php │ ├── layouts │ └── master.blade.php │ ├── people.blade.php │ ├── profile.blade.php │ ├── trending.blade.php │ ├── vendor │ └── .gitkeep │ └── welcome.blade.php ├── server.php ├── storage ├── .gitignore ├── framework │ ├── cache │ │ └── .gitignore │ ├── services.json │ ├── sessions │ │ └── .gitignore │ └── views │ │ └── .gitignore └── logs │ └── .gitignore └── tests ├── ExampleTest.php └── TestCase.php /.env.example: -------------------------------------------------------------------------------- 1 | APP_ENV=local 2 | APP_DEBUG=true 3 | APP_KEY=SomeRandomString 4 | 5 | DB_HOST=localhost 6 | DB_DATABASE=homestead 7 | DB_USERNAME=homestead 8 | DB_PASSWORD=secret 9 | 10 | CACHE_DRIVER=file 11 | SESSION_DRIVER=file 12 | QUEUE_DRIVER=sync 13 | 14 | MAIL_DRIVER=smtp 15 | MAIL_HOST=mailtrap.io 16 | MAIL_PORT=2525 17 | MAIL_USERNAME=null 18 | MAIL_PASSWORD=null 19 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.css linguist-vendored 3 | *.less linguist-vendored 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor 2 | /node_modules 3 | .env 4 | /bootstrap/compiled.php 5 | composer.phar 6 | .env.*.php 7 | .env.php 8 | .DS_Store 9 | Thumbs.db 10 | .vagrant* -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2017, Stream.io Inc, and individual contributors. 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, are permitted 6 | provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this list of 9 | conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of 12 | conditions and the following disclaimer in the documentation and/or other materials 13 | provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its contributors may 16 | be used to endorse or promote products derived from this software without specific prior 17 | written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 20 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY 21 | AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR 22 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 23 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 26 | OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 27 | POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: vendor/bin/heroku-php-apache2 public -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Stream Example App 2 | ------------------ 3 | 4 | This example app shows you how you can use [GetStream.io](https://getstream.io/ "GetStream.io") to build a site similar to Pinterest. 5 | 6 | The application is built using Laravel 5 and Stream-Laravel. 7 | 8 | The project is based on the [stream-laravel](https://github.com/GetStream/stream-laravel) integration for [Stream](https://getstream.io/). There is also a lower level [PHP - Stream integration](https://github.com/getstream/stream-php) library which is suitable for all PHP applications. 9 | 10 | You can sign up for a Stream account at https://getstream.io/get_started. 11 | 12 | If you're looking to self-host your feed solution we suggest the open source [Stream-Framework](https://github.com/tschellenbach/Stream-Framework), created by the Stream founders. 13 | 14 | ### Live demo 15 | 16 | _Coming soon._ 17 | 18 | ## Deploying the app 19 | 20 | ### Heroku 21 | 22 | The best way to understand and try out this application is via Heroku. You can deploy the app, for free, simply by clicking the following button: 23 | 24 | [![Deploy](https://www.herokucdn.com/deploy/button.png)](https://heroku.com/deploy) 25 | 26 | ### Local 27 | 28 | If you prefer to run this locally then make sure to generate the API keys on [GetStream.io](https://getstream.io/ "GetStream.io") and update the settings in config/database.php and in config/packages/get-stream/stream-laravel/config.php. 29 | 30 | Once you have the right settings you can get your database ready by running the provision command (```./provision.sh```) 31 | 32 | ## Tutorial 33 | 34 | _Coming soon._ 35 | 36 | ### Copyright and License Information 37 | 38 | Copyright (c) 2015-2017 Stream.io Inc, and individual contributors. All rights reserved. 39 | 40 | See the file "LICENSE" for information on the history of this software, terms & conditions for usage, and a DISCLAIMER OF ALL WARRANTIES. 41 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Stream Example App", 3 | "description": "Pinterest esque example app using GetStream.io", 4 | "keywords": [ 5 | "getstream.io", 6 | "HTML5", 7 | "PHP" 8 | ], 9 | "website": "https://getstream.io/", 10 | "repository": "https://github.com/GetStream/stream-example-php", 11 | "logo": "http://33.media.tumblr.com/avatar_be8b5eef9ae6_128.png", 12 | "success_url": "/", 13 | "addons": [ 14 | "stream", "heroku-postgresql" 15 | ], 16 | "scripts": { 17 | "postdeploy": "./provision.sh" 18 | } 19 | } -------------------------------------------------------------------------------- /app/Commands/Command.php: -------------------------------------------------------------------------------- 1 | firstOrFail(); 46 | $follow = Follow::firstOrNew(array( 47 | 'user_id' => $admin->id, 48 | 'target_id' => $admin->id, 49 | ) 50 | ); 51 | if ($follow->id === null) { 52 | $follow->save(); 53 | FeedManager::followUser($follow->user_id, $follow->target_id); 54 | } 55 | return Redirect::to(Input::get('next')); 56 | } 57 | 58 | /** 59 | * Get the console command arguments. 60 | * 61 | * @return array 62 | */ 63 | protected function getArguments() 64 | { 65 | return array(); 66 | } 67 | 68 | /** 69 | * Get the console command options. 70 | * 71 | * @return array 72 | */ 73 | protected function getOptions() 74 | { 75 | return array(); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /app/Console/Commands/Inspire.php: -------------------------------------------------------------------------------- 1 | comment(PHP_EOL.Inspiring::quote().PHP_EOL); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/Console/Kernel.php: -------------------------------------------------------------------------------- 1 | command('inspire') 27 | ->hourly(); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/Events/Event.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 33 | $this->registrar = $registrar; 34 | 35 | $this->middleware('guest', ['except' => 'getLogout']); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/Http/Controllers/Auth/PasswordController.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 33 | $this->passwords = $passwords; 34 | 35 | $this->middleware('guest'); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/Http/Controllers/BaseController.php: -------------------------------------------------------------------------------- 1 | layout)) 15 | { 16 | $this->layout = View::make($this->layout); 17 | } 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/Http/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | getActivities(0,25)['results']; 15 | $activities = $enricher->enrichActivities($activities); 16 | return View::make('flat_feed', array('activities'=> $activities)); 17 | } 18 | 19 | /** 20 | * Show the aggregated activities from followed users. 21 | */ 22 | public function aggregated_feed() 23 | { 24 | $feed = FeedManager::getNewsFeeds(Auth::id())['timeline_aggregated']; 25 | $enricher = new Enrich; 26 | $activities = $feed->getActivities(0,25)['results']; 27 | $activities = $enricher->enrichAggregatedActivities($activities); 28 | return View::make('aggregated_feed', array('activities'=> $activities)); 29 | } 30 | 31 | } 32 | 33 | ?> -------------------------------------------------------------------------------- /app/Http/Controllers/FollowController.php: -------------------------------------------------------------------------------- 1 | Auth::id(), 16 | 'target_id' => $target_id, 17 | ); 18 | $follow = Follow::withTrashed($params)->where($params)->first(); 19 | 20 | if ($follow === null) { 21 | $follow = new Follow($params); 22 | $follow->save(); 23 | FeedManager::followUser($follow->user_id, $follow->target_id); 24 | } elseif ($follow->trashed()){ 25 | $follow->restore(); 26 | FeedManager::followUser($follow->user_id, $follow->target_id); 27 | } 28 | return Redirect::to(Input::get('next')); 29 | } 30 | 31 | public function destroy($resource) 32 | { 33 | $follow = Follow::firstOrNew(array( 34 | 'id' => $resource, 35 | 'user_id' => Auth::id() 36 | ) 37 | ); 38 | if ($follow->id !== null) { 39 | $manager = App::make('feed_manager'); 40 | FeedManager::unfollowUser($follow->user_id, $follow->target_id); 41 | $follow->delete(); 42 | } 43 | return Redirect::to(Input::get('next')); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /app/Http/Controllers/HomeController.php: -------------------------------------------------------------------------------- 1 | middleware('auth'); 24 | } 25 | 26 | /** 27 | * Show the application dashboard to the user. 28 | * 29 | * @return Response 30 | */ 31 | public function index() 32 | { 33 | 34 | 35 | 36 | return view('home'); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/Http/Controllers/LandingController.php: -------------------------------------------------------------------------------- 1 | function($query) 11 | { 12 | $query->where('user_id', '=', Auth::id()); 13 | }, 'user'); 14 | $items = Item::with($pinned)->get()->take(25); 15 | return View::make('trending', array('items'=> $items)); 16 | } 17 | 18 | } 19 | 20 | ?> -------------------------------------------------------------------------------- /app/Http/Controllers/PinController.php: -------------------------------------------------------------------------------- 1 | Auth::id(), 14 | 'item_id' => Input::get('item'), 15 | 'influencer_id' => Input::get('influencer'), 16 | ); 17 | $pin = Pin::withTrashed()->where($params)->first(); 18 | if ($pin === null) { 19 | $pin = new Pin($params); 20 | $pin->save(); 21 | } 22 | elseif ($pin->trashed()) { 23 | $pin->restore(); 24 | } 25 | $next = Input::get('next'); 26 | return Redirect::to($next); 27 | } 28 | 29 | public function destroy($resource) 30 | { 31 | $pin = Pin::firstOrNew(array( 32 | 'id' => $resource, 33 | 'user_id' => Auth::id() 34 | ) 35 | ); 36 | if ($pin->id !== null) { 37 | $manager = App::make('feed_manager'); 38 | $pin->delete(); 39 | } 40 | return Redirect::to(Input::get('next')); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/Http/Controllers/ProfileController.php: -------------------------------------------------------------------------------- 1 | firstOrFail(); 13 | $feed = FeedManager::getUserFeed($user->id); 14 | $enricher = new Enrich; 15 | $activities = $feed->getActivities(0,25)['results']; 16 | $activities = $enricher->enrichActivities($activities); 17 | $follow = Follow::firstOrNew(array( 18 | 'user_id' => Auth::id(), 19 | 'target_id' => $user->id, 20 | ) 21 | ); 22 | return View::make('profile', array('profile' => $user, 'activities' => $activities, 'follow' => $follow)); 23 | } 24 | 25 | public function index() 26 | { 27 | $with = array('followers' => function($query) { 28 | $query->where('user_id', '=', Auth::id()); 29 | } 30 | ); 31 | $people = User::with($with)->get()->take(25); 32 | return View::make('people', array('people' => $people)); 33 | } 34 | } 35 | 36 | ?> 37 | -------------------------------------------------------------------------------- /app/Http/Controllers/WelcomeController.php: -------------------------------------------------------------------------------- 1 | middleware('guest'); 26 | } 27 | 28 | /** 29 | * Show the application welcome screen to the user. 30 | * 31 | * @return Response 32 | */ 33 | public function index() 34 | { 35 | return view('welcome'); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/Http/Kernel.php: -------------------------------------------------------------------------------- 1 | 'App\Http\Middleware\Authenticate', 28 | 'auth.basic' => 'Illuminate\Auth\Middleware\AuthenticateWithBasicAuth', 29 | 'guest' => 'App\Http\Middleware\RedirectIfAuthenticated', 30 | ]; 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 24 | } 25 | 26 | /** 27 | * Handle an incoming request. 28 | * 29 | * @param \Illuminate\Http\Request $request 30 | * @param \Closure $next 31 | * @return mixed 32 | */ 33 | public function handle($request, Closure $next) 34 | { 35 | if ($this->auth->guest()) 36 | { 37 | if ($request->ajax()) 38 | { 39 | return response('Unauthorized.', 401); 40 | } 41 | else 42 | { 43 | return redirect()->guest('auth/login'); 44 | } 45 | } 46 | 47 | return $next($request); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/Http/Middleware/RedirectIfAuthenticated.php: -------------------------------------------------------------------------------- 1 | auth = $auth; 25 | } 26 | 27 | /** 28 | * Handle an incoming request. 29 | * 30 | * @param \Illuminate\Http\Request $request 31 | * @param \Closure $next 32 | * @return mixed 33 | */ 34 | public function handle($request, Closure $next) 35 | { 36 | if ($this->auth->check()) 37 | { 38 | return new RedirectResponse(url('/home')); 39 | } 40 | 41 | return $next($request); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /app/Http/Middleware/VerifyCsrfToken.php: -------------------------------------------------------------------------------- 1 | 'autologin', 'as' => 'trending', 'uses' => 'LandingController@trending')); 16 | Route::get('/feed', array('before' => 'autologin', 'as' => 'feed', 'uses' => 'FeedController@feed')); 17 | Route::get('/aggregated', array('before' => 'autologin', 'as' => 'aggregated_feed', 'uses' => 'FeedController@aggregated_feed')); 18 | Route::get('/people', array('before' => 'autologin', 'as' => 'people', 'uses' => 'ProfileController@index')); 19 | Route::get('/profile/{username}', array('before' => 'autologin', 'as' => 'profile', 'uses' => 'ProfileController@profile')); 20 | Route::resource('pin', 'PinController', array('before' => 'autologin')); 21 | Route::resource('follow', 'FollowController', array('before' => 'autologin')); 22 | -------------------------------------------------------------------------------- /app/Models/Follow.php: -------------------------------------------------------------------------------- 1 | belongsTo('User'); 16 | } 17 | 18 | public function target() 19 | { 20 | return $this->belongsTo('User'); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/Models/Item.php: -------------------------------------------------------------------------------- 1 | belongsTo('User'); 14 | } 15 | 16 | public function pins() 17 | { 18 | return $this->hasMany('Pin'); 19 | } 20 | 21 | public function pinsFromMe() 22 | { 23 | return $this->hasMany('Pin')->where('user_id', '=', Auth::id()); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/Models/Pin.php: -------------------------------------------------------------------------------- 1 | belongsTo('Item'); 16 | } 17 | 18 | public function user() 19 | { 20 | return $this->belongsTo('User'); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/Models/User.php: -------------------------------------------------------------------------------- 1 | hasMany('Follow', 'target_id'); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/Providers/AppServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind( 29 | 'Illuminate\Contracts\Auth\Registrar', 30 | 'App\Services\Registrar' 31 | ); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/Providers/BusServiceProvider.php: -------------------------------------------------------------------------------- 1 | mapUsing(function($command) 17 | { 18 | return Dispatcher::simpleMapping( 19 | $command, 'App\Commands', 'App\Handlers\Commands' 20 | ); 21 | }); 22 | } 23 | 24 | /** 25 | * Register any application services. 26 | * 27 | * @return void 28 | */ 29 | public function register() 30 | { 31 | // 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/Providers/ConfigServiceProvider.php: -------------------------------------------------------------------------------- 1 | [ 15 | 'EventListener', 16 | ], 17 | ]; 18 | 19 | /** 20 | * Register any other events for your application. 21 | * 22 | * @param \Illuminate\Contracts\Events\Dispatcher $events 23 | * @return void 24 | */ 25 | public function boot(DispatcherContract $events) 26 | { 27 | parent::boot($events); 28 | 29 | // 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/Providers/RouteServiceProvider.php: -------------------------------------------------------------------------------- 1 | firstOrFail(); 35 | Auth::login($user); 36 | } 37 | }); 38 | // 39 | } 40 | 41 | /** 42 | * Define the routes for the application. 43 | * 44 | * @param \Illuminate\Routing\Router $router 45 | * @return void 46 | */ 47 | public function map(Router $router) 48 | { 49 | $router->group(['namespace' => $this->namespace], function($router) 50 | { 51 | require app_path('Http/routes.php'); 52 | }); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/Services/Registrar.php: -------------------------------------------------------------------------------- 1 | 'required|max:255', 19 | 'email' => 'required|email|max:255|unique:users', 20 | 'password' => 'required|confirmed|min:6', 21 | ]); 22 | } 23 | 24 | /** 25 | * Create a new user instance after a valid registration. 26 | * 27 | * @param array $data 28 | * @return User 29 | */ 30 | public function create(array $data) 31 | { 32 | return User::create([ 33 | 'name' => $data['name'], 34 | 'email' => $data['email'], 35 | 'password' => bcrypt($data['password']), 36 | ]); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/User.php: -------------------------------------------------------------------------------- 1 | make('Illuminate\Contracts\Console\Kernel'); 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', 31 | 'App\Http\Kernel' 32 | ); 33 | 34 | $app->singleton( 35 | 'Illuminate\Contracts\Console\Kernel', 36 | 'App\Console\Kernel' 37 | ); 38 | 39 | $app->singleton( 40 | 'Illuminate\Contracts\Debug\ExceptionHandler', 41 | 'App\Exceptions\Handler' 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 | 'eloquent', 19 | 20 | /* 21 | |-------------------------------------------------------------------------- 22 | | Authentication Model 23 | |-------------------------------------------------------------------------- 24 | | 25 | | When using the "Eloquent" authentication driver, we need to know which 26 | | Eloquent model should be used to retrieve your users. Of course, it 27 | | is often just the "User" model but you may use whatever you like. 28 | | 29 | */ 30 | 31 | 'model' => 'App\User', 32 | 33 | /* 34 | |-------------------------------------------------------------------------- 35 | | Authentication Table 36 | |-------------------------------------------------------------------------- 37 | | 38 | | When using the "Database" authentication driver, we need to know which 39 | | table should be used to retrieve your users. We have chosen a basic 40 | | default value but you may easily change it to any table you like. 41 | | 42 | */ 43 | 44 | 'table' => 'users', 45 | 46 | /* 47 | |-------------------------------------------------------------------------- 48 | | Password Reset Settings 49 | |-------------------------------------------------------------------------- 50 | | 51 | | Here you may set the options for resetting passwords including the view 52 | | that is your password reset e-mail. You can also set the name of the 53 | | table that maintains all of the reset tokens for your application. 54 | | 55 | | The expire time is the number of minutes that the reset token should be 56 | | considered valid. This security feature keeps tokens short-lived so 57 | | they have less time to be guessed. You may change this as needed. 58 | | 59 | */ 60 | 61 | 'password' => [ 62 | 'email' => 'emails.password', 63 | 'table' => 'password_resets', 64 | 'expire' => 60, 65 | ], 66 | 67 | ]; 68 | -------------------------------------------------------------------------------- /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' => '127.0.0.1', 'port' => 11211, 'weight' => 100 55 | ], 56 | ], 57 | ], 58 | 59 | 'redis' => [ 60 | 'driver' => 'redis', 61 | 'connection' => 'default', 62 | ], 63 | 64 | ], 65 | 66 | /* 67 | |-------------------------------------------------------------------------- 68 | | Cache Key Prefix 69 | |-------------------------------------------------------------------------- 70 | | 71 | | When utilizing a RAM based store such as APC or Memcached, there might 72 | | be other applications utilizing the same cache. So, we'll specify a 73 | | value to get prefixed to all our keys so we can avoid collisions. 74 | | 75 | */ 76 | 77 | 'prefix' => 'laravel', 78 | 79 | ]; 80 | -------------------------------------------------------------------------------- /config/compile.php: -------------------------------------------------------------------------------- 1 | [ 17 | 18 | realpath(__DIR__.'/../app/Providers/AppServiceProvider.php'), 19 | realpath(__DIR__.'/../app/Providers/BusServiceProvider.php'), 20 | realpath(__DIR__.'/../app/Providers/ConfigServiceProvider.php'), 21 | realpath(__DIR__.'/../app/Providers/EventServiceProvider.php'), 22 | realpath(__DIR__.'/../app/Providers/RouteServiceProvider.php'), 23 | 24 | ], 25 | 26 | /* 27 | |-------------------------------------------------------------------------- 28 | | Compiled File Providers 29 | |-------------------------------------------------------------------------- 30 | | 31 | | Here you may list service providers which define a "compiles" function 32 | | that returns additional files that should be compiled, providing an 33 | | easy way to get common files from any packages you are utilizing. 34 | | 35 | */ 36 | 37 | 'providers' => [ 38 | // 39 | ], 40 | 41 | ]; 42 | -------------------------------------------------------------------------------- /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 | 's3' => [ 52 | 'driver' => 's3', 53 | 'key' => 'your-key', 54 | 'secret' => 'your-secret', 55 | 'region' => 'your-region', 56 | 'bucket' => 'your-bucket', 57 | ], 58 | 59 | 'rackspace' => [ 60 | 'driver' => 'rackspace', 61 | 'username' => 'your-username', 62 | 'key' => 'your-key', 63 | 'container' => 'your-container', 64 | 'endpoint' => 'https://identity.api.rackspacecloud.com/v2.0/', 65 | 'region' => 'IAD', 66 | 'url_type' => 'publicURL' 67 | ], 68 | 69 | ], 70 | 71 | ]; 72 | -------------------------------------------------------------------------------- /config/queue.php: -------------------------------------------------------------------------------- 1 | env('QUEUE_DRIVER', 'sync'), 20 | 21 | /* 22 | |-------------------------------------------------------------------------- 23 | | Queue Connections 24 | |-------------------------------------------------------------------------- 25 | | 26 | | Here you may configure the connection information for each server that 27 | | is used by your application. A default configuration has been added 28 | | for each back-end shipped with Laravel. You are free to add more. 29 | | 30 | */ 31 | 32 | 'connections' => [ 33 | 34 | 'sync' => [ 35 | 'driver' => 'sync', 36 | ], 37 | 38 | 'database' => [ 39 | 'driver' => 'database', 40 | 'table' => 'jobs', 41 | 'queue' => 'default', 42 | 'expire' => 60, 43 | ], 44 | 45 | 'beanstalkd' => [ 46 | 'driver' => 'beanstalkd', 47 | 'host' => 'localhost', 48 | 'queue' => 'default', 49 | 'ttr' => 60, 50 | ], 51 | 52 | 'sqs' => [ 53 | 'driver' => 'sqs', 54 | 'key' => 'your-public-key', 55 | 'secret' => 'your-secret-key', 56 | 'queue' => 'your-queue-url', 57 | 'region' => 'us-east-1', 58 | ], 59 | 60 | 'iron' => [ 61 | 'driver' => 'iron', 62 | 'host' => 'mq-aws-us-east-1.iron.io', 63 | 'token' => 'your-token', 64 | 'project' => 'your-project-id', 65 | 'queue' => 'your-queue-name', 66 | 'encrypt' => true, 67 | ], 68 | 69 | 'redis' => [ 70 | 'driver' => 'redis', 71 | 'queue' => 'default', 72 | 'expire' => 60, 73 | ], 74 | 75 | ], 76 | 77 | /* 78 | |-------------------------------------------------------------------------- 79 | | Failed Queue Jobs 80 | |-------------------------------------------------------------------------- 81 | | 82 | | These options configure the behavior of failed queue job logging so you 83 | | can control which database and table are used to store the jobs that 84 | | have failed. You may change them to any database / table you wish. 85 | | 86 | */ 87 | 88 | 'failed' => [ 89 | 'database' => 'mysql', 'table' => 'failed_jobs', 90 | ], 91 | 92 | ]; 93 | -------------------------------------------------------------------------------- /config/services.php: -------------------------------------------------------------------------------- 1 | [ 18 | 'domain' => '', 19 | 'secret' => '', 20 | ], 21 | 22 | 'mandrill' => [ 23 | 'secret' => '', 24 | ], 25 | 26 | 'ses' => [ 27 | 'key' => '', 28 | 'secret' => '', 29 | 'region' => 'us-east-1', 30 | ], 31 | 32 | 'stripe' => [ 33 | 'model' => 'User', 34 | 'secret' => '', 35 | ], 36 | 37 | ]; 38 | -------------------------------------------------------------------------------- /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/migrations/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-PHP/38221b868b71c57e71cef3a84152240b6ec5a2cf/database/migrations/.gitkeep -------------------------------------------------------------------------------- /database/migrations/2014_10_01_122413_create_users_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->string('username'); 19 | $table->string('email'); 20 | $table->string('password'); 21 | $table->string('first_name')->nullable(); 22 | $table->string('last_name')->nullable(); 23 | $table->string('remember_token', 64)->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('users'); 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /database/migrations/2014_10_01_134126_create_items_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('user_id')->unsigned(); 19 | $table->foreign('user_id') 20 | ->references('id')->on('users') 21 | ->onDelete('cascade'); 22 | $table->string('image'); 23 | $table->string('message')->nullable(); 24 | $table->string('pin_count'); 25 | $table->timestamps(); 26 | }); 27 | } 28 | 29 | /** 30 | * Reverse the migrations. 31 | * 32 | * @return void 33 | */ 34 | public function down() 35 | { 36 | Schema::drop('items'); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/2014_10_01_161014_create_pins_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('user_id')->unsigned(); 19 | $table->foreign('user_id') 20 | ->references('id')->on('users') 21 | ->onDelete('cascade'); 22 | $table->integer('item_id')->unsigned(); 23 | $table->foreign('item_id') 24 | ->references('id')->on('items') 25 | ->onDelete('cascade'); 26 | $table->integer('influencer_id')->unsigned(); 27 | $table->foreign('influencer_id') 28 | ->references('id')->on('users') 29 | ->onDelete('cascade'); 30 | $table->string('message')->nullable(); 31 | $table->timestamps(); 32 | $table->softDeletes(); 33 | $table->unique(array('user_id', 'item_id')); 34 | }); 35 | 36 | } 37 | 38 | /** 39 | * Reverse the migrations. 40 | * 41 | * @return void 42 | */ 43 | public function down() 44 | { 45 | Schema::drop('pins'); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /database/migrations/2014_10_02_100110_create_follows_table.php: -------------------------------------------------------------------------------- 1 | increments('id'); 18 | $table->integer('user_id')->unsigned(); 19 | $table->foreign('user_id') 20 | ->references('id')->on('users') 21 | ->onDelete('cascade'); 22 | $table->integer('target_id')->unsigned(); 23 | $table->foreign('target_id') 24 | ->references('id')->on('users') 25 | ->onDelete('cascade'); 26 | $table->timestamps(); 27 | $table->unique(array('user_id', 'target_id')); 28 | }); 29 | } 30 | 31 | /** 32 | * Reverse the migrations. 33 | * 34 | * @return void 35 | */ 36 | public function down() 37 | { 38 | Schema::drop('follows'); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /database/migrations/2014_10_12_100000_create_password_resets_table.php: -------------------------------------------------------------------------------- 1 | string('email')->index(); 18 | $table->string('token')->index(); 19 | $table->timestamp('created_at'); 20 | }); 21 | } 22 | 23 | /** 24 | * Reverse the migrations. 25 | * 26 | * @return void 27 | */ 28 | public function down() 29 | { 30 | Schema::drop('password_resets'); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /database/migrations/2014_10_15_121152_add_deleted_at_to_follow_table.php: -------------------------------------------------------------------------------- 1 | softDeletes(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('follows', function(Blueprint $table) 29 | { 30 | // 31 | }); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /database/migrations/2014_10_15_121340_add_deleted_at_to_item_table.php: -------------------------------------------------------------------------------- 1 | softDeletes(); 18 | }); 19 | } 20 | 21 | /** 22 | * Reverse the migrations. 23 | * 24 | * @return void 25 | */ 26 | public function down() 27 | { 28 | Schema::table('items', function(Blueprint $table) 29 | { 30 | // 31 | }); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /database/seeds/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GetStream/Stream-Example-PHP/38221b868b71c57e71cef3a84152240b6ec5a2cf/database/seeds/.gitkeep -------------------------------------------------------------------------------- /database/seeds/DatabaseSeeder.php: -------------------------------------------------------------------------------- 1 | call('UserTableSeeder'); 17 | $this->call('ItemTableSeeder'); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /database/seeds/ItemTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 11 | $items = json_decode(file_get_contents(__DIR__."/items.json"), true); 12 | $user = User::where('username', '=', 'admin')->firstOrFail(); 13 | foreach ($items as $i => $item) { 14 | $items[$i]['user_id'] = $user->id; 15 | $items[$i]['created_at'] = new DateTime(); 16 | $items[$i]['updated_at'] = new DateTime(); 17 | } 18 | DB::table('items')->insert($items); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /database/seeds/UserTableSeeder.php: -------------------------------------------------------------------------------- 1 | delete(); 11 | DB::table('users')->insert([ 12 | 'username' => 'admin', 13 | 'email' => 'lightwalker@rebels.com', 14 | 'password' => Hash::make('admin'), 15 | 'first_name' => 'admin', 16 | 'last_name' => 'admin', 17 | 'created_at' => new DateTime(), 18 | 'updated_at' => new DateTime() 19 | ]); 20 | 21 | DB::table('users')->insert([ 22 | 'username' => 'thierry', 23 | 'email' => 'thierry@rebels.com', 24 | 'password' => Hash::make('thierry'), 25 | 'first_name' => 'thierry', 26 | 'last_name' => 'schellenbach', 27 | 'created_at' => new DateTime(), 28 | 'updated_at' => new DateTime() 29 | ]); 30 | 31 | DB::table('users')->insert([ 32 | 'username' => 'tommaso', 33 | 'email' => 'tommaso@empire.com', 34 | 'password' => Hash::make('tommaso'), 35 | 'first_name' => 'tommaso', 36 | 'last_name' => 'barbugli', 37 | 'created_at' => new DateTime(), 38 | 'updated_at' => new DateTime() 39 | ]); 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /database/seeds/items.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "pin_count": 0, 4 | "image": "items/011.jpg", 5 | "message": "beautiful flowers in the window" 6 | }, 7 | { 8 | "pin_count": 0, 9 | "image": "items/261379381_d8fde25053fa.jpg", 10 | "message": "" 11 | }, 12 | { 13 | "pin_count": 0, 14 | "image": "items/293704645_b4c6dab8ed99.jpg", 15 | "message": "" 16 | }, 17 | { 18 | "pin_count": 0, 19 | "image": "items/144568575661705981_4ade117f9005.jpeg", 20 | "message": "" 21 | }, 22 | { 23 | "pin_count": 0, 24 | "image": "items/164471358120532732_a3ae9498ad97.jpg", 25 | "message": "" 26 | }, 27 | { 28 | "pin_count": 0, 29 | "image": "items/184290639033145973_71e492dacec6.jpeg", 30 | "message": "" 31 | }, 32 | { 33 | "pin_count": 0, 34 | "image": "items/272236897059737539_c4806e0b14c4.jpg", 35 | "message": "But I must explain to you how all this mistaken idea" 36 | }, 37 | { 38 | "pin_count": 0, 39 | "image": "items/275088534757448505_4f85768f71ec.jpg", 40 | "message": "\"Lorem ipsum dolor sit amet" 41 | }, 42 | { 43 | "pin_count": 0, 44 | "image": "items/281485446934954481_c04cfc76c018.png", 45 | "message": "It is a long established fact that a reader will be distracted" 46 | }, 47 | { 48 | "pin_count": 0, 49 | "image": "items/389230669370236857_335b7c698687.jpg", 50 | "message": "Lorem Ipsum is simply dummy text" 51 | }, 52 | { 53 | "pin_count": 0, 54 | "image": "items/1addc6292e97f5f796cf368df5d4b322.jpg", 55 | "message": "squirel power!" 56 | }, 57 | { 58 | "pin_count": 0, 59 | "image": "items/6fb3f77d22ddc23305fcd1a2fd8c4e80_1.jpg", 60 | "message": "designed amazingness" 61 | }, 62 | { 63 | "pin_count": 0, 64 | "image": "items/73f98aae32e30a729bdf1031a78db65a.jpg", 65 | "message": "Outdoor" 66 | }, 67 | { 68 | "pin_count": 0, 69 | "image": "items/70602b9d4f965eee1d0345d99622798f.jpg", 70 | "message": "paris" 71 | }, 72 | { 73 | "pin_count": 0, 74 | "image": "items/167111313523218252_a6046175bb32.jpeg", 75 | "message": "relax" 76 | }, 77 | { 78 | "pin_count": 0, 79 | "image": "items/e315a43813c5533b9f47754406019e24.jpg", 80 | "message": "cuddle" 81 | }, 82 | { 83 | "pin_count": 0, 84 | "image": "items/e2110e4545f1cc11043659fdd9c07b43.jpg", 85 | "message": "" 86 | } 87 | ] 88 | -------------------------------------------------------------------------------- /install: -------------------------------------------------------------------------------- 1 | 1. if you don't have it yet, install composer 2 | 2. install dependencies with composer ```php composer install ``` 3 | 3. create an account on https://getstream.io 4 | 4. generate the config file php artisan vendor:publish --provider="GetStream\StreamLaravel\StreamLaravelServiceProvider" 5 | 5. get your API credentials (key and secret) from https://getstream.io 6 | 6. put your credentials in the config file created by artisan (stream-laravel) 7 | 7. adjust your database settings in config/database.php 8 | 8. run database setup ``` ./provision.sh ``` 9 | 10 | The example app is based on stream-laravel package, please refer to its documentation: https://github.com/GetStream/Stream-Laravel 11 | 12 | If you have any problems please open a issue on github and paste any error you get in the console. 13 | -------------------------------------------------------------------------------- /phpspec.yml: -------------------------------------------------------------------------------- 1 | suites: 2 | main: 3 | namespace: App 4 | psr4_prefix: App 5 | src_path: app -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./tests/ 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /provision.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | php artisan migrate --force --seed 3 | php artisan followAdmin 4 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | 3 | Options -MultiViews 4 | 5 | 6 | RewriteEngine On 7 | 8 | # Redirect Trailing Slashes... 9 | RewriteRule ^(.*)/$ /$1 [L,R=301] 10 | 11 | # Handle Front Controller... 12 | RewriteCond %{REQUEST_FILENAME} !-d 13 | RewriteCond %{REQUEST_FILENAME} !-f 14 | RewriteRule ^ index.php [L] 15 | 16 | -------------------------------------------------------------------------------- /public/css/_feedly.scss: -------------------------------------------------------------------------------- 1 | // MEDIA QUERIES 2 | // -------------------------------------------------- 3 | @mixin respond-to($size, $operator: 'min', $unit: 'px', $query: 'only screen', $unitToRender:'') { 4 | @if unitless($size) { 5 | $unitToRender: 'px' 6 | } @else { 7 | $unitToRender: '' 8 | } 9 | @media #{$query} and (#{$operator}-width: #{$size}#{$unitToRender}) { 10 | @content; 11 | } 12 | } 13 | 14 | @mixin column-gap($size) { 15 | -webkit-column-gap: $size#{'px'}; 16 | -moz-column-gap: $size#{'px'}; 17 | column-gap: $size#{'px'}; 18 | } 19 | 20 | @mixin column-count($quantity) { 21 | -webkit-column-count: $quantity; 22 | -moz-column-count: $quantity; 23 | column-count: $quantity; 24 | } 25 | 26 | body { 27 | min-height: 2000px; 28 | padding-top: 50px; 29 | } 30 | 31 | #wrapper { 32 | max-width: 1100px; 33 | margin: 50px auto; 34 | padding: 0 15px; 35 | } 36 | 37 | .container-pins { 38 | 39 | @include respond-to(480) { 40 | @include column-gap(15); 41 | @include column-count(2); 42 | } 43 | @include respond-to(768) { 44 | @include column-count(3); 45 | } 46 | @include respond-to(1024) { 47 | &:not(.profile) { 48 | @include column-count(4); 49 | } 50 | } 51 | @include respond-to(1100) { 52 | &:not(.profile) { 53 | @include column-count(5); 54 | } 55 | } 56 | } 57 | 58 | .aggregation { 59 | background: #fff; 60 | padding: $grid-gutter-width; 61 | margin-bottom: $grid-gutter-width; 62 | box-shadow: 0 1px 7px 2px rgba(0,0,0,.1); 63 | 64 | & .pin { 65 | max-width: 270px; 66 | margin-right: $grid-gutter-width / 2; 67 | box-shadow: none; 68 | border: 1px solid #eee; 69 | } 70 | & .pin-image { 71 | max-width: 100%; 72 | height: auto; 73 | width: auto; 74 | } 75 | 76 | & .pin-image-holder { 77 | width: auto; 78 | max-height: 270px; 79 | } 80 | } 81 | 82 | .aggregation-header { 83 | height: 45px; 84 | line-height: 45px; 85 | margin-bottom: $grid-gutter-width / 2; 86 | text-transform: uppercase; 87 | } 88 | 89 | .aggregation-time { 90 | text-transform: none; 91 | font-size: 10px; 92 | color: #999; 93 | } 94 | 95 | .pin { 96 | display: inline-block; 97 | background: #fff; 98 | box-shadow: 0 1px 7px 2px rgba(0,0,0,.1); 99 | margin: 0 0 $grid-gutter-width / 2; 100 | -webkit-column-break-inside: avoid; 101 | -moz-column-break-inside: avoid; 102 | column-break-inside: avoid; 103 | @include transition(all .2s ease); 104 | 105 | & textarea { 106 | resize: vertical; 107 | } 108 | } 109 | 110 | .pin-image { 111 | width: 100%; 112 | } 113 | 114 | .pin-image-holder { 115 | overflow: hidden; 116 | width: 100%; 117 | box-shadow: inset 0 1px 2px rgba(0,0,0,.2); 118 | border-bottom: 1px solid rgba(0,0,0,.1); 119 | } 120 | 121 | .pin-caption { 122 | padding: $grid-gutter-width / 2 $grid-gutter-width / 2 0 $grid-gutter-width / 2; 123 | color: $gray; 124 | font-size: 12px; 125 | line-height: 17px; 126 | word-wrap: break-word; 127 | } 128 | 129 | .pin-bottom { 130 | padding: $grid-gutter-width / 2; 131 | } 132 | 133 | .pin-attribution { 134 | white-space: nowrap; 135 | overflow: hidden; 136 | -ms-text-overflow: ellipsis; 137 | -o-text-overflow: ellipsis; 138 | text-overflow: ellipsis; 139 | color: #717171; 140 | display: block; 141 | color: $gray; 142 | font-size: 10px; 143 | padding: 5px 15px; 144 | background: #f5f5f5; 145 | border-top: 1px solid #eee; 146 | text-transform: uppercase; 147 | } -------------------------------------------------------------------------------- /public/css/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2011 Twitter, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /public/css/bootstrap/_alerts.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Alerts 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base styles 7 | // ------------------------- 8 | 9 | .alert { 10 | padding: $alert-padding; 11 | margin-bottom: $line-height-computed; 12 | border: 1px solid transparent; 13 | border-radius: $alert-border-radius; 14 | 15 | // Headings for larger alerts 16 | h4 { 17 | margin-top: 0; 18 | // Specified for the h4 to prevent conflicts of changing $headingsColor 19 | color: inherit; 20 | } 21 | // Provide class for links that match alerts 22 | .alert-link { 23 | font-weight: $alert-link-font-weight; 24 | } 25 | 26 | // Improve alignment and spacing of inner content 27 | > p, 28 | > ul { 29 | margin-bottom: 0; 30 | } 31 | > p + p { 32 | margin-top: 5px; 33 | } 34 | } 35 | 36 | // Dismissable alerts 37 | // 38 | // Expand the right padding and account for the close button's positioning. 39 | 40 | .alert-dismissable { 41 | padding-right: ($alert-padding + 20); 42 | 43 | // Adjust close link position 44 | .close { 45 | position: relative; 46 | top: -2px; 47 | right: -21px; 48 | color: inherit; 49 | } 50 | } 51 | 52 | // Alternate styles 53 | // 54 | // Generate contextual modifier classes for colorizing the alert. 55 | 56 | .alert-success { 57 | @include alert-variant($alert-success-bg, $alert-success-border, $alert-success-text); 58 | } 59 | .alert-info { 60 | @include alert-variant($alert-info-bg, $alert-info-border, $alert-info-text); 61 | } 62 | .alert-warning { 63 | @include alert-variant($alert-warning-bg, $alert-warning-border, $alert-warning-text); 64 | } 65 | .alert-danger { 66 | @include alert-variant($alert-danger-bg, $alert-danger-border, $alert-danger-text); 67 | } 68 | -------------------------------------------------------------------------------- /public/css/bootstrap/_badges.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Badges 3 | // -------------------------------------------------- 4 | 5 | 6 | // Base classes 7 | .badge { 8 | display: inline-block; 9 | min-width: 10px; 10 | padding: 3px 7px; 11 | font-size: $font-size-small; 12 | font-weight: $badge-font-weight; 13 | color: $badge-color; 14 | line-height: $badge-line-height; 15 | vertical-align: baseline; 16 | white-space: nowrap; 17 | text-align: center; 18 | background-color: $badge-bg; 19 | border-radius: $badge-border-radius; 20 | 21 | // Empty badges collapse automatically (not available in IE8) 22 | &:empty { 23 | display: none; 24 | } 25 | } 26 | 27 | // Hover state, but only for links 28 | a.badge { 29 | &:hover, 30 | &:focus { 31 | color: $badge-link-hover-color; 32 | text-decoration: none; 33 | cursor: pointer; 34 | } 35 | } 36 | 37 | // Quick fix for labels/badges in buttons 38 | .btn .badge { 39 | position: relative; 40 | top: -1px; 41 | } 42 | 43 | // Account for counters in navs 44 | a.list-group-item.active > .badge, 45 | .nav-pills > .active > a > .badge { 46 | color: $badge-active-color; 47 | background-color: $badge-active-bg; 48 | } 49 | .nav-pills > li > a > .badge { 50 | margin-left: 3px; 51 | } 52 | -------------------------------------------------------------------------------- /public/css/bootstrap/_breadcrumbs.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Breadcrumbs 3 | // -------------------------------------------------- 4 | 5 | 6 | .breadcrumb { 7 | padding: 8px 15px; 8 | margin-bottom: $line-height-computed; 9 | list-style: none; 10 | background-color: $breadcrumb-bg; 11 | border-radius: $border-radius-base; 12 | > li { 13 | display: inline-block; 14 | &+li:before { 15 | content: "/\00a0"; // Unicode space added since inline-block means non-collapsing white-space 16 | padding: 0 5px; 17 | color: $breadcrumb-color; 18 | } 19 | } 20 | > .active { 21 | color: $breadcrumb-active-color; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/css/bootstrap/_close.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Close icons 3 | // -------------------------------------------------- 4 | 5 | 6 | .close { 7 | float: right; 8 | font-size: ($font-size-base * 1.5); 9 | font-weight: $close-font-weight; 10 | line-height: 1; 11 | color: $close-color; 12 | text-shadow: $close-text-shadow; 13 | @include opacity(.2); 14 | 15 | &:hover, 16 | &:focus { 17 | color: $close-color; 18 | text-decoration: none; 19 | cursor: pointer; 20 | @include opacity(.5); 21 | } 22 | 23 | // [converter] extracted button& to button.close 24 | } 25 | 26 | // Additional properties for button version 27 | // iOS requires the button element instead of an anchor tag. 28 | // If you want the anchor version, it requires `href="#"`. 29 | button.close { 30 | padding: 0; 31 | cursor: pointer; 32 | background: transparent; 33 | border: 0; 34 | -webkit-appearance: none; 35 | } 36 | -------------------------------------------------------------------------------- /public/css/bootstrap/_code.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Code (inline and blocK) 3 | // -------------------------------------------------- 4 | 5 | 6 | // Inline and block code styles 7 | code, 8 | pre { 9 | font-family: $font-family-monospace; 10 | } 11 | 12 | // Inline code 13 | code { 14 | padding: 2px 4px; 15 | font-size: 90%; 16 | color: $code-color; 17 | background-color: $code-bg; 18 | white-space: nowrap; 19 | border-radius: $border-radius-base; 20 | } 21 | 22 | // Blocks of code 23 | pre { 24 | display: block; 25 | padding: (($line-height-computed - 1) / 2); 26 | margin: 0 0 ($line-height-computed / 2); 27 | font-size: ($font-size-base - 1); // 14px to 13px 28 | line-height: $line-height-base; 29 | word-break: break-all; 30 | word-wrap: break-word; 31 | color: $pre-color; 32 | background-color: $pre-bg; 33 | border: 1px solid $pre-border-color; 34 | border-radius: $border-radius-base; 35 | 36 | // Make prettyprint styles more spaced out for readability 37 | &.prettyprint { 38 | margin-bottom: $line-height-computed; 39 | } 40 | 41 | // Account for some code outputs that place code tags in pre tags 42 | code { 43 | padding: 0; 44 | font-size: inherit; 45 | color: inherit; 46 | white-space: pre-wrap; 47 | background-color: transparent; 48 | border: 0; 49 | } 50 | } 51 | 52 | // Enable scrollable blocks of code 53 | .pre-scrollable { 54 | max-height: $pre-scrollable-max-height; 55 | overflow-y: scroll; 56 | } 57 | -------------------------------------------------------------------------------- /public/css/bootstrap/_component-animations.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Component animations 3 | // -------------------------------------------------- 4 | 5 | // Heads up! 6 | // 7 | // We don't use the `.opacity()` mixin here since it causes a bug with text 8 | // fields in IE7-8. Source: https://github.com/twitter/bootstrap/pull/3552. 9 | 10 | .fade { 11 | opacity: 0; 12 | @include transition(opacity .15s linear); 13 | &.in { 14 | opacity: 1; 15 | } 16 | } 17 | 18 | .collapse { 19 | display: none; 20 | &.in { 21 | display: block; 22 | } 23 | } 24 | .collapsing { 25 | position: relative; 26 | height: 0; 27 | overflow: hidden; 28 | @include transition(height .35s ease); 29 | } 30 | -------------------------------------------------------------------------------- /public/css/bootstrap/_input-groups.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Input groups 3 | // -------------------------------------------------- 4 | 5 | // Base styles 6 | // ------------------------- 7 | .input-group { 8 | position: relative; // For dropdowns 9 | display: table; 10 | border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table 11 | 12 | // Undo padding and float of grid classes 13 | &.col { 14 | float: none; 15 | padding-left: 0; 16 | padding-right: 0; 17 | } 18 | 19 | .form-control { 20 | width: 100%; 21 | margin-bottom: 0; 22 | } 23 | } 24 | 25 | // Sizing options 26 | // 27 | // Remix the default form control sizing classes into new ones for easier 28 | // manipulation. 29 | 30 | .input-group-lg > .form-control, 31 | .input-group-lg > .input-group-addon, 32 | .input-group-lg > .input-group-btn > .btn { @extend .input-lg; } 33 | .input-group-sm > .form-control, 34 | .input-group-sm > .input-group-addon, 35 | .input-group-sm > .input-group-btn > .btn { @extend .input-sm; } 36 | 37 | 38 | // Display as table-cell 39 | // ------------------------- 40 | .input-group-addon, 41 | .input-group-btn, 42 | .input-group .form-control { 43 | display: table-cell; 44 | 45 | &:not(:first-child):not(:last-child) { 46 | border-radius: 0; 47 | } 48 | } 49 | // Addon and addon wrapper for buttons 50 | .input-group-addon, 51 | .input-group-btn { 52 | width: 1%; 53 | white-space: nowrap; 54 | vertical-align: middle; // Match the inputs 55 | } 56 | 57 | // Text input groups 58 | // ------------------------- 59 | .input-group-addon { 60 | padding: $padding-base-vertical $padding-base-horizontal; 61 | font-size: $font-size-base; 62 | font-weight: normal; 63 | line-height: 1; 64 | text-align: center; 65 | background-color: $input-group-addon-bg; 66 | border: 1px solid $input-group-addon-border-color; 67 | border-radius: $border-radius-base; 68 | 69 | // Sizing 70 | &.input-sm { 71 | padding: $padding-small-vertical $padding-small-horizontal; 72 | font-size: $font-size-small; 73 | border-radius: $border-radius-small; 74 | } 75 | &.input-lg { 76 | padding: $padding-large-vertical $padding-large-horizontal; 77 | font-size: $font-size-large; 78 | border-radius: $border-radius-large; 79 | } 80 | 81 | // Nuke default margins from checkboxes and radios to vertically center within. 82 | input[type="radio"], 83 | input[type="checkbox"] { 84 | margin-top: 0; 85 | } 86 | } 87 | 88 | // Reset rounded corners 89 | .input-group .form-control:first-child, 90 | .input-group-addon:first-child, 91 | .input-group-btn:first-child > .btn, 92 | .input-group-btn:first-child > .dropdown-toggle, 93 | .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { 94 | @include border-right-radius(0); 95 | } 96 | .input-group-addon:first-child { 97 | border-right: 0; 98 | } 99 | .input-group .form-control:last-child, 100 | .input-group-addon:last-child, 101 | .input-group-btn:last-child > .btn, 102 | .input-group-btn:last-child > .dropdown-toggle, 103 | .input-group-btn:first-child > .btn:not(:first-child) { 104 | @include border-left-radius(0); 105 | } 106 | .input-group-addon:last-child { 107 | border-left: 0; 108 | } 109 | 110 | // Button input groups 111 | // ------------------------- 112 | .input-group-btn { 113 | position: relative; 114 | white-space: nowrap; 115 | } 116 | .input-group-btn > .btn { 117 | position: relative; 118 | // Jankily prevent input button groups from wrapping 119 | + .btn { 120 | margin-left: -4px; 121 | } 122 | // Bring the "active" button to the front 123 | &:hover, 124 | &:active { 125 | z-index: 2; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /public/css/bootstrap/_jumbotron.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Jumbotron 3 | // -------------------------------------------------- 4 | 5 | 6 | .jumbotron { 7 | padding: $jumbotron-padding; 8 | margin-bottom: $jumbotron-padding; 9 | font-size: ($font-size-base * 1.5); 10 | font-weight: 200; 11 | line-height: ($line-height-base * 1.5); 12 | color: $jumbotron-color; 13 | background-color: $jumbotron-bg; 14 | 15 | h1 { 16 | line-height: 1; 17 | color: $jumbotron-heading-color; 18 | } 19 | p { 20 | line-height: 1.4; 21 | } 22 | 23 | .container & { 24 | border-radius: $border-radius-large; // Only round corners at higher resolutions if contained in a container 25 | } 26 | 27 | @media screen and (min-width: $screen-tablet) { 28 | padding-top: ($jumbotron-padding * 1.6); 29 | padding-bottom: ($jumbotron-padding * 1.6); 30 | 31 | .container & { 32 | padding-left: ($jumbotron-padding * 2); 33 | padding-right: ($jumbotron-padding * 2); 34 | } 35 | 36 | h1 { 37 | font-size: ($font-size-base * 4.5); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /public/css/bootstrap/_labels.scss: -------------------------------------------------------------------------------- 1 | // 2 | // Labels 3 | // -------------------------------------------------- 4 | 5 | .label { 6 | display: inline; 7 | padding: .2em .6em .3em; 8 | font-size: 75%; 9 | font-weight: bold; 10 | line-height: 1; 11 | color: $label-color; 12 | text-align: center; 13 | white-space: nowrap; 14 | vertical-align: baseline; 15 | border-radius: .25em; 16 | 17 | // Add hover effects, but only for links 18 | &[href] { 19 | &:hover, 20 | &:focus { 21 | color: $label-link-hover-color; 22 | text-decoration: none; 23 | cursor: pointer; 24 | } 25 | } 26 | 27 | // Empty labels collapse automatically (not available in IE8) 28 | &:empty { 29 | display: none; 30 | } 31 | } 32 | 33 | // Colors 34 | // Contextual variations (linked labels get darker on :hover) 35 | 36 | .label-default { 37 | @include label-variant($label-default-bg); 38 | } 39 | 40 | .label-primary { 41 | @include label-variant($label-primary-bg); 42 | } 43 | 44 | .label-success { 45 | @include label-variant($label-success-bg); 46 | } 47 | 48 | .label-info { 49 | @include label-variant($label-info-bg); 50 | } 51 | 52 | .label-warning { 53 | @include label-variant($label-warning-bg); 54 | } 55 | 56 | .label-danger { 57 | @include label-variant($label-danger-bg); 58 | } 59 | -------------------------------------------------------------------------------- /public/css/bootstrap/_list-group.scss: -------------------------------------------------------------------------------- 1 | // 2 | // List groups 3 | // -------------------------------------------------- 4 | 5 | // Base class 6 | // 7 | // Easily usable on