diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..004e6ff --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +# OS +.DS_Store + +# Composer +/vendor \ No newline at end of file diff --git a/app/commands/.gitkeep b/app/commands/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/config/api.php b/app/config/api.php new file mode 100644 index 0000000..769efa2 --- /dev/null +++ b/app/config/api.php @@ -0,0 +1,5 @@ + 100, +); \ No newline at end of file diff --git a/app/config/app.php b/app/config/app.php new file mode 100644 index 0000000..249b7be --- /dev/null +++ b/app/config/app.php @@ -0,0 +1,177 @@ + true, + + /* + |-------------------------------------------------------------------------- + | Application Timezone + |-------------------------------------------------------------------------- + | + | Here you may specify the default timezone for your application, which + | will be used by the PHP date and date-time functions. We have gone + | ahead and set this to a sensible default for you out of the box. + | + */ + + 'timezone' => 'UTC', + + /* + |-------------------------------------------------------------------------- + | Application Locale Configuration + |-------------------------------------------------------------------------- + | + | The application locale determines the default locale that will be used + | by the translation service provider. You are free to set this value + | to any of the locales which will be supported by the application. + | + */ + + 'locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Application Fallback Locale + |-------------------------------------------------------------------------- + | + | The fallback locale determines the locale to use when the current one + | is not available. You may change the value to correspond to any of + | the language folders that are provided through your application. + | + */ + + 'fallback_locale' => 'en', + + /* + |-------------------------------------------------------------------------- + | Encryption Key + |-------------------------------------------------------------------------- + | + | This key is used by the Illuminate encrypter service and should be set + | to a random, long string, otherwise these encrypted values will not + | be safe. Make sure to change it before deploying any application! + | + */ + + 'key' => '1S@4nE2hUeHKQI01XWJEGBygKYcA09GT', + + /* + |-------------------------------------------------------------------------- + | Autoloaded Service Providers + |-------------------------------------------------------------------------- + | + | The service providers listed here will be automatically loaded on the + | request to your application. Feel free to add your own services to + | this array to grant expanded functionality to your applications. + | + */ + + 'providers' => array( + + 'Illuminate\Foundation\Providers\ArtisanServiceProvider', + 'Illuminate\Auth\AuthServiceProvider', + 'Illuminate\Cache\CacheServiceProvider', + 'Illuminate\Foundation\Providers\CommandCreatorServiceProvider', + 'Illuminate\Session\CommandsServiceProvider', + 'Illuminate\Foundation\Providers\ComposerServiceProvider', + 'Illuminate\Routing\ControllerServiceProvider', + 'Illuminate\Cookie\CookieServiceProvider', + 'Illuminate\Database\DatabaseServiceProvider', + 'Illuminate\Encryption\EncryptionServiceProvider', + 'Illuminate\Filesystem\FilesystemServiceProvider', + 'Illuminate\Hashing\HashServiceProvider', + 'Illuminate\Foundation\Providers\KeyGeneratorServiceProvider', + 'Illuminate\Log\LogServiceProvider', + 'Illuminate\Mail\MailServiceProvider', + 'Illuminate\Database\MigrationServiceProvider', + 'Illuminate\Pagination\PaginationServiceProvider', + 'Illuminate\Foundation\Providers\PublisherServiceProvider', + 'Illuminate\Queue\QueueServiceProvider', + 'Illuminate\Redis\RedisServiceProvider', + 'Illuminate\Auth\Reminders\ReminderServiceProvider', + 'Illuminate\Database\SeedServiceProvider', + 'Illuminate\Foundation\Providers\ServerServiceProvider', + 'Illuminate\Session\SessionServiceProvider', + 'Illuminate\Foundation\Providers\TinkerServiceProvider', + 'Illuminate\Translation\TranslationServiceProvider', + 'Illuminate\Validation\ValidationServiceProvider', + 'Illuminate\View\ViewServiceProvider', + 'Illuminate\Workbench\WorkbenchServiceProvider', + + ), + + /* + |-------------------------------------------------------------------------- + | Service Provider Manifest + |-------------------------------------------------------------------------- + | + | The service provider manifest is used by Laravel to lazy load service + | providers which are not needed for each request, as well to keep a + | list of all of the services. Here, you may set its storage spot. + | + */ + + 'manifest' => __DIR__.'/../storage/meta', + + /* + |-------------------------------------------------------------------------- + | Class Aliases + |-------------------------------------------------------------------------- + | + | This array of class aliases will be registered when this application + | is started. However, feel free to register as many as you wish as + | the aliases are "lazy" loaded so they don't hinder performance. + | + */ + + 'aliases' => array( + + 'App' => 'Illuminate\Support\Facades\App', + 'Artisan' => 'Illuminate\Support\Facades\Artisan', + 'Auth' => 'Illuminate\Support\Facades\Auth', + 'Blade' => 'Illuminate\Support\Facades\Blade', + 'Cache' => 'Illuminate\Support\Facades\Cache', + 'ClassLoader' => 'Illuminate\Support\ClassLoader', + 'Config' => 'Illuminate\Support\Facades\Config', + 'Controller' => 'Illuminate\Routing\Controllers\Controller', + 'Cookie' => 'Illuminate\Support\Facades\Cookie', + 'Crypt' => 'Illuminate\Support\Facades\Crypt', + 'DB' => 'Illuminate\Support\Facades\DB', + 'Eloquent' => 'Illuminate\Database\Eloquent\Model', + 'Event' => 'Illuminate\Support\Facades\Event', + 'File' => 'Illuminate\Support\Facades\File', + 'Hash' => 'Illuminate\Support\Facades\Hash', + 'Input' => 'Illuminate\Support\Facades\Input', + 'Lang' => 'Illuminate\Support\Facades\Lang', + 'Log' => 'Illuminate\Support\Facades\Log', + 'Mail' => 'Illuminate\Support\Facades\Mail', + 'Paginator' => 'Illuminate\Support\Facades\Paginator', + 'Password' => 'Illuminate\Support\Facades\Password', + 'Queue' => 'Illuminate\Support\Facades\Queue', + 'Redirect' => 'Illuminate\Support\Facades\Redirect', + 'Redis' => 'Illuminate\Support\Facades\Redis', + 'Request' => 'Illuminate\Support\Facades\Request', + 'Response' => 'Illuminate\Support\Facades\Response', + 'Route' => 'Illuminate\Support\Facades\Route', + 'Schema' => 'Illuminate\Support\Facades\Schema', + 'Seeder' => 'Illuminate\Database\Seeder', + 'Session' => 'Illuminate\Support\Facades\Session', + 'URL' => 'Illuminate\Support\Facades\URL', + 'Validator' => 'Illuminate\Support\Facades\Validator', + 'View' => 'Illuminate\Support\Facades\View', + + ), + +); diff --git a/app/config/auth.php b/app/config/auth.php new file mode 100644 index 0000000..62ea9c3 --- /dev/null +++ b/app/config/auth.php @@ -0,0 +1,63 @@ + 'eloquent', + + /* + |-------------------------------------------------------------------------- + | Authentication Model + |-------------------------------------------------------------------------- + | + | When using the "Eloquent" authentication driver, we need to know which + | Eloquent model should be used to retrieve your users. Of course, it + | is often just the "User" model but you may use whatever you like. + | + */ + + 'model' => 'User', + + /* + |-------------------------------------------------------------------------- + | Authentication Table + |-------------------------------------------------------------------------- + | + | When using the "Database" authentication driver, we need to know which + | table should be used to retrieve your users. We have chosen a basic + | default value but you may easily change it to any table you like. + | + */ + + 'table' => 'users', + + /* + |-------------------------------------------------------------------------- + | Password Reminder Settings + |-------------------------------------------------------------------------- + | + | Here you may set the settings for password reminders, including a view + | that should be used as your password reminder e-mail. You will also + | be able to set the name of the table that holds the reset tokens. + | + */ + + 'reminder' => array( + + 'email' => 'emails.auth.reminder', 'table' => 'password_reminders', + + ), + +); \ No newline at end of file diff --git a/app/config/cache.php b/app/config/cache.php new file mode 100644 index 0000000..f55ca14 --- /dev/null +++ b/app/config/cache.php @@ -0,0 +1,89 @@ + 'file', + + /* + |-------------------------------------------------------------------------- + | File Cache Location + |-------------------------------------------------------------------------- + | + | When using the "file" cache driver, we need a location where the cache + | files may be stored. A sensible default has been specified, but you + | are free to change it to any other place on disk that you desire. + | + */ + + 'path' => __DIR__.'/../storage/cache', + + /* + |-------------------------------------------------------------------------- + | Database Cache Connection + |-------------------------------------------------------------------------- + | + | When using the "database" cache driver you may specify the connection + | that should be used to store the cached items. When this option is + | null the default database connection will be utilized for cache. + | + */ + + 'connection' => null, + + /* + |-------------------------------------------------------------------------- + | Database Cache Table + |-------------------------------------------------------------------------- + | + | When using the "database" cache driver we need to know the table that + | should be used to store the cached items. A default table name has + | been provided but you're free to change it however you deem fit. + | + */ + + 'table' => 'cache', + + /* + |-------------------------------------------------------------------------- + | Memcached Servers + |-------------------------------------------------------------------------- + | + | Now you may specify an array of your Memcached servers that should be + | used when utilizing the Memcached cache driver. All of the servers + | should contain a value for "host", "port", and "weight" options. + | + */ + + 'memcached' => array( + + array('host' => '127.0.0.1', 'port' => 11211, 'weight' => 100), + + ), + + /* + |-------------------------------------------------------------------------- + | Cache Key Prefix + |-------------------------------------------------------------------------- + | + | When utilizing a RAM based store such as APC or Memcached, there might + | be other applications utilizing the same cache. So, we'll specify a + | value to get prefixed to all our keys so we can avoid collisions. + | + */ + + 'prefix' => 'laravel', + +); diff --git a/app/config/database.php b/app/config/database.php new file mode 100644 index 0000000..d811e9a --- /dev/null +++ b/app/config/database.php @@ -0,0 +1,122 @@ + PDO::FETCH_CLASS, + + /* + |-------------------------------------------------------------------------- + | Default Database Connection Name + |-------------------------------------------------------------------------- + | + | Here you may specify which of the database connections below you wish + | to use as your default connection for all database work. Of course + | you may use many connections at once using the Database library. + | + */ + + 'default' => 'mysql', + + /* + |-------------------------------------------------------------------------- + | Database Connections + |-------------------------------------------------------------------------- + | + | Here are each of the database connections setup for your application. + | Of course, examples of configuring each database platform that is + | supported by Laravel is shown below to make development simple. + | + | + | All database work in Laravel is done through the PHP PDO facilities + | so make sure you have the driver for your particular database of + | choice installed on your machine before you begin development. + | + */ + + 'connections' => array( + + 'sqlite' => array( + 'driver' => 'sqlite', + 'database' => __DIR__.'/../database/production.sqlite', + 'prefix' => '', + ), + + 'mysql' => array( + 'driver' => 'mysql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + 'collation' => 'utf8_unicode_ci', + 'prefix' => '', + ), + + 'pgsql' => array( + 'driver' => 'pgsql', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'charset' => 'utf8', + 'prefix' => '', + 'schema' => 'public', + ), + + 'sqlsrv' => array( + 'driver' => 'sqlsrv', + 'host' => 'localhost', + 'database' => 'database', + 'username' => 'root', + 'password' => '', + 'prefix' => '', + ), + + ), + + /* + |-------------------------------------------------------------------------- + | Migration Repository Table + |-------------------------------------------------------------------------- + | + | This table keeps track of all the migrations that have already run for + | your application. Using this information, we can determine which of + | the migrations on disk have not actually be run in the databases. + | + */ + + 'migrations' => 'migrations', + + /* + |-------------------------------------------------------------------------- + | Redis Databases + |-------------------------------------------------------------------------- + | + | Redis is an open source, fast, and advanced key-value store that also + | provides a richer set of commands than a typical key-value systems + | such as APC or Memcached. Laravel makes it easy to dig right in. + | + */ + + 'redis' => array( + + 'default' => array( + 'host' => '127.0.0.1', + 'port' => 6379, + 'database' => 0, + ), + + ), + +); \ No newline at end of file diff --git a/app/config/local/.gitignore b/app/config/local/.gitignore new file mode 100644 index 0000000..c96a04f --- /dev/null +++ b/app/config/local/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/app/config/mail.php b/app/config/mail.php new file mode 100644 index 0000000..e793d88 --- /dev/null +++ b/app/config/mail.php @@ -0,0 +1,83 @@ + 'smtp.postmarkapp.com', + + /* + |-------------------------------------------------------------------------- + | SMTP Host Port + |-------------------------------------------------------------------------- + | + | This is the SMTP port used by your application to delivery e-mails to + | users of your application. Like the host we have set this value to + | stay compatible with the Postmark e-mail application by default. + | + */ + + 'port' => 2525, + + /* + |-------------------------------------------------------------------------- + | Global "From" Address + |-------------------------------------------------------------------------- + | + | You may wish for all e-mails sent by your application to be sent from + | the same address. Here, you may specify a name and address that is + | used globally for all e-mails that are sent by your application. + | + */ + + 'from' => array('address' => null, 'name' => null), + + /* + |-------------------------------------------------------------------------- + | E-Mail Encryption Protocol + |-------------------------------------------------------------------------- + | + | Here you may specify the encryption protocol that should be used when + | the application send e-mail messages. A sensible default using the + | transport layer security protocol should provide great security. + | + */ + + 'encryption' => 'tls', + + /* + |-------------------------------------------------------------------------- + | SMTP Server Username + |-------------------------------------------------------------------------- + | + | If your SMTP server requires a username for authentication, you should + | set it here. This will get used to authenticate with your server on + | connection. You may also set the "password" value below this one. + | + */ + + 'username' => null, + + /* + |-------------------------------------------------------------------------- + | SMTP Server Password + |-------------------------------------------------------------------------- + | + | Here you may set the password required by your SMTP server to send out + | messages from your application. This will be given to the server on + | connection so that the application will be able to send messages. + | + */ + + 'password' => null, + +); diff --git a/app/config/packages/.gitkeep b/app/config/packages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/config/queue.php b/app/config/queue.php new file mode 100644 index 0000000..5dc5cb0 --- /dev/null +++ b/app/config/queue.php @@ -0,0 +1,45 @@ + 'sync', + + /* + |-------------------------------------------------------------------------- + | Queue Connections + |-------------------------------------------------------------------------- + | + | Here you may configure the connection information for each server that + | is used by your application. A default configuration has been added + | for each back-end shipped with Laravel. You are free to add more. + | + */ + + 'connections' => array( + + 'sync' => array( + 'driver' => 'sync', + ), + + 'beanstalkd' => array( + 'driver' => 'beanstalkd', + 'host' => 'localhost', + 'queue' => 'default', + ), + + ), + +); \ No newline at end of file diff --git a/app/config/session.php b/app/config/session.php new file mode 100644 index 0000000..95beffa --- /dev/null +++ b/app/config/session.php @@ -0,0 +1,99 @@ + 'array', + + /* + |-------------------------------------------------------------------------- + | Session Lifetime + |-------------------------------------------------------------------------- + | + | Here you may specify the number of minutes that you wish the session + | to be allowed to remain idle for it is expired. If you want them + | to immediately expire when the browser closes, set it to zero. + | + */ + + 'lifetime' => 120, + + /* + |-------------------------------------------------------------------------- + | Session File Location + |-------------------------------------------------------------------------- + | + | When using the "file" session driver, we need a location where session + | files may be stored. A default has been set for you but a different + | location may be specified. This is only needed for file sessions. + | + */ + + 'path' => __DIR__.'/../storage/sessions', + + /* + |-------------------------------------------------------------------------- + | Session Database Connection + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the database + | connection that should be used to manage your sessions. This should + | correspond to a connection in your "database" configuration file. + | + */ + + 'connection' => null, + + /* + |-------------------------------------------------------------------------- + | Session Database Table + |-------------------------------------------------------------------------- + | + | When using the "database" session driver, you may specify the table we + | should use to manage the sessions. Of course, a sensible default is + | provided for you; however, you are free to change this as needed. + | + */ + + 'table' => 'sessions', + + /* + |-------------------------------------------------------------------------- + | Session Sweeping Lottery + |-------------------------------------------------------------------------- + | + | Some session drivers must manually sweep their storage location to get + | rid of old sessions from storage. Here are the chances that it will + | happen on a given request. By default, the odds are 2 out of 100. + | + */ + + 'lottery' => array(2, 100), + + /* + |-------------------------------------------------------------------------- + | Session Cookie Name + |-------------------------------------------------------------------------- + | + | Here you may change the name of the cookie used to identify a session + | instance by ID. The name specified here will get used every time a + | new session cookie is created by the framework for every driver. + | + */ + + 'cookie' => 'laravel_session', + +); diff --git a/app/config/testing/cache.php b/app/config/testing/cache.php new file mode 100644 index 0000000..16d3ae2 --- /dev/null +++ b/app/config/testing/cache.php @@ -0,0 +1,20 @@ + 'array', + +); \ No newline at end of file diff --git a/app/config/testing/session.php b/app/config/testing/session.php new file mode 100644 index 0000000..338aeba --- /dev/null +++ b/app/config/testing/session.php @@ -0,0 +1,21 @@ + 'array', + +); \ No newline at end of file diff --git a/app/config/view.php b/app/config/view.php new file mode 100644 index 0000000..eba10a4 --- /dev/null +++ b/app/config/view.php @@ -0,0 +1,31 @@ + array(__DIR__.'/../views'), + + /* + |-------------------------------------------------------------------------- + | Pagination View + |-------------------------------------------------------------------------- + | + | This view will be used to render the pagination link output, and can + | be easily customized here to show any view you like. A clean view + | compatible with Twitter's Bootstrap is given to you by default. + | + */ + + 'pagination' => 'pagination::slider', + +); diff --git a/app/controllers/.gitkeep b/app/controllers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/BaseController.php b/app/controllers/BaseController.php new file mode 100644 index 0000000..097e161 --- /dev/null +++ b/app/controllers/BaseController.php @@ -0,0 +1,18 @@ +layout)) + { + $this->layout = View::make($this->layout); + } + } + +} \ No newline at end of file diff --git a/app/database/migrations/.gitkeep b/app/database/migrations/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/database/migrations/2013_01_27_195248_create_users.php b/app/database/migrations/2013_01_27_195248_create_users.php new file mode 100644 index 0000000..970e13c --- /dev/null +++ b/app/database/migrations/2013_01_27_195248_create_users.php @@ -0,0 +1,34 @@ +increments('id'); + $table->string('username', 50)->unique(); + $table->string('password', 60); + $table->string('api_key', 32)->unique(); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('users'); + } + +} \ No newline at end of file diff --git a/app/database/migrations/2013_01_31_141600_create_lists.php b/app/database/migrations/2013_01_31_141600_create_lists.php new file mode 100644 index 0000000..873dcb3 --- /dev/null +++ b/app/database/migrations/2013_01_31_141600_create_lists.php @@ -0,0 +1,35 @@ +increments('id'); + $table->integer('user_id')->unsigned(); + $table->string('name'); + $table->timestamps(); + + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade')->onUpdate('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('lists'); + } + +} \ No newline at end of file diff --git a/app/database/migrations/2013_01_31_141613_create_tasks.php b/app/database/migrations/2013_01_31_141613_create_tasks.php new file mode 100644 index 0000000..82ff8e7 --- /dev/null +++ b/app/database/migrations/2013_01_31_141613_create_tasks.php @@ -0,0 +1,36 @@ +increments('id'); + $table->integer('list_id')->unsigned(); + $table->string('description'); + $table->boolean('completed'); + $table->timestamps(); + + $table->foreign('list_id')->references('id')->on('lists')->onDelete('cascade')->onUpdate('cascade'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('tasks'); + } + +} \ No newline at end of file diff --git a/app/database/production.sqlite b/app/database/production.sqlite new file mode 100644 index 0000000..e69de29 diff --git a/app/database/seeds/.gitkeep b/app/database/seeds/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/database/seeds/DatabaseSeeder.php b/app/database/seeds/DatabaseSeeder.php new file mode 100644 index 0000000..45f529c --- /dev/null +++ b/app/database/seeds/DatabaseSeeder.php @@ -0,0 +1,25 @@ +call('UserTableSeeder'); + } +} + +class UserTableSeeder extends Seeder +{ + public function run() + { + User::create(array( + 'username' => 'admin', + 'password' => 'admin', + )); + } +} \ No newline at end of file diff --git a/app/errors.php b/app/errors.php new file mode 100644 index 0000000..a1e2cba --- /dev/null +++ b/app/errors.php @@ -0,0 +1,67 @@ +getHeaders(); + + switch ($code) + { + case 401: + $default_message = 'Invalid API key'; + $headers['WWW-Authenticate'] = 'Basic realm="REST API"'; + break; + + case 403: + $default_message = 'Insufficient privileges to perform this action'; + break; + + case 404: + $default_message = 'The requested resource was not found'; + break; + + default: + $default_message = 'An unexpected error occurred'; + } + + return Response::json(array( + 'error' => $e->getMessage() ?: $default_message, + ), $code, $headers); +}); + +// ErrorMessageException handler +App::error(function(ErrorMessageException $e) +{ + return Response::json(array( + 'errors' => $e->getMessages()->all(), + ), 400); +}); + +// NotFoundException handler +App::error(function(NotFoundException $e) +{ + $default_message = 'The requested resource was not found'; + + return Response::json(array( + 'error' => $e->getMessage() ?: $default_message, + ), 404); +}); + +// PermissionException handler +App::error(function(PermissionException $e) +{ + $default_message = 'Insufficient privileges to perform this action'; + + return Response::json(array( + 'error' => $e->getMessage() ?: $default_message, + ), 403); +}); \ No newline at end of file diff --git a/app/events.php b/app/events.php new file mode 100644 index 0000000..7f59772 --- /dev/null +++ b/app/events.php @@ -0,0 +1,10 @@ +api_key = User::createApiKey(); +}); \ No newline at end of file diff --git a/app/exceptions/ErrorMessageException.php b/app/exceptions/ErrorMessageException.php new file mode 100644 index 0000000..e814994 --- /dev/null +++ b/app/exceptions/ErrorMessageException.php @@ -0,0 +1,46 @@ +messages = $messages->getMessageBag(); + $this->messages->setFormat(':message'); + + parent::__construct('', $code, $previous); + } + + /** + * Return error messages. + * + * @return Illuminate\Support\MessageBag + */ + public function getMessages() + { + return $this->messages; + } +} \ No newline at end of file diff --git a/app/exceptions/NotFoundException.php b/app/exceptions/NotFoundException.php new file mode 100644 index 0000000..464fb86 --- /dev/null +++ b/app/exceptions/NotFoundException.php @@ -0,0 +1,3 @@ +first(); + + if (!$user) + { + App::abort(401); + } + + Auth::login($user); +}); + +Route::filter('api.limit', function() +{ + $key = sprintf('api:%s', Auth::user()->api_key); + + // Create the key if it doesn't exist + apc_add($key, 0, 60*60); + + // Increment by 1 + $count = apc_inc($key); + + // Fail if hourly requests exceeded + if ($count > Config::get('api.requests_per_hour')) + { + App::abort(403, 'Hourly request limit exceeded'); + } +}); \ No newline at end of file diff --git a/app/lang/en/pagination.php b/app/lang/en/pagination.php new file mode 100644 index 0000000..eb9be3b --- /dev/null +++ b/app/lang/en/pagination.php @@ -0,0 +1,20 @@ + '« Previous', + + 'next' => 'Next »', + +); \ No newline at end of file diff --git a/app/lang/en/reminders.php b/app/lang/en/reminders.php new file mode 100644 index 0000000..4a9f176 --- /dev/null +++ b/app/lang/en/reminders.php @@ -0,0 +1,22 @@ + "Passwords must be six characters and match the confirmation.", + + "user" => "We can't find a user with that e-mail address.", + + "token" => "This password reset token is invalid.", + +); \ No newline at end of file diff --git a/app/lang/en/validation.php b/app/lang/en/validation.php new file mode 100644 index 0000000..a67a114 --- /dev/null +++ b/app/lang/en/validation.php @@ -0,0 +1,91 @@ + "The :attribute must be accepted.", + "active_url" => "The :attribute is not a valid URL.", + "after" => "The :attribute must be a date after :date.", + "alpha" => "The :attribute may only contain letters.", + "alpha_dash" => "The :attribute may only contain letters, numbers, and dashes.", + "alpha_num" => "The :attribute may only contain letters and numbers.", + "before" => "The :attribute must be a date before :date.", + "between" => array( + "numeric" => "The :attribute must be between :min - :max.", + "file" => "The :attribute must be between :min - :max kilobytes.", + "string" => "The :attribute must be between :min - :max characters.", + ), + "confirmed" => "The :attribute confirmation does not match.", + "date" => "The :attribute is not a valid date.", + "date_format" => "The :attribute does not match the format :format.", + "different" => "The :attribute and :other must be different.", + "digits" => "The :attribute must be :digits digits.", + "digits_between" => "The :attribute must be between :min and :max digits.", + "email" => "The :attribute format is invalid.", + "exists" => "The selected :attribute is invalid.", + "image" => "The :attribute must be an image.", + "in" => "The selected :attribute is invalid.", + "integer" => "The :attribute must be an integer.", + "ip" => "The :attribute must be a valid IP address.", + "match" => "The :attribute format is invalid.", + "max" => array( + "numeric" => "The :attribute must be less than :max.", + "file" => "The :attribute must be less than :max kilobytes.", + "string" => "The :attribute must be less than :max characters.", + ), + "mimes" => "The :attribute must be a file of type: :values.", + "min" => array( + "numeric" => "The :attribute must be at least :min.", + "file" => "The :attribute must be at least :min kilobytes.", + "string" => "The :attribute must be at least :min characters.", + ), + "notin" => "The selected :attribute is invalid.", + "numeric" => "The :attribute must be a number.", + "required" => "The :attribute field is required.", + "required_with" => "The :attribute field is required when :values is present.", + "same" => "The :attribute and :other must match.", + "size" => array( + "numeric" => "The :attribute must be :size.", + "file" => "The :attribute must be :size kilobytes.", + "string" => "The :attribute must be :size characters.", + ), + "unique" => "The :attribute has already been taken.", + "url" => "The :attribute format is invalid.", + + /* + |-------------------------------------------------------------------------- + | Custom Validation Language Lines + |-------------------------------------------------------------------------- + | + | Here you may specify custom validation messages for attributes using the + | convention "attribute.rule" to name the lines. This makes it quick to + | specify a specific custom language line for a given attribute rule. + | + */ + + 'custom' => array(), + + /* + |-------------------------------------------------------------------------- + | Custom Validation Attributes + |-------------------------------------------------------------------------- + | + | The following language lines are used to swap attribute place-holders + | with something more reader friendly such as E-Mail Address instead + | of "email". This simply helps us make messages a little cleaner. + | + */ + + 'attributes' => array(), + +); \ No newline at end of file diff --git a/app/models/Task.php b/app/models/Task.php new file mode 100644 index 0000000..566f172 --- /dev/null +++ b/app/models/Task.php @@ -0,0 +1,99 @@ +belongsTo('TaskList', 'list_id'); + } + + /** + * Completed mutator. + * + * @param mixed $completed + * @return void + */ + protected function setCompletedAttribute($completed) + { + $value = 0; + + $completed = strtolower($completed); + + if (in_array($completed, array('y', 'yes', '1', 'true', 't'))) + { + $value = 1; + } + + $this->attributes['completed'] = $value; + } + + /** + * Completed accessor. + * + * @return bool + */ + protected function getCompletedAttribute($completed) + { + return (bool) $completed; + } + + /** + * Validate the model's attributes. + * + * @return void + */ + public function validate() + { + $val = Validator::make($this->attributes, array( + 'description' => 'required', + )); + + if ($val->fails()) + { + throw new ValidationException($val); + } + } + + /** + * Convert the model instance to an array. + * + * @return array + */ + public function toArray() + { + $data = parent::toArray(); + + $data['id'] = (int) $this->id; + $data['completed'] = $this->completed; + $data['created_at'] = $this->fromDateTime($this->created_at); + $data['updated_at'] = $this->fromDateTime($this->updated_at); + + return $data; + } +} \ No newline at end of file diff --git a/app/models/TaskList.php b/app/models/TaskList.php new file mode 100644 index 0000000..5310cc7 --- /dev/null +++ b/app/models/TaskList.php @@ -0,0 +1,118 @@ +belongsTo('User'); + } + + /** + * Tasks relationship. + * + * @return Illuminate\Database\Eloquent\Relations\HasMany + */ + public function tasks() + { + return $this->hasMany('Task', 'list_id'); + } + + /** + * Find a list by ID, and verify its ownership by the given user + * + * @param Illuminate\Auth\UserInterface|int $owner + * @param int $id + * @return Illuminate\Database\Eloquent\Model + */ + public static function findByOwnerAndId($owner, $id) + { + if (!is_numeric($owner) && !($owner instanceof UserInterface)) + { + throw new InvalidArgumentException('Owner must be either a numeric ID or an instance of UserInterface'); + } + + $list = static::find($id); + + if (!$list) + { + throw new NotFoundException('List was not found'); + } + + $owner_id = ($owner instanceof UserInterface) ? (int) $owner->id : (int) $owner; + + if ((int) $list->user_id !== $owner_id) + { + throw new PermissionException('Insufficient access privileges for this list'); + } + + return $list; + } + + /** + * Validate the model's attributes. + * + * @return void + */ + public function validate() + { + $val = Validator::make($this->attributes, array( + 'name' => 'required', + )); + + if ($val->fails()) + { + throw new ValidationException($val); + } + } + + /** + * Convert the model instance to an array. + * + * @return array + */ + public function toArray() + { + $data = parent::toArray(); + + $data['id'] = (int) $this->id; + $data['created_at'] = $this->fromDateTime($this->created_at); + $data['updated_at'] = $this->fromDateTime($this->updated_at); + + return $data; + } +} \ No newline at end of file diff --git a/app/models/User.php b/app/models/User.php new file mode 100644 index 0000000..4bbb417 --- /dev/null +++ b/app/models/User.php @@ -0,0 +1,72 @@ +hasMany('TaskList'); + } + + /** + * Get the unique identifier for the user. + * + * @return mixed + */ + public function getAuthIdentifier() + { + return $this->getKey(); + } + + /** + * Get the password for the user. + * + * @return string + */ + public function getAuthPassword() + { + return $this->password; + } + + /** + * Password mutator. + * + * @param string $password + * @return void + */ + public function setPasswordAttribute($password) + { + $this->attributes['password'] = Hash::make($password); + } + + /** + * Generate a random, unique API key. + * + * @return string + */ + public static function createApiKey() + { + return Str::secureRandom(32); + } +} \ No newline at end of file diff --git a/app/routes.php b/app/routes.php new file mode 100644 index 0000000..055781f --- /dev/null +++ b/app/routes.php @@ -0,0 +1,150 @@ + 'v1', 'before' => 'api.auth|api.limit'), function() +{ + // Get all lists + Route::get('lists', function() + { + $lists = Auth::user()->tasklists; + + return Response::json($lists->toArray()); + }); + + // Create new list + Route::post('lists', function() + { + $list = new TaskList(Input::get()); + $list->validate(); + $list->user_id = Auth::user()->id; + + if (!$list->save()) + { + App::abort(500, 'List was not saved'); + } + + return Response::json($list->toArray(), 201); + }); + + // Get list by ID + Route::get('lists/{id}', function($id) + { + $list = TaskList::findByOwnerAndId(Auth::user(), $id); + + return Response::json($list->toArray()); + })->where('id', '\d+'); + + // Update list by ID + Route::put('lists/{id}', function($id) + { + $list = TaskList::findByOwnerAndId(Auth::user(), $id); + $list->fill(Input::get()); + $list->validate(); + + if (!$list->save()) + { + App::abort(500, 'List was not updated'); + } + + return Response::json($list->toArray()); + })->where('id', '\d+'); + + // Delete list by ID + Route::delete('lists/{id}', function($id) + { + $list = TaskList::findByOwnerAndId(Auth::user(), $id); + $list->delete(); + + return Response::json(null, 204); + })->where('id', '\d+'); + + // Get tasks for list + Route::get('lists/{id}/tasks', function($id) + { + $list = TaskList::findByOwnerAndId(Auth::user(), $id); + + return Response::json($list->tasks->toArray()); + })->where('id', '\d+'); + + // Create task + Route::post('lists/{id}/tasks', function($id) + { + $list = TaskList::findByOwnerAndId(Auth::user(), $id); + + $task = new Task(Input::get()); + $task->validate(); + $task->list_id = $id; + + if (!$task->save()) + { + App::abort(500, 'Task was not saved'); + } + + return Response::json($task->toArray(), 201); + })->where('id', '\d+'); + + // Get task by ID + Route::get('lists/{list_id}/tasks/{id}', function($list_id, $id) + { + $list = TaskList::findByOwnerAndId(Auth::user(), $list_id); + + $task = $list->tasks()->find($id); + + if (!$task) + { + App::abort(404); + } + + return Response::json($task->toArray()); + })->where('list_id', '\d+')->where('id', '\d+'); + + // Update task + Route::put('lists/{list_id}/tasks/{id}', function($list_id, $id) + { + $list = TaskList::findByOwnerAndId(Auth::user(), $list_id); + + $task = $list->tasks()->find($id); + + if (!$task) + { + App::abort(404); + } + + $task->fill(Input::get()); + $task->validate(); + + if (!$task->save()) + { + App::abort(500, 'Task was not updated'); + } + + return Response::json($task->toArray()); + })->where('list_id', '\d+')->where('id', '\d+'); + + // Delete task + Route::delete('lists/{list_id}/tasks/{id}', function($list_id, $id) + { + $list = TaskList::findByOwnerAndId(Auth::user(), $list_id); + + $task = $list->tasks()->find($id); + + if (!$task) + { + App::abort(404); + } + + $task->delete(); + + return Response::json(null, 204); + })->where('list_id', '\d+')->where('id', '\d+'); +}); \ No newline at end of file diff --git a/app/start/artisan.php b/app/start/artisan.php new file mode 100644 index 0000000..1df850b --- /dev/null +++ b/app/start/artisan.php @@ -0,0 +1,13 @@ +boot(); + +/* +|-------------------------------------------------------------------------- +| Load The Artisan Console Application +|-------------------------------------------------------------------------- +| +| We'll need to run the script to load and return the Artisan console +| application. We keep this in its own script so that we will load +| the console application independent of running commands which +| will allow us to fire commands from Routes when we want to. +| +*/ + +$artisan = Illuminate\Console\Application::start($app); + +/* +|-------------------------------------------------------------------------- +| Run The Artisan Application +|-------------------------------------------------------------------------- +| +| When we run the console application, the current CLI command will be +| executed in this console and the response sent back to a terminal +| or another output device for the developers. Here goes nothing! +| +*/ + +$artisan->run(); diff --git a/bootstrap/autoload.php b/bootstrap/autoload.php new file mode 100644 index 0000000..461a1a8 --- /dev/null +++ b/bootstrap/autoload.php @@ -0,0 +1,44 @@ + __DIR__.'/../app', + + /* + |-------------------------------------------------------------------------- + | Public Path + |-------------------------------------------------------------------------- + | + | The public path contains the assets for your web application, such as + | your JavaScript and CSS files, and also contains the primary entry + | point for web requests into these applications from the outside. + | + */ + + 'public' => __DIR__.'/../public', + + /* + |-------------------------------------------------------------------------- + | Base Path + |-------------------------------------------------------------------------- + | + | The base path is the root of the Laravel installation. Most likely you + | will not need to change this value. But, if for some wild reason it + | is necessary you will do so here, just proceed with some caution. + | + */ + + 'base' => __DIR__.'/..', + +); diff --git a/bootstrap/start.php b/bootstrap/start.php new file mode 100644 index 0000000..7e5462e --- /dev/null +++ b/bootstrap/start.php @@ -0,0 +1,70 @@ +detectEnvironment(array( + + 'local' => array('localhost', '*.dev', '*.app'), + +)); + +/* +|-------------------------------------------------------------------------- +| Bind Paths +|-------------------------------------------------------------------------- +| +| Here we are binding the paths configured in paths.php to the app. You +| should not be changing these here. If you need to change these you +| may do so within the paths.php file and they will be bound here. +| +*/ + +$app->bindInstallPaths(require __DIR__.'/paths.php'); + +/* +|-------------------------------------------------------------------------- +| Load The Application +|-------------------------------------------------------------------------- +| +| Here we will load the Illuminate application. We'll keep this is in a +| separate location so we can isolate the creation of an application +| from the actual running of the application with a given request. +| +*/ + +require $app->getBootstrapFile(); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..024d065 --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "require": { + "laravel/framework": "4.0.*" + }, + "autoload": { + "classmap": [ + "app/commands", + "app/controllers", + "app/exceptions", + "app/models", + "app/database/migrations", + "app/database/seeds", + "app/tests/TestCase.php" + ] + }, + "minimum-stability": "dev" +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..e21c716 --- /dev/null +++ b/composer.lock @@ -0,0 +1,926 @@ +{ + "hash": "2a9c37e837664e054f147503d358abb1", + "packages": [ + { + "name": "ircmaxell/password-compat", + "version": "1.0.x-dev", + "source": { + "type": "git", + "url": "https://github.com/ircmaxell/password_compat.git", + "reference": "v1.0.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/ircmaxell/password_compat/archive/v1.0.0.zip", + "reference": "v1.0.0", + "shasum": "" + }, + "time": "2013-01-14 16:49:31", + "type": "library", + "autoload": { + "files": [ + "lib/password.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Anthony Ferrara", + "email": "ircmaxell@php.net", + "homepage": "http://blog.ircmaxell.com" + } + ], + "description": "A compatibility library for the proposed simplified password hashing algorithm: https://wiki.php.net/rfc/password_hash", + "homepage": "https://github.com/ircmaxell/password_compat", + "keywords": [ + "hashing", + "password" + ] + }, + { + "name": "laravel/framework", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/laravel/framework.git", + "reference": "5d0e6ecc135421b5672b63c583a508fc71af55c7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/laravel/framework/zipball/5d0e6ecc135421b5672b63c583a508fc71af55c7", + "reference": "5d0e6ecc135421b5672b63c583a508fc71af55c7", + "shasum": "" + }, + "require": { + "ext-mcrypt": "*", + "ircmaxell/password-compat": "1.0.*", + "monolog/monolog": "1.3.*", + "patchwork/utf8": "1.0.*", + "php": ">=5.3.0", + "swiftmailer/swiftmailer": "4.3.*", + "symfony/browser-kit": "2.2.*", + "symfony/console": "2.2.*", + "symfony/css-selector": "2.2.*", + "symfony/dom-crawler": "2.2.*", + "symfony/event-dispatcher": "2.2.*", + "symfony/finder": "2.2.*", + "symfony/http-foundation": "2.2.*", + "symfony/http-kernel": "2.2.*", + "symfony/process": "2.2.*", + "symfony/routing": "2.2.*", + "symfony/translation": "2.2.*" + }, + "replace": { + "illuminate/auth": "self.version", + "illuminate/cache": "self.version", + "illuminate/config": "self.version", + "illuminate/console": "self.version", + "illuminate/container": "self.version", + "illuminate/cookie": "self.version", + "illuminate/database": "self.version", + "illuminate/encryption": "self.version", + "illuminate/events": "self.version", + "illuminate/exception": "self.version", + "illuminate/filesystem": "self.version", + "illuminate/foundation": "self.version", + "illuminate/hashing": "self.version", + "illuminate/http": "self.version", + "illuminate/log": "self.version", + "illuminate/mail": "self.version", + "illuminate/pagination": "self.version", + "illuminate/queue": "self.version", + "illuminate/redis": "self.version", + "illuminate/routing": "self.version", + "illuminate/session": "self.version", + "illuminate/support": "self.version", + "illuminate/translation": "self.version", + "illuminate/validation": "self.version", + "illuminate/view": "self.version", + "illuminate/workbench": "self.version" + }, + "require-dev": { + "aws/aws-sdk-php": "2.1.*", + "mockery/mockery": "0.7.2" + }, + "time": "2013-02-15 16:15:55", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/Illuminate/Queue/Pheanstalk" + ], + "files": [ + "src/Illuminate/Support/helpers.php" + ], + "psr-0": { + "Illuminate": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Taylor Otwell", + "email": "taylorotwell@gmail.com" + } + ], + "description": "The Laravel Framework.", + "keywords": [ + "framework", + "laravel" + ] + }, + { + "name": "monolog/monolog", + "version": "1.3.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog", + "reference": "1.3.1" + }, + "dist": { + "type": "zip", + "url": "https://github.com/Seldaek/monolog/archive/1.3.1.zip", + "reference": "1.3.1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": ">=1.0,<2.0" + }, + "require-dev": { + "doctrine/couchdb": "dev-master", + "mlehner/gelf-php": "1.0.*", + "raven/raven": "0.3.*" + }, + "suggest": { + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "mlehner/gelf-php": "Allow sending log messages to a GrayLog2 server", + "raven/raven": "Allow sending log messages to a Sentry server" + }, + "time": "2013-01-11 10:23:20", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.3.x-dev" + } + }, + "autoload": { + "psr-0": { + "Monolog": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be", + "role": "Developer" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ] + }, + { + "name": "patchwork/utf8", + "version": "v1.0.4", + "source": { + "type": "git", + "url": "https://github.com/nicolas-grekas/Patchwork-UTF8.git", + "reference": "v1.0.4" + }, + "dist": { + "type": "zip", + "url": "https://github.com/nicolas-grekas/Patchwork-UTF8/archive/v1.0.4.zip", + "reference": "v1.0.4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2012-12-13 08:48:39", + "type": "library", + "autoload": { + "files": [ + "bootup.utf8.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "(Apache-2.0 or GPL-2.0)" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com", + "role": "Developer" + } + ], + "description": "UTF-8 strings handling for PHP 5.3: portable, performant and extended", + "homepage": "https://github.com/nicolas-grekas/Patchwork-UTF8", + "keywords": [ + "i18n", + "unicode", + "utf-8", + "utf8" + ] + }, + { + "name": "psr/log", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log", + "reference": "1.0.0" + }, + "dist": { + "type": "zip", + "url": "https://github.com/php-fig/log/archive/1.0.0.zip", + "reference": "1.0.0", + "shasum": "" + }, + "time": "2012-12-21 11:40:51", + "type": "library", + "autoload": { + "psr-0": { + "Psr\\Log\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "keywords": [ + "log", + "psr", + "psr-3" + ] + }, + { + "name": "swiftmailer/swiftmailer", + "version": "dev-master", + "source": { + "type": "git", + "url": "git://github.com/swiftmailer/swiftmailer.git", + "reference": "b6bfc8f7f8ae5dac7883885ee323dc3b53ab7d21" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/b6bfc8f7f8ae5dac7883885ee323dc3b53ab7d21", + "reference": "b6bfc8f7f8ae5dac7883885ee323dc3b53ab7d21", + "shasum": "" + }, + "require": { + "php": ">=5.2.4" + }, + "time": "2013-02-04 10:09:01", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Chris Corbyn" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.org", + "keywords": [ + "mail", + "mailer" + ] + }, + { + "name": "symfony/browser-kit", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/BrowserKit", + "source": { + "type": "git", + "url": "https://github.com/symfony/BrowserKit", + "reference": "v2.2.0-RC2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/v2.2.0-RC2", + "reference": "v2.2.0-RC2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "symfony/dom-crawler": ">=2.0,<3.0" + }, + "require-dev": { + "symfony/css-selector": ">=2.0,<3.0", + "symfony/process": ">=2.0,<3.0" + }, + "suggest": { + "symfony/process": "2.2.*" + }, + "time": "2013-02-08 16:10:53", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\BrowserKit\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony BrowserKit Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/console", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/Console", + "source": { + "type": "git", + "url": "https://github.com/symfony/Console", + "reference": "v2.2.0-RC1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Console/zipball/v2.2.0-RC1", + "reference": "v2.2.0-RC1", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-01-24 15:55:08", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Console\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Console Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/css-selector", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/CssSelector", + "source": { + "type": "git", + "url": "https://github.com/symfony/CssSelector", + "reference": "v2.2.0-BETA2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/CssSelector/zipball/v2.2.0-BETA2", + "reference": "v2.2.0-BETA2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-01-17 15:25:59", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\CssSelector\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony CssSelector Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/dom-crawler", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/DomCrawler", + "source": { + "type": "git", + "url": "https://github.com/symfony/DomCrawler", + "reference": "v2.2.0-RC2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/v2.2.0-RC2", + "reference": "v2.2.0-RC2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/css-selector": ">=2.0,<3.0" + }, + "suggest": { + "symfony/css-selector": "2.2.*" + }, + "time": "2013-02-08 16:10:52", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\DomCrawler\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony DomCrawler Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/event-dispatcher", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/EventDispatcher", + "reference": "v2.2.0-RC2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/v2.2.0-RC2", + "reference": "v2.2.0-RC2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/dependency-injection": ">=2.0,<3.0" + }, + "suggest": { + "symfony/dependency-injection": "2.2.*", + "symfony/http-kernel": "2.2.*" + }, + "time": "2013-02-11 11:26:43", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/finder", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/Finder", + "source": { + "type": "git", + "url": "https://github.com/symfony/Finder", + "reference": "v2.2.0-RC1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Finder/zipball/v2.2.0-RC1", + "reference": "v2.2.0-RC1", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-02-04 12:41:13", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Finder\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Finder Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/http-foundation", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/HttpFoundation", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpFoundation", + "reference": "v2.2.0-RC2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/v2.2.0-RC2", + "reference": "v2.2.0-RC2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-02-11 12:46:49", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Symfony/Component/HttpFoundation/Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/http-kernel", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/HttpKernel", + "source": { + "type": "git", + "url": "https://github.com/symfony/HttpKernel", + "reference": "cec254f40ea9baaa2b90de7ca6de236de76823eb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/cec254f40ea9baaa2b90de7ca6de236de76823eb", + "reference": "cec254f40ea9baaa2b90de7ca6de236de76823eb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "psr/log": ">=1.0,<2.0", + "symfony/event-dispatcher": ">=2.1,<3.0", + "symfony/http-foundation": ">=2.2,<2.3-dev" + }, + "require-dev": { + "symfony/browser-kit": "2.2.*", + "symfony/class-loader": ">=2.1,<3.0", + "symfony/config": ">=2.0,<3.0", + "symfony/console": "2.2.*", + "symfony/dependency-injection": ">=2.0,<3.0", + "symfony/finder": ">=2.0,<3.0", + "symfony/process": ">=2.0,<3.0", + "symfony/routing": ">=2.2,<2.3-dev", + "symfony/stopwatch": ">=2.2,<2.3-dev" + }, + "suggest": { + "symfony/browser-kit": "2.2.*", + "symfony/class-loader": "2.2.*", + "symfony/config": "2.2.*", + "symfony/console": "2.2.*", + "symfony/dependency-injection": "2.2.*", + "symfony/finder": "2.2.*" + }, + "time": "2013-02-13 01:35:51", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpKernel\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/process", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/Process", + "source": { + "type": "git", + "url": "https://github.com/symfony/Process", + "reference": "v2.2.0-RC1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Process/zipball/v2.2.0-RC1", + "reference": "v2.2.0-RC1", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "time": "2013-01-31 18:27:04", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Process\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Process Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/routing", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/Routing", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing", + "reference": "v2.2.0-RC2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Routing/zipball/v2.2.0-RC2", + "reference": "v2.2.0-RC2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/common": ">=2.2,<3.0", + "psr/log": ">=1.0,<2.0", + "symfony/config": ">=2.2,<2.3-dev", + "symfony/yaml": ">=2.0,<3.0" + }, + "suggest": { + "doctrine/common": "~2.2", + "symfony/config": "2.2.*", + "symfony/yaml": "2.2.*" + }, + "time": "2013-02-11 11:24:47", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Routing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "http://symfony.com" + }, + { + "name": "symfony/translation", + "version": "2.2.x-dev", + "target-dir": "Symfony/Component/Translation", + "source": { + "type": "git", + "url": "https://github.com/symfony/Translation", + "reference": "v2.2.0-RC2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Translation/zipball/v2.2.0-RC2", + "reference": "v2.2.0-RC2", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/config": ">=2.0,<2.3-dev", + "symfony/yaml": ">=2.2,<3.0" + }, + "suggest": { + "symfony/config": "2.2.*", + "symfony/yaml": "2.2.*" + }, + "time": "2013-02-08 16:10:57", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.2-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "http://symfony.com" + } + ], + "packages-dev": null, + "aliases": [ + + ], + "minimum-stability": "dev", + "stability-flags": [ + + ] +} diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..c42dc4f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./app/tests/ + + + \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..0e85365 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,8 @@ +php_value magic_quotes_gpc Off + + + Options -MultiViews + RewriteEngine On + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^ index.php [L] + \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..030db7d --- /dev/null +++ b/public/index.php @@ -0,0 +1,51 @@ + + */ + +define('LARAVEL_START', microtime(true)); + +/* +|-------------------------------------------------------------------------- +| Register The Auto Loader +|-------------------------------------------------------------------------- +| +| Composer provides a convenient, automatically generated class loader +| for our application. We just need to utilize it! We'll require it +| into the script here so that we do not have to worry about the +| loading of any our classes "manually". Feels great to relax. +| +*/ + +require __DIR__.'/../bootstrap/autoload.php'; + +/* +|-------------------------------------------------------------------------- +| Turn On The Lights +|-------------------------------------------------------------------------- +| +| We need to illuminate PHP development, so let's turn on the lights. +| This bootstrap the framework and gets it ready for use, then it +| will load up this application so that we can run it and send +| the responses back to the browser and delight these users. +| +*/ + +$app = require_once __DIR__.'/../bootstrap/start.php'; + +/* +|-------------------------------------------------------------------------- +| Run The Application +|-------------------------------------------------------------------------- +| +| Once we have the application, we can simply call the run method, +| which will execute the request and send the response back to +| the client's browser allowing them to enjoy the creative +| and wonderful applications we have created for them. +| +*/ + +$app->run(); diff --git a/public/packages/.gitkeep b/public/packages/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..c30190d --- /dev/null +++ b/readme.md @@ -0,0 +1,86 @@ +# Laracon 2013 Demo API + +## Setup Instructions + +1. Make sure you have [Composer](http://getcomposer.org/) installed. I recommend [installing it globally](http://getcomposer.org/doc/00-intro.md#globally). +2. Clone or download this repository. +3. In a terminal, `cd` into this project's directory and run `composer install`. +4. Create a new MySQL database. +5. Update the `mysql` connection options in `app/config/database.php`. +6. Run `php artisan migrate` in the terminal. +7. Run `php artisan db:seed` in the terminal. +8. Start hitting the API in whatever tool you choose. [Postman](https://chrome.google.com/webstore/detail/postman-rest-client/fdmmgilgnpjigdojojpjoooidkmcomcm?utm_source=chrome-ntp-launcher) is a nice Chrome extension, and [HTTPie](https://github.com/jkbr/httpie) is a good command line option. + +## API Documentation + +### Authentication + +The API uses Basic HTTP authentication for all requests. You must pass a valid API key as a username, and the password can literally be anything. + +If you were going to connect via cURL, you would do this: + +`curl -u "youruserapikeygoeshere:whatever" http://localapidomain.dev/v1/lists` + +Note that we sent **whatever** as the password. This can be anything… it doesn't matter. However, since authentication is based on the API key only, you shouldn't share it with anyone. + +### Response Formats + +All responses are in JSON format. + +### Response Codes + +* **200:** The request was successful. +* **201:** The resource was successfully created. +* **204:** The request was successful, but we did not send any content back. +* **400:** The request failed due to an application error, such as a validation error. +* **401:** An API key was either not sent or invalid. +* **403:** The resource does not belong to the authenticated user and is forbidden. +* **404:** The resource was not found. +* **500:** A server error occurred. + +## API Endpoints + +### GET /v1/lists + +Retrieve an array of the authenticated user's tasklists. + +### POST /v1/lists + +Create a new tasklist. Returns status code **201** on success. Accepts the following parameters: + +* **name** – The name of the tasklist. + +### GET /v1/lists/{id} + +Retrieve the tasklist with the given ID. + +### PUT /v1/lists/{id} + +Update the tasklist with the given ID. Accepts the same parameters as **POST /v1/lists**. + +### DELETE /v1/lists/{id} + +Delete the tasklist (and all associated tasks) with the given ID. Returns status code **204** on success. + +### GET /v1/lists/{id}/tasks + +Retrieve tasks for the tasklist with the given ID. + +### POST /v1/lists/{id}/tasks + +Create a new task for the tasklist with the given ID. Returns status code **201** on success. Accepts the following parameters: + +* **description** – The description of the task. +* **completed** – Whether or not the task is completed. A value of **yes**, **y**, **1**, **true**, or **t** will set the task as completed. Anything else will set the task as not completed. + +### GET /v1/lists/{id}/tasks/{taskid} + +Retrieve the task with the given ID. + +### PUT /v1/lists/{id}/tasks/{taskid} + +Update the task with the given ID. Accepts the same parameters as **POST /v1/lists/{id}/tasks**. + +### DELETE /v1/lists/{id}/tasks/{taskid} + +Delete the task with the given ID. Returns status code **204** on success. \ No newline at end of file diff --git a/server.php b/server.php new file mode 100644 index 0000000..5f187f3 --- /dev/null +++ b/server.php @@ -0,0 +1,19 @@ +instance('path', $appPath = __DIR__.'/app'); + +$app->instance('path.base', __DIR__); + +/* +|-------------------------------------------------------------------------- +| Detect The Application Environment +|-------------------------------------------------------------------------- +| +| Laravel takes a dead simple approach to your application environments +| so you can just specify a machine name or HTTP host that matches a +| given environment, then we will automatically detect it for you. +| +*/ + +$env = $app->detectEnvironment(array( + + 'local' => array('localhost', '*.dev', '*.app'), + +)); + +/* +|-------------------------------------------------------------------------- +| Load The Application +|-------------------------------------------------------------------------- +| +| Here we will load the Illuminate application. We'll keep this is in a +| separate location so we can isolate the creation of an application +| from the actual running of the application with a given request. +| +*/ + +require $app->getBootstrapFile(); + +/* +|-------------------------------------------------------------------------- +| Return The Application +|-------------------------------------------------------------------------- +| +| This script returns the application instance. The instance is given to +| the calling script so we can separate the building of the instances +| from the actual running of the application and sending responses. +| +*/ + +return $app; \ No newline at end of file