datatable.directory codebase
				https://datatable.directory/
			
			
		
			You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							183 lines
						
					
					
						
							6.4 KiB
						
					
					
				
			
		
		
	
	
							183 lines
						
					
					
						
							6.4 KiB
						
					
					
				| <?php
 | |
| 
 | |
| namespace App\Http\Controllers;
 | |
| 
 | |
| use App\Models\Revision;
 | |
| use App\Models\Row;
 | |
| 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
 | |
| {
 | |
|     public function view(Request $request, User $user, string $table)
 | |
|     {
 | |
|         $input = $this->validate($request, [
 | |
|             'rev' => 'nullable|int'
 | |
|         ]);
 | |
| 
 | |
|         /** @var Table $tableModel */
 | |
|         $tableModel = $user->tables()->where('name', $table)->first();
 | |
| 
 | |
|         // make it possible to show other revisions
 | |
|         if ($input->has('rev')) {
 | |
|             $rev = (int)$input->rev;
 | |
|             $revision = $tableModel->revisions()->orderBy('created_at')->skip($rev - 1)->first();
 | |
|             if ($revision === null) abort(404, "No such revision");
 | |
|         } else {
 | |
|             $revision = $tableModel->revision;
 | |
|         }
 | |
| 
 | |
|         return view('table.view', [
 | |
|             'table' => $tableModel,
 | |
|             'revision' => $revision,
 | |
|             'columns' => Column::columnsFromJson(json_decode($revision->columns)),
 | |
|             'rows' => $revision->rows()->paginate(25),
 | |
|         ]);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * SHow a form for creating a new table
 | |
|      *
 | |
|      * @return \Illuminate\Http\Response
 | |
|      */
 | |
|     public function create()
 | |
|     {
 | |
|         $exampleColumns =
 | |
|             "latin,string,Latin Name\n" .
 | |
|             "common,string,Common Name\n" .
 | |
|             "lifespan,int,Lifespan (years)";
 | |
| 
 | |
|         $exampleData =
 | |
|             "Mercenaria mercenaria,hard clam,40\n" .
 | |
|             "Magallana gigas,pacific oyster,30\n" .
 | |
|             "Patella vulgata,common limpet,20";
 | |
| 
 | |
|         return view('table.create',
 | |
|             compact('exampleColumns', 'exampleData')
 | |
|         );
 | |
|     }
 | |
| 
 | |
|     public function storeNew(Request $request)
 | |
|     {
 | |
|         /** @var User $u */
 | |
|         $u = \Auth::user();
 | |
| 
 | |
|         $input = $this->validate($request, [
 | |
|             'name' => [
 | |
|                 'required',
 | |
|                 VALI_NAME,
 | |
|                 Rule::unique('tables'),
 | |
|             ],
 | |
|             'title' => ['required', VALI_LINE],
 | |
|             'description' => ['nullable', VALI_TEXT],
 | |
|             'license' => ['nullable', VALI_TEXT],
 | |
|             'origin' => ['nullable', VALI_TEXT],
 | |
|             'columns' => 'required|string',
 | |
|             'data' => 'string|nullable',
 | |
|         ]);
 | |
| 
 | |
|         // Check if table name is unique for user
 | |
|         if ($u->tables()->where('name', $input->name)->exists()) {
 | |
|             return $this->backWithErrors([
 | |
|                 'name' => "A table called \"$input->name\" already exists in your account.",
 | |
|             ]);
 | |
|         }
 | |
| 
 | |
|         // Parse and validate the columns specification
 | |
|         /** @var Column[] $columns */
 | |
|         $columns = [];
 | |
|         $column_keys = []; // for checking duplicates
 | |
|         $colTable = array_map('str_getcsv', explode("\n", $input->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 || 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],
 | |
|                     'title' => count($col) >= 3 ? $col[2] : $col[0], // title falls back to =name if not specified,
 | |
|                 ]);
 | |
|             } catch (\Exception $e) {
 | |
|                 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", $input->data));
 | |
| 
 | |
|         // Preparing data to insert into the Rows table
 | |
|         $rowsData = null;
 | |
|         try {
 | |
|             $rowsData = array_map(function ($row) use ($columns) {
 | |
|                 if (count($row) == 0 || count($row)==1&&$row[0]=='') return null;
 | |
|                 if (count($row) != count($columns)) {
 | |
|                     throw new NotApplicableException("All rows must have " . count($columns) . " fields.");
 | |
|                 }
 | |
|                 $parsed = [];
 | |
|                 foreach ($row as $i => $val) {
 | |
|                     $key = $columns[$i]->name;
 | |
|                     if (strlen($val) > 255) {
 | |
|                         // try to stop people inserting unstructured crap / malformed CSV
 | |
|                         throw new NotApplicableException("Value for column $key too long.");
 | |
|                     }
 | |
|                     $parsed[$key] = $columns[$i]->cast($val);
 | |
|                 }
 | |
|                 return [
 | |
|                     'data' => json_encode($parsed),
 | |
|                 ];
 | |
|             }, $rowTable);
 | |
| 
 | |
|             $rowsData = array_filter($rowsData);
 | |
|         } catch (\Exception $e) {
 | |
|             return $this->backWithErrors(['data' => $e->getMessage()]);
 | |
|         }
 | |
| 
 | |
|         $revisionFields = [
 | |
|             'note' => "Initial revision of table $u->name/$input->name",
 | |
|             'columns' => json_encode($columns),
 | |
|             'row_count' => count($rowsData),
 | |
|         ];
 | |
| 
 | |
|         $tableFields = [
 | |
|             'owner_id' => $u->id,
 | |
|             'revision_id' => 0,
 | |
|             'name' => $input->name,
 | |
|             'title' => $input->title,
 | |
|             'description' => $input->description,
 | |
|             'license' => $input->license,
 | |
|             'origin' => $input->origin,
 | |
|         ];
 | |
| 
 | |
|         \DB::transaction(function () use ($revisionFields, $tableFields, $rowsData) {
 | |
|             $revision = Revision::create($revisionFields);
 | |
| 
 | |
|             $tableFields['revision_id'] = $revision->id; // to meet the not-null constraint
 | |
|             $table = Table::create($tableFields);
 | |
| 
 | |
|             // Attach the revision to the table, set as current
 | |
|             $table->revisions()->attach($revision);
 | |
|             $table->revision()->associate($revision);
 | |
| 
 | |
|             // Spawn rows, linked to the revision
 | |
|             $revision->rows()->createMany($rowsData);
 | |
|         });
 | |
| 
 | |
|         return redirect(route('table.view', ['user' => $u, 'table' => $input->name]));
 | |
|     }
 | |
| }
 | |
| 
 |