logo, improved listing, some helpers, mail confirms table, confirmed flag, user title column..
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class ConfirmUser extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'user:confirm {user}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Confirm user\'s e-mail (for testing)';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$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.");
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ class LoginController extends Controller
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/home';
|
||||
protected $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
|
||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Foundation\Auth\RegistersUsers;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class RegisterController extends Controller
|
||||
{
|
||||
@@ -28,7 +29,7 @@ class RegisterController extends Controller
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $redirectTo = '/home';
|
||||
protected $redirectTo = '/';
|
||||
|
||||
/**
|
||||
* Create a new controller instance.
|
||||
@@ -49,9 +50,15 @@ class RegisterController extends Controller
|
||||
protected function validator(array $data)
|
||||
{
|
||||
return Validator::make($data, [
|
||||
'name' => 'required|string|max:255|regex:/^[a-zA-Z0-9_.-]+$/',
|
||||
'name' => [
|
||||
'regex:/^[a-zA-Z0-9_.-]+$/',
|
||||
'required',
|
||||
'string',
|
||||
'max:255',
|
||||
'unique:users'
|
||||
],
|
||||
'email' => 'required|string|email|max:255|unique:users',
|
||||
'password' => 'required|string|min:6|confirmed',
|
||||
'password' => 'required|string|min:6|max:1000|confirmed', // max len to foil DOS attempts
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -65,7 +72,7 @@ class RegisterController extends Controller
|
||||
{
|
||||
return User::create([
|
||||
'name' => $data['name'],
|
||||
'title' => $data['name'], // display name - by default, init to name
|
||||
'title' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
]);
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the application dashboard.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$tables = \Auth::user()->tables()->paginate(10);
|
||||
|
||||
return view('home')->with(compact('tables'));
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ use App\Models\Table;
|
||||
use App\Models\User;
|
||||
use App\Utils\Column;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Validation\Rule;
|
||||
use MightyPork\Exceptions\NotApplicableException;
|
||||
|
||||
class TableController extends Controller
|
||||
@@ -16,7 +17,7 @@ class TableController extends Controller
|
||||
{
|
||||
/** @var Table $tableModel */
|
||||
$tableModel = $user->tables()->where('name', $table)->first();
|
||||
$revision = $tableModel->activeRevision;
|
||||
$revision = $tableModel->revision;
|
||||
|
||||
return view('table.view', [
|
||||
'table' => $tableModel,
|
||||
@@ -54,12 +55,12 @@ class TableController extends Controller
|
||||
$u = \Auth::user();
|
||||
|
||||
$this->validate($request, [
|
||||
'name' => 'required',
|
||||
'title' => 'string',
|
||||
'description' => 'string|nullable',
|
||||
'license' => 'string|nullable',
|
||||
'origin' => 'string|nullable',
|
||||
'columns' => 'required',
|
||||
'name' => 'required|string|max:255',
|
||||
'title' => 'string|string|max:255',
|
||||
'description' => 'string|nullable|max:4000',
|
||||
'license' => 'string|nullable|max:4000',
|
||||
'origin' => 'string|nullable|max:4000',
|
||||
'columns' => 'required|string',
|
||||
'data' => 'string|nullable',
|
||||
]);
|
||||
|
||||
@@ -74,16 +75,24 @@ class TableController extends Controller
|
||||
// Parse and validate the columns specification
|
||||
/** @var Column[] $columns */
|
||||
$columns = [];
|
||||
$column_keys = []; // for checking duplicates
|
||||
$colTable = array_map('str_getcsv', explode("\n", $request->get('columns')));
|
||||
|
||||
// prevent griefing via long list of columns
|
||||
if (count($colTable) > 100) return $this->backWithErrors(['columns' => "Too many columns"]);
|
||||
|
||||
foreach ($colTable as $col) {
|
||||
$col = array_map('trim', $col);
|
||||
if (count($col) < 2) {
|
||||
return $this->backWithErrors([
|
||||
'columns' => "All columns must have at least name and type.",
|
||||
]);
|
||||
if (count($col) < 2 || strlen($col[0])==0) {
|
||||
return $this->backWithErrors(['columns' => "All columns must have at least name and type."]);
|
||||
}
|
||||
|
||||
try {
|
||||
if (in_array($col[0], $column_keys)) {
|
||||
return $this->backWithErrors(['columns' => "Duplicate column: $col[0]"]);
|
||||
}
|
||||
$column_keys[] = $col[0];
|
||||
|
||||
$columns[] = new Column([
|
||||
'name' => $col[0],
|
||||
'type' => $col[1],
|
||||
@@ -93,6 +102,7 @@ class TableController extends Controller
|
||||
return $this->backWithErrors(['columns' => $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
if (count($columns) == 0) return $this->backWithErrors(['columns' => "Define at least one column"]);
|
||||
|
||||
$rowTable = array_map('str_getcsv', explode("\n", $request->get('data')));
|
||||
|
||||
@@ -110,38 +120,42 @@ class TableController extends Controller
|
||||
}
|
||||
return [
|
||||
'data' => json_encode($parsed),
|
||||
'refs' => 1,
|
||||
];
|
||||
}, $rowTable);
|
||||
} catch (\Exception $e) {
|
||||
return $this->backWithErrors(['columns' => $e->getMessage()]);
|
||||
}
|
||||
|
||||
$revision = Revision::create([
|
||||
'refs' => 1, // from the new table
|
||||
$revisionFields = [
|
||||
'note' => "Initial revision of table $u->name/$tabName",
|
||||
'columns' => json_encode($columns),
|
||||
]);
|
||||
'row_count' => count($rowsData),
|
||||
];
|
||||
|
||||
$table = Table::create([
|
||||
$tableFields = [
|
||||
'owner_id' => $u->id,
|
||||
'revision_id' => $revision->id,
|
||||
'revision_id' => 0,
|
||||
'name' => $tabName,
|
||||
'title' => $request->get('title'),
|
||||
'description' => $request->get('description'),
|
||||
'license' => $request->get('license'),
|
||||
'origin' => $request->get('origin'),
|
||||
]);
|
||||
];
|
||||
|
||||
// Attach the revision to the table, set as current
|
||||
$table->revisions()->attach($revision);
|
||||
$table->activeRevision()->associate($revision);
|
||||
\DB::transaction(function () use ($revisionFields, $tableFields, $rowsData) {
|
||||
$revision = Revision::create($revisionFields);
|
||||
|
||||
// Spawn the rows, linked to the revision
|
||||
$revision->rows()->createMany($rowsData);
|
||||
$tableFields['revision_id'] = $revision->id; // to meet the not-null constraint
|
||||
$table = Table::create($tableFields);
|
||||
|
||||
// Now we create rows, a revision pointing to them, and the table using it.
|
||||
// Attach the revision to the table, set as current
|
||||
$table->revisions()->attach($revision);
|
||||
$table->revision()->associate($revision);
|
||||
|
||||
return "Ok.";
|
||||
// Spawn rows, linked to the revision
|
||||
$revision->rows()->createMany($rowsData);
|
||||
});
|
||||
|
||||
return redirect(route('table.view', ['user' => $u, 'table' => $tabName]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* Show the application dashboard.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*/
|
||||
public function view(User $user)
|
||||
{
|
||||
$tables = $user->tables()
|
||||
->with('revision:id,row_count')
|
||||
->paginate(3);
|
||||
|
||||
return view('user.view')->with(compact('tables', 'user'));
|
||||
}
|
||||
}
|
||||
@@ -14,8 +14,8 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* @property int $object_id
|
||||
* @property int $author_id
|
||||
* @property string $message
|
||||
* @property User $author
|
||||
* @property mixed $object - morph
|
||||
* @property-read User $author
|
||||
*/
|
||||
class ContentReport extends Model
|
||||
{
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* E-mail confirmation
|
||||
*
|
||||
* @property int $id
|
||||
* @property \Carbon\Carbon $created_at
|
||||
* @property int $user_id
|
||||
* @property string $token
|
||||
* @property-read User $user
|
||||
*/
|
||||
class EmailConfirmation extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
const UPDATED_AT = null; // disable update timestamp
|
||||
|
||||
/** Authoring user */
|
||||
public function user()
|
||||
{
|
||||
return $this->belongsTo(User::class, 'user_id');
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,10 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* @property int $revision_id
|
||||
* @property int $author_id
|
||||
* @property string $note
|
||||
* @property User $author
|
||||
* @property Table $table
|
||||
* @property Revision $revision
|
||||
* @property object $changes - JSONB
|
||||
* @property-read User $author
|
||||
* @property-read Table $table
|
||||
* @property-read Revision $revision
|
||||
*/
|
||||
class Proposal extends Model
|
||||
{
|
||||
|
||||
@@ -15,10 +15,11 @@ use Riesjart\Relaquent\Model\Concerns\HasRelaquentRelationships;
|
||||
* @property int $ancestor_id
|
||||
* @property string $note
|
||||
* @property object $columns
|
||||
* @property Revision|null $parentRevision
|
||||
* @property Row[]|Collection $rows
|
||||
* @property Proposal|null $sourceProposal - proposal that was used to create this revision
|
||||
* @property Proposal[]|Collection $dependentProposals
|
||||
* @property int $row_count - cached number of rows in the revision
|
||||
* @property-read Revision|null $parentRevision
|
||||
* @property-read Row[]|Collection $rows
|
||||
* @property-read Proposal|null $sourceProposal - proposal that was used to create this revision
|
||||
* @property-read Proposal[]|Collection $dependentProposals
|
||||
*/
|
||||
class Revision extends Model
|
||||
{
|
||||
|
||||
+12
-10
@@ -2,9 +2,11 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Concerns\ManyManyThrough;
|
||||
use App\Models\Concerns\Reportable;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Riesjart\Relaquent\Model\Concerns\HasRelaquentRelationships;
|
||||
|
||||
/**
|
||||
* A data table object (referencing revisions)
|
||||
@@ -20,15 +22,15 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* @property string $description
|
||||
* @property string $license
|
||||
* @property string $origin
|
||||
* @property User $owner
|
||||
* @property Table $parentTable
|
||||
* @property Table[]|Collection $forks
|
||||
* @property Revision[]|Collection $revisions
|
||||
* @property Revision $activeRevision
|
||||
* @property Proposal[]|Collection $proposal
|
||||
* @property TableComment[]|Collection $comments
|
||||
* @property User[]|Collection $favouritingUsers
|
||||
* @property User[]|Collection $discussionFollowers
|
||||
* @property-read User $owner
|
||||
* @property-read Table $parentTable
|
||||
* @property-read Table[]|Collection $forks
|
||||
* @property-read Revision[]|Collection $revisions
|
||||
* @property-read Revision $revision
|
||||
* @property-read Proposal[]|Collection $proposal
|
||||
* @property-read TableComment[]|Collection $comments
|
||||
* @property-read User[]|Collection $favouritingUsers
|
||||
* @property-read User[]|Collection $discussionFollowers
|
||||
*/
|
||||
class Table extends Model
|
||||
{
|
||||
@@ -60,7 +62,7 @@ class Table extends Model
|
||||
}
|
||||
|
||||
/** Active revision */
|
||||
public function activeRevision()
|
||||
public function revision()
|
||||
{
|
||||
return $this->belongsTo(Revision::class, 'revision_id');
|
||||
}
|
||||
|
||||
@@ -16,10 +16,10 @@ use Illuminate\Database\Eloquent\Model;
|
||||
* @property int $author_id
|
||||
* @property int $ancestor_id
|
||||
* @property string $message
|
||||
* @property User $author
|
||||
* @property Table $table
|
||||
* @property TableComment|null $ancestor
|
||||
* @property TableComment[]|Collection $replies
|
||||
* @property-read User $author
|
||||
* @property-read Table $table
|
||||
* @property-read TableComment|null $ancestor
|
||||
* @property-read TableComment[]|Collection $replies
|
||||
*/
|
||||
class TableComment extends Model
|
||||
{
|
||||
|
||||
+44
-16
@@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notification;
|
||||
use MightyPork\Exceptions\ArgumentException;
|
||||
use MightyPork\Exceptions\NotExistException;
|
||||
|
||||
/**
|
||||
* A user in the application
|
||||
@@ -19,16 +21,18 @@ use Illuminate\Notifications\Notification;
|
||||
* @property string $title - for display
|
||||
* @property string $email - unique, for login and social auth chaining
|
||||
* @property string $password - hashed pw
|
||||
* @property Proposal[]|Collection $proposals
|
||||
* @property Table[]|Collection $tables
|
||||
* @property OAuthIdentity[]|Collection $socialIdentities
|
||||
* @property TableComment[]|Collection $tableComments
|
||||
* @property Table[]|Collection $favouriteTables
|
||||
* @property Table[]|Collection $followedDiscussions
|
||||
* @property ContentReport[]|Collection $authoredReports
|
||||
* @property Notification[]|Collection $notifications
|
||||
* @property Notification[]|Collection $readNotifications
|
||||
* @property Notification[]|Collection $unreadNotifications
|
||||
* @property bool $confirmed - user e-mail is confirmed
|
||||
* @property-read Proposal[]|Collection $proposals
|
||||
* @property-read Table[]|Collection $tables
|
||||
* @property-read OAuthIdentity[]|Collection $socialIdentities
|
||||
* @property-read TableComment[]|Collection $tableComments
|
||||
* @property-read Table[]|Collection $favouriteTables
|
||||
* @property-read Table[]|Collection $followedDiscussions
|
||||
* @property-read ContentReport[]|Collection $authoredReports
|
||||
* @property-read Notification[]|Collection $notifications
|
||||
* @property-read Notification[]|Collection $readNotifications
|
||||
* @property-read Notification[]|Collection $unreadNotifications
|
||||
* @property-read string $handle - user handle, including @
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
{
|
||||
@@ -137,12 +141,36 @@ class User extends Authenticatable
|
||||
$this->followedDiscussions()->detach($table);
|
||||
}
|
||||
|
||||
public function fakeTables()
|
||||
/**
|
||||
* Resolve user by a given name, id, or e-mail
|
||||
*
|
||||
* @param string $ident
|
||||
* @return User
|
||||
*/
|
||||
public static function resolve($ident)
|
||||
{
|
||||
return [
|
||||
(object)['title' => 'Table 1'],
|
||||
(object)['title' => 'Table 2'],
|
||||
(object)['title' => 'Table 3']
|
||||
];
|
||||
$ident = "$ident";
|
||||
if (strlen($ident) == 0) throw new ArgumentException("Can't find user by empty string.");
|
||||
|
||||
if (is_numeric($ident)) {
|
||||
$u = static::find((int)$ident);
|
||||
}
|
||||
else if ($ident[0] == '@') {
|
||||
$u = static::where('name', substr($ident, 1))->first();
|
||||
}
|
||||
else {
|
||||
$u = static::where('email', $ident)->first();
|
||||
}
|
||||
|
||||
if (!$u) throw new NotExistException("No user found matching \"$ident\"");
|
||||
|
||||
return $u;
|
||||
}
|
||||
|
||||
public function __get($name)
|
||||
{
|
||||
if ($name == 'handle') return "@$this->name";
|
||||
|
||||
return parent::__get($name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@ class AppServiceProvider extends ServiceProvider
|
||||
\Route::bind('user', function ($value) {
|
||||
$u = User::where('name', $value)->first();
|
||||
// it may also be the _id directly
|
||||
if (!$u) $u = User::find($value);
|
||||
if (!$u && is_numeric($value)) $u = User::find((int)$value);
|
||||
if (!$u) abort(404);
|
||||
return $u;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,11 +53,7 @@ class RouteServiceProvider extends ServiceProvider
|
||||
{
|
||||
Route::middleware('web')
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/public.php'));
|
||||
|
||||
Route::middleware(['web', 'auth'])
|
||||
->namespace($this->namespace)
|
||||
->group(base_path('routes/user.php'));
|
||||
->group(base_path('routes/web.php'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+4
-3
@@ -19,9 +19,10 @@ class Widget
|
||||
{
|
||||
protected $attributesArray = [],
|
||||
$id, $name, $label, $value,
|
||||
$viewName,
|
||||
$help,
|
||||
$labelCols, $fieldCols,
|
||||
$viewName, // view to use inside the form namespace
|
||||
$help, // help button with tooltip
|
||||
$prepend, $append, // input-group (add-ons)
|
||||
$labelCols, $fieldCols, // grid layout setting (label/field cols)
|
||||
$styleArray = [];
|
||||
|
||||
public function __construct($viewName, $name, $label)
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
<?php
|
||||
|
||||
// global helpers
|
||||
function authed() {
|
||||
return ! \Auth::guest();
|
||||
}
|
||||
|
||||
function guest() {
|
||||
return \Auth::guest();
|
||||
}
|
||||
|
||||
function user() {
|
||||
return \Auth::user();
|
||||
}
|
||||
|
||||
function ellipsis($s, $len) {
|
||||
return \MightyPork\Utils\Str::ellipsis($s, $len);
|
||||
}
|
||||
|
||||
function faker() {
|
||||
static $fac = null;
|
||||
if ($fac !== null) return $fac;
|
||||
return $fac = Faker\Factory::create();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user