Skip to content

Commit

Permalink
Merge pull request #9 from delaynore/feature/attachments
Browse files Browse the repository at this point in the history
Добавление файлов, локализация
  • Loading branch information
delaynore authored May 14, 2024
2 parents 1e26671 + c51e26e commit c022f83
Show file tree
Hide file tree
Showing 20 changed files with 298 additions and 68 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
5. [x] Изменять понятие
6. [x] Изменять родителя понятия
7. [x] Удалять понятие
8. [ ] Загружать файлы
8. [x] Загружать файлы
9. [ ] Отношения между понятиями
10. [x] Атрибуты понятия
11. [x] Ссылки на другие понятия
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace App\Enums;

enum AttacmentType: string
enum AttachmentType: string
{
case IMAGE = 'image';
case VIDEO = 'video';
Expand Down
74 changes: 60 additions & 14 deletions app/Http/Controllers/AttachmentController.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,71 @@
namespace App\Http\Controllers;

use App\Models\Attachment;
use App\Enums\AttachmentType;
use App\Http\Requests\StoreAttachmentRequest;
use App\Http\Requests\UpdateAttachmentRequest;
use App\Models\Concept;
use App\Models\Dictionary;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;

class AttachmentController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
{
//
}

/**
* Show the form for creating a new resource.
*/
public function create()
public function create(Dictionary $dictionary, Concept $concept)
{
//
return view('attachment.create', compact('dictionary', 'concept'));
}

/**
* Store a newly created resource in storage.
*/
public function store(StoreAttachmentRequest $request)
public function store(Request $request, Dictionary $dictionary, Concept $concept)
{
//
Gate::authorize('must-be-owner', $dictionary);

$validated = $request->validate([
'name' => 'sometimes|required|alpha_num|max:255',
'file' => 'required|mimes:png,jpg,jpeg,mp3,mp4|max:10240',
]);

$file = $request->file('file');

$extension = $file->extension();
$fileName = ($validated['name'] ?? $file->getClientOriginalName()) . '.' . $extension;

$path = public_path('storage/attachments/' . $concept->id);
$publicPath = 'attachments/' . $concept->id;
$file->move($path, $fileName);
if ($extension == 'mp3') {
$type = AttachmentType::AUDIO;
} else if ($extension == 'mp4') {
$type = AttachmentType::VIDEO;
} else {
$type = AttachmentType::IMAGE;
}

Storage::setVisibility($path, 'public');

$attachment = Attachment::create([
'name' => $validated['name'] ?? $file->getClientOriginalName(),
'path' => $publicPath . '/' . $fileName,
'type' => $type,
'fk_user_id' => $request->user()->id,
'fk_concept_id' => $concept->id,
]);

$attachment->saveOrFail();

return redirect()->back()
->with(
'success',
__('shared.entity.created', ['entity' => Str::lower(__('entities.attachment.singular'))])
);
}

/**
Expand Down Expand Up @@ -59,8 +97,16 @@ public function update(UpdateAttachmentRequest $request, Attachment $attachment)
/**
* Remove the specified resource from storage.
*/
public function destroy(Attachment $attachment)
public function destroy(Dictionary $dictionary, Concept $concept, Attachment $attachment)
{
//
Gate::authorize('must-be-owner', $attachment->concept->dictionary);

$attachment->deleteOrFail();

return redirect()->back()
->with(
'success',
__('shared.entity.deleted', ['entity' => Str::lower(__('entities.attachment.singular'))])
);
}
}
22 changes: 17 additions & 5 deletions app/Http/Controllers/ConceptController.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ public function index()
public function create(Dictionary $dictionary)
{

if ($dictionary->fk_user_id != auth()->user()->id) {
return abort(404);
}
Gate::authorize("must-be-owner", $dictionary);

if (request()->get('parentId') == null) {
return view('concept.create', compact('dictionary'));
}
Expand All @@ -41,6 +40,8 @@ public function create(Dictionary $dictionary)
*/
public function store(Request $request, string $dictionaryId)
{
$dictionary = Dictionary::findOrFail($dictionaryId);
Gate::authorize('must-be-owner', $dictionary);
$validated = $request->validate([
'name' => ['required', 'max:50', Rule::notIn(Dictionary::find($dictionaryId)->concepts()->pluck('name'))],
'definition' => 'max:1000',
Expand Down Expand Up @@ -75,6 +76,7 @@ public function show(string $dictionary, string $concept)
*/
public function edit(Dictionary $dictionary, Concept $concept)
{
Gate::authorize('must-be-owner', $concept->dictionary);
return view('concept.edit', compact('dictionary', 'concept'));
}

Expand All @@ -83,12 +85,14 @@ public function edit(Dictionary $dictionary, Concept $concept)
*/
public function update(Request $request, Dictionary $dictionary, Concept $concept)
{
Gate::authorize('must-be-owner', $concept->dictionary);

$validated = $request->validate([
'name' => ['required', 'max:50', Rule::notIn($dictionary->concepts->where('id', '!=', $concept->id)->pluck('name'))],
'definition' => 'max:1000',
'parent' => [Rule::excludeIf(empty($request->input('parent'))), 'uuid', Rule::in($dictionary->concepts->pluck('id'))],
]);

if (in_array('parent', $validated)) {
$children = $concept->allChildren();
$searchId = $validated['parent'];
Expand Down Expand Up @@ -124,7 +128,7 @@ public function update(Request $request, Dictionary $dictionary, Concept $concep
public function destroy(Request $request, string $dictionaryId, string $conceptId)
{
$concept = Concept::findOrFail($conceptId);
//Gate::authorize('delete', [Auth::user(), $concept]);
Gate::authorize('must-be-owner', $concept->dictionary);

$concept->deleteOrFail();

Expand All @@ -138,4 +142,12 @@ public function examples(string $dictionary, string $concept)
$dictionary = Dictionary::findOrFail($dictionary);
return view('concept.examples', compact('dictionary', 'concept', 'concepts'));
}

public function attachments(string $dictionary, string $concept){
$concepts = Dictionary::findOrFail($dictionary)->rootConcepts();
$concept = Concept::findOrFail($concept);
$dictionary = Dictionary::findOrFail($dictionary);
$attachments = $concept->attachements()->get();
return view('components.dashboard.tabs.attachments', compact('dictionary', 'concept', 'concepts', 'attachments'));
}
}
20 changes: 9 additions & 11 deletions app/Models/Attachment.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace App\Models;

use App\Enums\AttachmentType;
use App\Enums\DataType;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Concerns\HasUuids;
Expand All @@ -14,10 +15,10 @@
* @property string $name
* @property DataType $type
* @property string $path
* @property string $fkConceptId
* @property string $fkUserId
* @property Carbon $createdAt
* @property Carbon $updatedAt
* @property string $fk_concept_id
* @property string $fk_user_id
* @property Carbon $created_at
* @property Carbon $updated_at
*/
class Attachment extends Model
{
Expand All @@ -37,15 +38,12 @@ public function user(): BelongsTo
protected $fillable = [
'name',
'type',
'fk_concept_id'
'fk_concept_id',
'fk_user_id',
'path',
];

protected $casts = [
'id' => 'uuid',
'name' => 'string',
'type' => DataType::class,
'path' => 'string',
'fk_concept_id' => 'uuid',
'fk_user_id' => 'uuid',
'type' => AttachmentType::class,
];
}
2 changes: 1 addition & 1 deletion app/Models/Concept.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public function dictionary(): BelongsTo

public function attachements(): HasMany
{
return $this->hasMany(Attachment::class);
return $this->hasMany(Attachment::class, 'fk_concept_id');
}

public function attributes(): BelongsToMany
Expand Down
7 changes: 7 additions & 0 deletions app/Providers/AppServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,12 @@ public function boot(): void
return Auth::check();
});
#endregion

#region Concept gates

Gate::define('must-be-owner', function (User $user, Dictionary $dictionary) {
return $user->id === $dictionary->fk_user_id;
});
#endregion
}
}
21 changes: 21 additions & 0 deletions lang/ru/attachment-page.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

return [
'title' => 'Вложения',
'create' => [
'title' => 'Добавление вложения',
'form-title' => 'Добавление вложения для - :concept',
'name' => [
'label' => 'Название вложения',
'placeholder' => 'Если хотите введите название',
],
'file' => [
'label' => 'Выберите файл',
],
'description' => [
'label' => 'Описание',
'placeholder' => 'Опишите на какую тему будет словарь...',
],
],
'available-file-types' => 'JPEG, JPG, PNG, MP3, MP4, максимум 10 МБ',
];
19 changes: 13 additions & 6 deletions lang/ru/dashboard.php
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
<?php

return [
'sidebar' => [
'menu' => [
'export' => 'Экспорт списка понятий',
'create' => 'Создать новое понятие'
]
]
'sidebar' => [
'menu' => [
'export' => 'Экспорт списка понятий',
'create' => 'Создать новое понятие'
],
],
'attachments' => [
'title' => 'Вложения ":concept"',
'header' => 'Вложения ":concept"',
],
'examples' => [
'header' => 'Примеры ":concept"',
],
];
2 changes: 1 addition & 1 deletion lang/ru/dictionary-page.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@
'created_at' => 'Создан',
'actions' => 'Действия',
]
]
],
];
8 changes: 6 additions & 2 deletions lang/ru/entities.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,16 @@
'singular' => 'Атрибут',
'plural' => 'Атрибуты',
'props' => [
'Название',
'Тип данных'
'name' => 'Название',
'type' => 'Тип данных'
],
],
'dictionary' => [
'singular' => 'Словарь',
'plural' => 'Словари',
],
'attachment' => [
'singular' => 'Вложение',
'plural' => 'Вложения',
],
];
3 changes: 3 additions & 0 deletions lang/ru/shared.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,7 @@
'add' => 'Добавить',
'filter' => 'Фильтр',
'link' => 'Ссылка',
'open' => 'Открыть',
'number' => 'No',
'remove' => 'Убрать',
];
49 changes: 49 additions & 0 deletions resources/views/attachment/create.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
@props(['dictionary', 'concept'])

<x-layout.main>
<x-slot name="navigation"></x-slot>
<x-slot:title>{{ __('attachment-page.create.title') }}</x-slot:title>

<div class="flex overflow-y-auto overflow-x-hidden justify-center items-center w-full md:inset-0 h-modal md:h-full">
<div class="relative p-4 w-full max-w-2xl h-full md:h-auto">
<div class="relative p-4 bg-white rounded-lg shadow dark:bg-gray-800 sm:p-5">
<div class="flex justify-between items-center pb-4 mb-4 rounded-t border-b sm:mb-5 dark:border-gray-600">
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">
{{ __('attachment-page.create.form-title', ['concept' => $concept->name]) }}
</h3>
<a href="{{ route('concept.attachments', [$dictionary, $concept]) }}" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 ml-auto inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white">
<svg aria-hidden="true" class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clip-rule="evenodd"></path>
</svg>
<span class="sr-only">{{ __('screen-reader.modal.close')}}</span>
</a>
</div>
<form enctype="multipart/form-data" method="POST" action="{{route('attachment.store', [$dictionary, $concept])}}">
@csrf
<div class="grid gap-4 mb-4">
<div class="col-span-2">
<label for="name" class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">{{ __('attachment-page.create.name.label')}}</label>
<input type="text" name="name" id="name" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="{{ __('attachment-page.create.name.placeholder') }}" value="{{old('name')}}" required="">
<x-input-error :messages="$errors->get('name')" class="mt-2" />
</div>
<div class="col-span-2">

<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white" for="file">{{ __('attachment-page.create.file.label')}}</label>
<input accept=".jpeg,.jpg,.png,.mp3,.mp4" name="file" class="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer bg-gray-50 dark:text-gray-400 focus:outline-none dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400" aria-describedby="file_input_help" id="file" type="file">
<p class="mt-1 text-sm text-gray-500 dark:text-gray-300" id="file_input_help">{{ __('attachment-page.available-file-types') }}</p>

<x-input-error :messages="$errors->get('file')" class="mt-2" />
</div>
</div>
<button type="submit" class="text-white inline-flex items-center bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800">
<svg class="mr-1 -ml-1 w-6 h-6" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd"></path>
</svg>
{{ __('shared.submit.create') }}
</button>
</form>
</div>
</div>
</div>

</x-layout.main>
Loading

0 comments on commit c022f83

Please sign in to comment.