From fabc3ad24e0bdaf9663185a587e2a590b624c905 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Sat, 4 Aug 2018 19:57:59 +0200 Subject: [PATCH] add column unique numbering scheme, more efficient selects --- app/Console/Commands/ConfirmUser.php | 2 - app/Http/Controllers/TableController.php | 116 ++++++++++++------ app/Models/BaseModel.php | 4 +- app/Models/Revision.php | 18 ++- app/Models/Row.php | 47 +++++-- app/Models/User.php | 10 ++ app/Tables/BaseExporter.php | 11 +- app/Tables/CStructArrayExporter.php | 15 +-- app/Tables/Column.php | 47 ++++--- app/Tables/ColumnNumerator.php | 52 ++++++++ app/Tables/CsvExporter.php | 10 +- app/Tables/PhpExporter.php | 2 +- app/Tables/RowNumerator.php | 32 +++++ ...8_01_204822_add_row_sequence_generator.php | 2 +- ...ke_grid_column_mandatory_in_rows_table.php | 2 +- .../2018_08_04_165815_add_column_sequence.php | 30 +++++ porklib/Utils/Utils.php | 22 ++++ porklib/helpers.php | 4 + resources/views/table/_rows.blade.php | 8 +- routes/login.php | 93 +++++++------- tests/Feature/ExampleTest.php | 1 - tests/Unit/ColumnNumeratorTest.php | 72 +++++++++++ tests/Unit/ExampleTest.php | 19 --- 23 files changed, 449 insertions(+), 170 deletions(-) create mode 100644 app/Tables/ColumnNumerator.php create mode 100644 app/Tables/RowNumerator.php create mode 100644 database/migrations/2018_08_04_165815_add_column_sequence.php create mode 100644 tests/Unit/ColumnNumeratorTest.php delete mode 100644 tests/Unit/ExampleTest.php diff --git a/app/Console/Commands/ConfirmUser.php b/app/Console/Commands/ConfirmUser.php index a907e0e..6de9845 100644 --- a/app/Console/Commands/ConfirmUser.php +++ b/app/Console/Commands/ConfirmUser.php @@ -41,7 +41,5 @@ class ConfirmUser extends Command $u = User::resolve($this->argument('user')); $u->update(['confirmed' => true]); $this->info("User #$u->id with e-mail $u->email and handle @$u->name was confirmed."); - - dd($u); } } diff --git a/app/Http/Controllers/TableController.php b/app/Http/Controllers/TableController.php index 0a621c3..8f84bcf 100644 --- a/app/Http/Controllers/TableController.php +++ b/app/Http/Controllers/TableController.php @@ -3,15 +3,16 @@ namespace App\Http\Controllers; use App\Models\Revision; -use App\Models\Row; use App\Models\Table; use App\Models\User; use App\Tables\Column; +use App\Tables\ColumnNumerator; use App\Tables\CStructArrayExporter; use App\Tables\CsvExporter; use App\Tables\CXMacroExporter; use App\Tables\JsonExporter; use App\Tables\PhpExporter; +use App\Tables\RowNumerator; use Illuminate\Http\Request; use Illuminate\Validation\Rule; use MightyPork\Exceptions\NotApplicableException; @@ -41,11 +42,15 @@ class TableController extends Controller $this->countTableVisit($request, $tableModel); + $columns = Column::columnsFromJson($revision->columns); + + $rows = $revision->rowsData($columns)->paginate(25, []); + return view('table.view', [ 'table' => $tableModel, 'revision' => $revision, - 'columns' => Column::columnsFromJson($revision->columns), - 'rows' => $revision->rows()->paginate(25), + 'columns' => $columns, + 'rows' => $rows, ]); } @@ -78,6 +83,7 @@ class TableController extends Controller "Patella vulgata,common limpet,20"; $columns = Column::columnsFromJson([ + // fake 'id' to satisfy the check in Column constructor ['name' => 'latin', 'type' => 'string', 'title' => 'Latin Name'], ['name' => 'common', 'type' => 'string', 'title' => 'Common Name'], ['name' => 'lifespan', 'type' => 'int', 'title' => 'Lifespan (years)'] @@ -90,6 +96,9 @@ class TableController extends Controller ]); } + /** + * Show table settings edit form + */ public function settings(Request $request, User $user, string $table) { /** @var Table $tableModel */ @@ -102,6 +111,9 @@ class TableController extends Controller ]); } + /** + * Store modified table settings + */ public function storeSettings(Request $request, User $user, string $table) { /** @var Table $tableModel */ @@ -149,85 +161,100 @@ class TableController extends Controller ]); // Check if table name is unique for user - if ($u->tables()->where('name', $input->name)->exists()) { + if ($u->hasTable($input->name)) { return $this->backWithErrors([ 'name' => "A table called \"$input->name\" already exists in your account.", ]); } + // --- COLUMNS --- // Parse and validate the columns specification /** @var Column[] $columns */ $columns = []; - $column_keys = []; // for checking duplicates + $col_names = []; // for checking duplicates $colsArray = fromJSON($input->columns); // prevent griefing via long list of columns if (count($colsArray) > 100) return $this->backWithErrors(['columns' => "Too many columns"]); - foreach ($colsArray as $col) { - if (!isset($col->name) || !isset($col->type) || empty($col->name) || empty($col->type)) { - return $this->backWithErrors(['columns' => "All columns must have at least name and type."]); + foreach ($colsArray as $colObj) { + // ensure column has a title + if (!isset($colObj->title)) { + $colObj->title = $colObj->name; } try { - if (in_array($col->name, $column_keys)) { - return $this->backWithErrors(['columns' => "Duplicate column: $col->name"]); + if (in_array($colObj->name, $col_names)) { + return $this->backWithErrors(['columns' => "Duplicate column: $colObj->name"]); } - $column_keys[] = $col->name; - if (!isset($col->title)) $col->title = $col->name; - $columns[] = new Column($col); + $col_names[] = $colObj->name; + $columns[] = new Column($colObj); } catch (\Exception $e) { + // validation errors from the Column constructor return $this->backWithErrors(['columns' => $e->getMessage()]); } } - if (count($columns) == 0) return $this->backWithErrors(['columns' => "Define at least one column"]); + if (count($columns) == 0) { + return $this->backWithErrors(['columns' => "Define at least one column"]); + } + + // Now assign column IDs + $columnNumerator = new ColumnNumerator(count($columns)); + foreach ($columns as $column) { + $column->setID($columnNumerator->next()); + } - $rowTable = array_map('str_getcsv', explode("\n", $input->data)); + // --- DATA --- + $dataCsvLines = array_map('str_getcsv', explode("\n", $input->data)); // Preparing data to insert into the Rows table - $rowsData = null; + $rowsToInsert = null; + $rowNumerator = null; try { - $grid_range = Row::allocateGRIDs(count($rowTable)); - $grid_cnt = $grid_range[0]; - - $rowsData = array_map(function ($row) use ($columns, &$grid_cnt) { - if (count($row) == 0 || count($row) == 1 && $row[0] == '') return null; + $rowsToInsert = collect($dataCsvLines)->map(function ($row) use ($columns) { + if (count($row) == 0 || count($row) == 1 && $row[0] == '') return null; // discard empty lines if (count($row) != count($columns)) { throw new NotApplicableException("All rows must have " . count($columns) . " fields."); } - $parsed = [ - '_grid' => $grid_cnt++ - ]; + $data = []; foreach ($row as $i => $val) { - $key = $columns[$i]->name; - if (strlen($val) > 255) { + $col = $columns[$i]; + + if (strlen($val) > 1000) { // try to stop people inserting unstructured crap / malformed CSV - throw new NotApplicableException("Value for column $key too long."); + throw new NotApplicableException("Value for column {$col->name} too long."); } - $parsed[$key] = $columns[$i]->cast($val); + $data[$col->id] = $col->cast($val); } - return [ - 'data' => $parsed, - ]; - }, $rowTable); - $rowsData = array_filter($rowsData); + // return in a format prepared for filling eloquent + return ['data' => $data]; + })->filter()->all(); // remove nulls, to array + + // attach _id labels to all rows + $rowNumerator = new RowNumerator(count($dataCsvLines)); + foreach ($rowsToInsert as &$item) { + $item['data']['_id'] = $rowNumerator->next(); + } } catch (\Exception $e) { return $this->backWithErrors(['data' => $e->getMessage()]); } + // --- STORE TO DB --- + + /* Fields for the new Revision instance */ $revisionFields = [ 'note' => "Initial revision of table $u->name/$input->name", 'columns' => $columns, - 'row_count' => count($rowsData), + 'row_count' => count($rowsToInsert), ]; + /* Fields for the new Table instance */ $tableFields = [ 'owner_id' => $u->id, - 'revision_id' => 0, 'name' => $input->name, 'title' => $input->title, 'description' => $input->description, @@ -235,10 +262,12 @@ class TableController extends Controller 'origin' => $input->origin, ]; - \DB::transaction(function () use ($revisionFields, $tableFields, $rowsData) { + /** @var Table $table */ + $table = null; + \DB::transaction(function () use ($revisionFields, $tableFields, $rowsToInsert, &$table) { $revision = Revision::create($revisionFields); - $tableFields['revision_id'] = $revision->id; // to meet the not-null constraint + $tableFields['revision_id'] = $revision->id; // current revision (not-null constraint on this FK) $table = Table::create($tableFields); // Attach the revision to the table, set as current @@ -246,10 +275,10 @@ class TableController extends Controller $table->revision()->associate($revision); // Spawn rows, linked to the revision - $revision->rows()->createMany($rowsData); + $revision->rows()->createMany($rowsToInsert); }); - return redirect(route('table.view', ['user' => $u, 'table' => $input->name])); + return redirect($table->viewRoute); } /** @@ -260,7 +289,7 @@ class TableController extends Controller */ private function countTableVisit(Request $request, Table $table) { - $cookieName = "view_$table->id"; + $cookieName = "view_{$table->owner->name}_{$table->name}"; if (!$request->cookie($cookieName, false)) { $ua = $request->userAgent(); // Filter out suspicious user agents @@ -271,6 +300,13 @@ class TableController extends Controller } } + /** + * Simple export via a preset + * + * @param Request $request + * @param User $user + * @param string $table + */ public function export(Request $request, User $user, string $table) { /** @var Table $tableModel */ diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index ccd53d4..8561357 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -10,7 +10,7 @@ class BaseModel extends Model { public function getAttribute($key) { - if (! $key) { + if ($this->exists && ! $key) { throw new \LogicException("No attribute ".var_export($key, true)); } @@ -25,7 +25,7 @@ class BaseModel extends Model */ public function getRelationValue($key) { - if (!method_exists($this, $key)) { + if ($this->exists && !method_exists($this, $key)) { throw new \LogicException("No attribute or relation ".var_export($key, true)); } diff --git a/app/Models/Revision.php b/app/Models/Revision.php index 219e6af..b140623 100644 --- a/app/Models/Revision.php +++ b/app/Models/Revision.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Tables\Column; use Illuminate\Support\Collection; use Riesjart\Relaquent\Model\Concerns\HasRelaquentRelationships; @@ -32,7 +33,22 @@ class Revision extends BaseModel /** Included rows */ public function rows() { - return $this->belongsToMany(Row::class, 'revision_row_pivot'); + return $this->belongsToMany(Row::class, 'revision_row_pivot')->as('_row_pivot'); + } + + /** Included rows + * @param Column[] $columns + * @return \Illuminate\Database\Query\Builder|static + */ + public function rowsData($columns, $withId=true) + { + $selects = $withId ? ["data->>'_id' as _id"] : []; + + foreach ($columns as $col) { + $selects[] = "data->>'$col->id' as $col->name"; + } + + return $this->rows()->select([])->selectRaw(implode(', ', $selects)); } /** Proposal that lead to this revision */ diff --git a/app/Models/Row.php b/app/Models/Row.php index 2ef2c63..1b7220c 100644 --- a/app/Models/Row.php +++ b/app/Models/Row.php @@ -6,7 +6,7 @@ namespace App\Models; * Row in a data table * * @property int $id - * @property string $data - JSONB, always containing _grid + * @property string $data - JSONB, always containing _id */ class Row extends BaseModel { @@ -17,12 +17,12 @@ class Row extends BaseModel protected $guarded = []; public $timestamps = false; - public function getGridAttribute() { - return $this->data->_grid; + public function getRowIdAttribute() { + return $this->data->_id; } - public function setGridAttribute($value) { - $this->data->_grid = $value; + public function setRowIdAttribute($value) { + $this->data->_id = $value; } /** @@ -34,21 +34,48 @@ class Row extends BaseModel * * @return int */ - public static function allocateGRID() + public static function allocateRowID() { - return \DB::selectOne("SELECT nextval('global_row_id_seq') AS grid;")->grid; + return \DB::selectOne("SELECT nextval('global_row_id_seq') AS rid;")->rid; } /** * Allocate a block of Global Row IDs, application-unique. * - * @see Row::allocateGRID() + * @see Row::allocateRowID() * * @return int[] first and last */ - public static function allocateGRIDs($count) + public static function allocateRowIDs($count) { - $last = \DB::selectOne("SELECT multi_nextval('global_row_id_seq', ?) AS last_grid;", [$count])->last_grid; + $last = \DB::selectOne("SELECT multi_nextval('global_row_id_seq', ?) AS last_id;", [$count])->last_id; + return [$last - $count + 1, $last]; + } + + /** + * Allocate a single Global Column ID, application-unique. + * + * GCIDs are used to uniquely identify existing or proposed new columns, + * and are preserved after column modifications, to ensure change proposals have + * a clear target. This is the column equivalent of GRID. + * + * @return int + */ + public static function allocateColID() + { + return \DB::selectOne("SELECT nextval('global_column_id_seq') AS cid;")->cid; + } + + /** + * Allocate a block of Global Column IDs, application-unique. + * + * @see Row::allocateColID() + * + * @return int[] first and last + */ + public static function allocateColIDs($count) + { + $last = \DB::selectOne("SELECT multi_nextval('global_column_id_seq', ?) AS last_id;", [$count])->last_id; return [$last - $count + 1, $last]; } } diff --git a/app/Models/User.php b/app/Models/User.php index ccc61ef..d661bbe 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -226,4 +226,14 @@ class User extends BaseModel implements $this->notify(new ConfirmEmail($newEmail, $confirmation->token)); } + + /** + * Check if this user has a table with the givenname + * + * @param string $name + */ + public function hasTable(string $name) + { + $this->tables()->where('name', $name)->exists(); + } } diff --git a/app/Tables/BaseExporter.php b/app/Tables/BaseExporter.php index 38daa00..6671d0e 100644 --- a/app/Tables/BaseExporter.php +++ b/app/Tables/BaseExporter.php @@ -128,14 +128,13 @@ abstract class BaseExporter $start = 0; while ($start < $count) { - $rows = $revision->rows()->offset($start)->limit($chunkSize)->get(); + // TODO raw query to allow selecting aggregates, column subsets, etc + $rows = $revision->rowsData($this->columns, false) + ->offset($start)->limit($chunkSize)->get()->toArray(); foreach ($rows as $row) { - $data = $row->data; - - // column renaming, value formatting... - - yield $data; + unset($row['_row_pivot']); + yield $row; } $start += $chunkSize; diff --git a/app/Tables/CStructArrayExporter.php b/app/Tables/CStructArrayExporter.php index 282a0f9..ac2d3a4 100644 --- a/app/Tables/CStructArrayExporter.php +++ b/app/Tables/CStructArrayExporter.php @@ -99,20 +99,7 @@ class CStructArrayExporter extends BaseExporter echo ","; } - $val = 0; - switch ($field->type) { - case 'string': - $val = ""; - break; - - case 'bool': - $val = false; - break; - } - - if (isset($row->{$field->name})) { - $val = $row->{$field->name}; - } + $val = $row[$field->name]; // export to C format switch ($field->type) { diff --git a/app/Tables/Column.php b/app/Tables/Column.php index 1b32bc3..7be9660 100644 --- a/app/Tables/Column.php +++ b/app/Tables/Column.php @@ -11,9 +11,10 @@ use MightyPork\Utils\Utils; /** * Helper class representing one column in a data table. * + * @property-read string $id + * @property-read string $type * @property-read string $name * @property-read string $title - * @property-read string $type */ class Column implements JsonSerializable { @@ -21,9 +22,10 @@ class Column implements JsonSerializable 'int', 'bool', 'float', 'string' ]; + private $id; + private $type; private $name; private $title; - private $type; public static function columnsFromJson($columns) { @@ -36,13 +38,13 @@ class Column implements JsonSerializable }, $columns); } - public function __get($name) + /** + * Set column ID + * @param string $id - GCID + */ + public function setID($id) { - if (property_exists($this, $name)) { - return $this->$name; - } - - throw new NotApplicableException("No such column property"); + $this->id = $id; } /** @@ -52,18 +54,32 @@ class Column implements JsonSerializable */ public function __construct($obj) { - $b = new \objBag($obj); + $b = objBag($obj); + + if (!$b->has('name')) throw new NotApplicableException('Missing name in column'); + if (!$b->has('type')) throw new NotApplicableException('Missing type in column'); + + if ($b->name[0] == '_') { // global row ID + throw new NotApplicableException("Column name can't start with underscore."); + } + + if (!in_array($b->type, self::colTypes)) { + throw new NotApplicableException("\"$b->type\" is not a valid column type."); + } + + $this->id = $b->get('id', null); $this->name = $b->name; - $this->title = $b->title; $this->type = $b->type; + $this->title = $b->title ?: $b->name; + } - if ($this->name == '_grid') { // global row ID - throw new NotApplicableException("_grid is a reserved column name."); + public function __get($name) + { + if (property_exists($this, $name)) { + return $this->$name; } - if (!in_array($this->type, self::colTypes)) { - throw new NotApplicableException("\"$this->type\" is not a valid column type."); - } + throw new NotApplicableException("No such column property: $name"); } /** @@ -72,6 +88,7 @@ class Column implements JsonSerializable public function toArray() { return [ + 'id' => $this->id, 'name' => $this->name, 'title' => $this->title, 'type' => $this->type, diff --git a/app/Tables/ColumnNumerator.php b/app/Tables/ColumnNumerator.php new file mode 100644 index 0000000..e053192 --- /dev/null +++ b/app/Tables/ColumnNumerator.php @@ -0,0 +1,52 @@ +next, $this->last) = Row::allocateColIDs($capacity); + } + + /** + * Get next column name, incrementing the internal state + * + * @return string + */ + public function next() + { + if ($this->next > $this->last) + throw new \OutOfBoundsException("Column numerator has run out of allocated GCID slots"); + + $key = Utils::alphabetEncode($this->next, self::ALPHABET); + $this->next++; + return $key; + } +} diff --git a/app/Tables/CsvExporter.php b/app/Tables/CsvExporter.php index 62394f4..a6601d4 100644 --- a/app/Tables/CsvExporter.php +++ b/app/Tables/CsvExporter.php @@ -44,15 +44,7 @@ class CsvExporter extends BaseExporter fputcsv($handle, $columnNames, $this->delimiter); foreach ($this->iterateRows() as $row) { - $items = []; - foreach ($this->columns as $column) { - if (isset($row->{$column->name})) { - $items[] = $row->{$column->name}; - } else { - $items[] = null; - } - } - fputcsv($handle, $items, $this->delimiter); + fputcsv($handle, array_values($row), $this->delimiter); } fclose($handle); diff --git a/app/Tables/PhpExporter.php b/app/Tables/PhpExporter.php index f7a090b..a9a5160 100644 --- a/app/Tables/PhpExporter.php +++ b/app/Tables/PhpExporter.php @@ -53,7 +53,7 @@ class PhpExporter extends BaseExporter var_export($column->name); echo ' => '; - var_export($row->{$column->name}); + var_export($row[$column->name]); } echo ']'; diff --git a/app/Tables/RowNumerator.php b/app/Tables/RowNumerator.php new file mode 100644 index 0000000..dde4484 --- /dev/null +++ b/app/Tables/RowNumerator.php @@ -0,0 +1,32 @@ +next, $this->last) = Row::allocateRowIDs($capacity); + } + + public function next() + { + if ($this->next > $this->last) + throw new \OutOfBoundsException("Row numerator has run out of allocated GRID slots"); + + return $this->next++; + } +} diff --git a/database/migrations/2018_08_01_204822_add_row_sequence_generator.php b/database/migrations/2018_08_01_204822_add_row_sequence_generator.php index 6493881..4164f41 100644 --- a/database/migrations/2018_08_01_204822_add_row_sequence_generator.php +++ b/database/migrations/2018_08_01_204822_add_row_sequence_generator.php @@ -30,7 +30,7 @@ END; $$ LANGUAGE 'plpgsql'; PGSQL ); - DB::unprepared('CREATE SEQUENCE global_row_id_seq START 0 MINVALUE 0;'); + DB::unprepared('CREATE SEQUENCE IF NOT EXISTS global_row_id_seq START 0 MINVALUE 0;'); // We have to increment manually once before the above function can be used - that is because // nextval will return the initial value the first time it's called, so it would not advance by // the given step at all. This would give us negative values - not a problem in postgres without unsigned diff --git a/database/migrations/2018_08_01_213353_make_grid_column_mandatory_in_rows_table.php b/database/migrations/2018_08_01_213353_make_grid_column_mandatory_in_rows_table.php index 450f99c..37b1cf7 100644 --- a/database/migrations/2018_08_01_213353_make_grid_column_mandatory_in_rows_table.php +++ b/database/migrations/2018_08_01_213353_make_grid_column_mandatory_in_rows_table.php @@ -13,7 +13,7 @@ class MakeGridColumnMandatoryInRowsTable extends Migration */ public function up() { - DB::unprepared("ALTER TABLE rows ADD CONSTRAINT grid_must_exist CHECK (data ? '_grid');"); + DB::unprepared("ALTER TABLE rows ADD CONSTRAINT grid_must_exist CHECK (data ? '_id');"); } /** diff --git a/database/migrations/2018_08_04_165815_add_column_sequence.php b/database/migrations/2018_08_04_165815_add_column_sequence.php new file mode 100644 index 0000000..3335097 --- /dev/null +++ b/database/migrations/2018_08_04_165815_add_column_sequence.php @@ -0,0 +1,30 @@ += 0); + + return $key; + } } diff --git a/porklib/helpers.php b/porklib/helpers.php index a88be7c..8134d80 100644 --- a/porklib/helpers.php +++ b/porklib/helpers.php @@ -166,6 +166,10 @@ class objBag implements JsonSerializable, ArrayAccess { } } +/** + * @param $obj + * @return objBag + */ function objBag($obj) { return new \objBag($obj); } diff --git a/resources/views/table/_rows.blade.php b/resources/views/table/_rows.blade.php index 019a4f4..e71908a 100644 --- a/resources/views/table/_rows.blade.php +++ b/resources/views/table/_rows.blade.php @@ -10,18 +10,18 @@ - + @foreach($columns as $col) - + @endforeach @foreach($rows as $row) - + @foreach($columns as $col) - + @endforeach @endforeach diff --git a/routes/login.php b/routes/login.php index 9448913..b179720 100644 --- a/routes/login.php +++ b/routes/login.php @@ -16,58 +16,63 @@ Route::get('/auth/resend-email-confirmation', 'Auth\ConfirmEmailController@resen // ----------------- SOCIAL LOGIN -------------------- -function _loginVia($method) { - $wasLoggedIn = !guest(); - - try { - SocialAuth::login($method, function (User $user, ProviderUser $details) use($wasLoggedIn) { - if ($user->exists && !$wasLoggedIn) { - // check if this identity already existed - if (! $user->socialIdentities() - ->where('provider', $details->provider) - ->where('provider_user_id', $details->id) - ->exists()) { - Auth::logout(); - - abort(403, - "Account with this e-mail already exists. Add the identity +if (!function_exists('_loginVia')) { + function _loginVia($method) + { + $wasLoggedIn = !guest(); + + try { + SocialAuth::login($method, function (User $user, ProviderUser $details) use ($wasLoggedIn) { + if ($user->exists && !$wasLoggedIn) { + // check if this identity already existed + if (!$user->socialIdentities() + ->where('provider', $details->provider) + ->where('provider_user_id', $details->id) + ->exists()) { + Auth::logout(); + + abort(403, + "Account with this e-mail already exists. Add the identity to the account manually after logging in through an existing authentication method."); + } } - } - // update user name first time user logs in - if (!$user->exists) { - if (!config('app.allow_regs')) { - abort(403, "Registrations are closed."); + // update user name first time user logs in + if (!$user->exists) { + if (!config('app.allow_regs')) { + abort(403, "Registrations are closed."); + } + + $basename = $details->nickname ?: ($details->full_name ?: $details->email); + $user->name = $basename; + $cnt = 1; + while (User::where('name', $user->name)->exists()) { + $cnt++; + $user->name = $basename . $cnt; + } + + $user->title = $basename; } - $basename = $details->nickname ?: ($details->full_name ?: $details->email); - $user->name = $basename; - $cnt = 1; - while (User::where('name', $user->name)->exists()) { - $cnt++; - $user->name = $basename . $cnt; + // set e-mail from provider data, only if user e-mail is empty + if ("$user->email" === "") { + $user->email = $details->email; } - } - - // set e-mail from provider data, only if user e-mail is empty - if ("$user->email" === "") { - $user->email = $details->email; - } - $user->confirmed = true; // mark as confirmed, we trust the provider - }); - } catch (ApplicationRejectedException $e) { - abort(401, $e->getMessage()); - } catch (InvalidAuthorizationCodeException $e) { - abort(401, $e->getMessage()); + $user->confirmed = true; // mark as confirmed, we trust the provider + }); + } catch (ApplicationRejectedException $e) { + abort(401, $e->getMessage()); + } catch (InvalidAuthorizationCodeException $e) { + abort(401, $e->getMessage()); + } + + if ($wasLoggedIn) + return redirect(route('profile.manage-oauth')); + else + return Redirect::intended(); } - - if ($wasLoggedIn) - return redirect(route('profile.manage-oauth')); - else - return Redirect::intended(); -}; +} Route::get('/auth/github/authorize', function() { diff --git a/tests/Feature/ExampleTest.php b/tests/Feature/ExampleTest.php index f31e495..eeea1d6 100644 --- a/tests/Feature/ExampleTest.php +++ b/tests/Feature/ExampleTest.php @@ -15,7 +15,6 @@ class ExampleTest extends TestCase public function testBasicTest() { $response = $this->get('/'); - $response->assertStatus(200); } } diff --git a/tests/Unit/ColumnNumeratorTest.php b/tests/Unit/ColumnNumeratorTest.php new file mode 100644 index 0000000..8a45671 --- /dev/null +++ b/tests/Unit/ColumnNumeratorTest.php @@ -0,0 +1,72 @@ +next()); + self::assertEquals('b', $n->next()); + self::assertEquals('c', $n->next()); + } + + public function testIncrementsWithCustomInit() + { + $n = new ColumnNumerator('r'); + + self::assertEquals('r', $n->next()); + self::assertEquals('s', $n->next()); + self::assertEquals('t', $n->next()); + } + + public function testIncrementsMultiChar() + { + $n = new ColumnNumerator('aaa'); + + self::assertEquals('aaa', $n->next()); + self::assertEquals('aab', $n->next()); + self::assertEquals('aac', $n->next()); + } + + public function testOverflowGrow() + { + $n = new ColumnNumerator('y'); + + self::assertEquals('y', $n->next()); + self::assertEquals('z', $n->next()); + self::assertEquals('aa', $n->next()); + self::assertEquals('ab', $n->next()); + } + + public function testOverflowNoGrow() + { + $n = new ColumnNumerator('yy'); + + self::assertEquals('yy', $n->next()); + self::assertEquals('yz', $n->next()); + self::assertEquals('za', $n->next()); + self::assertEquals('zb', $n->next()); + } + + public function testResume() + { + $n = new ColumnNumerator('yy'); + self::assertEquals('yy', $n->next()); + self::assertEquals('yz', $n->next()); + + $n2 = new ColumnNumerator($n->getState()); + unset($n); + + self::assertEquals('za', $n2->next(), 'recreated numerator continues'); + self::assertEquals('zb', $n2->next()); + + self::assertEquals($n2->getState(), 'zc', 'State is next string'); + } +} diff --git a/tests/Unit/ExampleTest.php b/tests/Unit/ExampleTest.php deleted file mode 100644 index e9fe19c..0000000 --- a/tests/Unit/ExampleTest.php +++ /dev/null @@ -1,19 +0,0 @@ -assertTrue(true); - } -}
#GRID_id{{ $col->title }}{{$col->name}} ("{{ $col->title }}") [ {{$col->id}} ]
{{ $row->data->_grid }}{{ $row->_id }}{{ $row->data->{$col->name} }}{{ $row->{$col->name} }}