diff --git a/app/Http/Controllers/AttributeController.php b/app/Http/Controllers/AttributeController.php index 6501094..394bfd3 100644 --- a/app/Http/Controllers/AttributeController.php +++ b/app/Http/Controllers/AttributeController.php @@ -7,6 +7,7 @@ use App\Http\Requests\StoreAttributeRequest; use App\Http\Requests\UpdateAttributeRequest; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Gate; use Illuminate\Validation\Rule; use Illuminate\Validation\Rules\Enum; @@ -61,6 +62,7 @@ public function store(Request $request) */ public function edit(Attribute $attribute) { + Gate::authorize('redactor'); return view('attribute.edit', compact('attribute')); } @@ -69,6 +71,8 @@ public function edit(Attribute $attribute) */ public function update(Request $request, Attribute $attribute) { + Gate::authorize('redactor'); + $validated = $request->validate([ 'name' => ['required', 'max:255', Rule::unique('attributes')->ignore($attribute->id)], 'type' => [new Enum(DataType::class)], @@ -83,6 +87,8 @@ public function update(Request $request, Attribute $attribute) */ public function destroy(Attribute $attribute) { + Gate::authorize('admin'); + if ($attribute->concepts()->count() > 0) { return redirect()->back()->with('attribute.delete.error', 'Атрибут не может быть удален, так как он используется в словарях. Удалите его из словарей'); } diff --git a/app/Http/Controllers/ConceptController.php b/app/Http/Controllers/ConceptController.php index e4a15b2..64a442e 100644 --- a/app/Http/Controllers/ConceptController.php +++ b/app/Http/Controllers/ConceptController.php @@ -3,9 +3,13 @@ namespace App\Http\Controllers; use App\Models\Concept; +use App\Models\ConceptRelation; use App\Models\Dictionary; +use App\Models\RelationType; +use Illuminate\Database\Eloquent\Relations\Relation; use Illuminate\Http\Request; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Gate; use Illuminate\Validation\Rule; @@ -68,7 +72,49 @@ public function show(string $dictionary, string $concept) $concept = Concept::findOrFail($concept); $dictionary = Dictionary::findOrFail($dictionary); $concepts = $dictionary->rootConcepts(); - return view('concept.show', compact('concept', 'dictionary', 'concepts')); + + $query = ConceptRelation::where('fk_concept_1_id', $concept->id)->orWhere('fk_concept_2_id', $concept->id); + $conceptRelationTypes = $query->distinct('fk_relation_type_id')->get('fk_relation_type_id')->pluck('fk_relation_type_id')->reverse(); + + $conceptRelations = []; + + foreach ($conceptRelationTypes as $conceptRelationType) { + + $relatedConcepts1 = DB::table('concept_relations') + ->join('concepts as concept_1', 'concept_relations.fk_concept_1_id', '=', 'concept_1.id') + ->join('concepts as concept_2', 'concept_relations.fk_concept_2_id', '=', 'concept_2.id') + ->join('relation_types', 'concept_relations.fk_relation_type_id', '=', 'relation_types.id') + ->select( + 'concept_relations.id as relation_id', + 'concept_2.id AS concept_id', + 'concept_2.name AS concept_name' + ) + ->where('concept_1.id', '=', $concept->id) + ->where('concept_relations.fk_relation_type_id', '=', $conceptRelationType) + ->get(); + + $relatedConcepts2 = DB::table('concept_relations') + ->join('concepts as concept_1', 'concept_relations.fk_concept_1_id', '=', 'concept_1.id') + ->join('concepts as concept_2', 'concept_relations.fk_concept_2_id', '=', 'concept_2.id') + ->join('relation_types', 'concept_relations.fk_relation_type_id', '=', 'relation_types.id') + ->select( + 'concept_relations.id as relation_id', + 'concept_1.id AS concept_id', + 'concept_1.name AS concept_name' + ) + ->where('concept_2.id', '=', $concept->id) + ->where('concept_relations.fk_relation_type_id', '=', $conceptRelationType) + ->get(); + + + $merged = array_merge($relatedConcepts1->toArray(), $relatedConcepts2->toArray()); + array_push($conceptRelations, [ + RelationType::find($conceptRelationType), + $merged + ]); + } + + return view('concept.show', compact('concept', 'dictionary', 'concepts', 'conceptRelations')); } /** @@ -143,7 +189,8 @@ public function examples(string $dictionary, string $concept) return view('concept.examples', compact('dictionary', 'concept', 'concepts')); } - public function attachments(string $dictionary, string $concept){ + public function attachments(string $dictionary, string $concept) + { $concepts = Dictionary::findOrFail($dictionary)->rootConcepts(); $concept = Concept::findOrFail($concept); $dictionary = Dictionary::findOrFail($dictionary); diff --git a/app/Http/Controllers/ConceptRelationController.php b/app/Http/Controllers/ConceptRelationController.php index 2c2cfa9..1c047dd 100644 --- a/app/Http/Controllers/ConceptRelationController.php +++ b/app/Http/Controllers/ConceptRelationController.php @@ -2,34 +2,50 @@ namespace App\Http\Controllers; +use App\Models\Concept; use App\Models\ConceptRelation; -use App\Http\Requests\StoreConceptRelationRequest; -use App\Http\Requests\UpdateConceptRelationRequest; +use App\Models\Dictionary; +use App\Models\RelationType; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Gate; +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; class ConceptRelationController 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) { - // + $relationTypes = RelationType::all(); + $concepts = $dictionary->concepts()->orderBy('name')->get(); + return view('concept-relation.create', compact('dictionary','concept', 'relationTypes', 'concepts')); } /** * Store a newly created resource in storage. */ - public function store(StoreConceptRelationRequest $request) + public function store(Request $request, Dictionary $dictionary, Concept $concept) { - // + $validated = $request->validate([ + 'fk_concept_1_id' => 'required|uuid|exists:concepts,id', + 'fk_concept_2_id' => 'required|uuid|exists:concepts,id|different:fk_concept_1_id', + 'fk_relation_type_id'=> 'required|uuid|exists:relation_types,id', + ]); + $sameRelation = ConceptRelation::where('fk_relation_type_id', $validated['fk_relation_type_id'])->where('fk_concept_1_id', $validated['fk_concept_1_id']) + ->where('fk_concept_2_id', $validated['fk_concept_2_id']) + ->orWhere('fk_concept_2_id', $validated['fk_concept_1_id'])->where('fk_concept_1_id', $validated['fk_concept_2_id']); + if ($sameRelation->count() > 0) { + $messages = ['max' => __('dashboard.relation.create.errors.exist')]; + Validator::validate($request->all(), [ + 'fk_relation_type_id' => 'max:0' + ], $messages); + } + ConceptRelation::create($validated); + + return redirect()->route('concept.show', compact('dictionary', 'concept'))->with('success', __('dashboard.relation.messages.created')); } /** @@ -43,24 +59,58 @@ public function show(ConceptRelation $conceptRelation) /** * Show the form for editing the specified resource. */ - public function edit(ConceptRelation $conceptRelation) + public function edit(string $dictionary, string $concept, string $conceptRelation) { - // + $dictionary = Dictionary::findOrFail($dictionary); + Gate::authorize('must-be-owner', $dictionary); + + $relationTypes = RelationType::all(); + $concepts = $dictionary->concepts()->orderBy('name')->get(); + $conceptRelation = ConceptRelation::findOrFail($conceptRelation); + + return view('concept-relation.edit', compact('dictionary', 'concept', 'conceptRelation', 'relationTypes', 'concepts')); } /** * Update the specified resource in storage. */ - public function update(UpdateConceptRelationRequest $request, ConceptRelation $conceptRelation) + public function update(Request $request, string $dictionary, string $concept, string $conceptRelation) { - // + $dictionary = Dictionary::findOrFail($dictionary); + Gate::authorize('must-be-owner', $dictionary); + + $validated = $request->validate([ + 'fk_concept_1_id' => 'required|uuid|exists:concepts,id', + 'fk_concept_2_id' => 'required|uuid|exists:concepts,id|different:fk_concept_1_id', + 'fk_relation_type_id'=> 'required|uuid|exists:relation_types,id', + ]); + $sameRelation = ConceptRelation::where('fk_relation_type_id', $validated['fk_relation_type_id'])->where('fk_concept_1_id', $validated['fk_concept_1_id']) + ->where('fk_concept_2_id', $validated['fk_concept_2_id']) + ->orWhere('fk_concept_2_id', $validated['fk_concept_1_id'])->where('fk_concept_1_id', $validated['fk_concept_2_id']); + if ($sameRelation->count() > 1) { + $messages = ['max' => __('dashboard.relation.create.errors.exist')]; + Validator::validate($request->all(), [ + 'fk_relation_type_id' => 'max:0' + ], $messages); + } + + $conceptRelation = ConceptRelation::findOrFail($conceptRelation); + $conceptRelation->updateOrFail($validated); + return redirect()->route('concept.show', compact('dictionary', 'concept'))->with('success',__('dashboard.relation.messages.updated')); } /** * Remove the specified resource from storage. */ - public function destroy(ConceptRelation $conceptRelation) + public function destroy(string $dictionary, string $concept, string $conceptRelation) { - // + $dictionary = Dictionary::findOrFail($dictionary); + Gate::authorize('must-be-owner', $dictionary); + + $conceptRelation = ConceptRelation::findOrFail($conceptRelation); + + $conceptRelation->delete(); + + return redirect()->back()->with('error',__('dashboard.relation.messages.deleted')); } } diff --git a/app/Http/Controllers/RelationTypeController.php b/app/Http/Controllers/RelationTypeController.php index 05b524b..1ecf844 100644 --- a/app/Http/Controllers/RelationTypeController.php +++ b/app/Http/Controllers/RelationTypeController.php @@ -5,6 +5,8 @@ use App\Models\RelationType; use App\Http\Requests\StoreRelationTypeRequest; use App\Http\Requests\UpdateRelationTypeRequest; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Gate; class RelationTypeController extends Controller { @@ -13,7 +15,14 @@ class RelationTypeController extends Controller */ public function index() { - // + $query = RelationType::query(); + if (request('search')) { + $query->where('name', 'ilike', '%' . request('search') . '%'); + } + + $relationTypes = $query->paginate(15); + + return view("relation-type.index", compact('relationTypes')); } /** @@ -21,15 +30,35 @@ public function index() */ public function create() { - // + Gate::authorize('redactor'); + + return view('relation-type.create'); } /** * Store a newly created resource in storage. */ - public function store(StoreRelationTypeRequest $request) + public function store(Request $request) { - // + Gate::authorize('redactor'); + + $validated = $this->validate($request, [ + 'name' => 'required|string|unique:relation_types,name|unique:relation_types,name_plural', + 'description' => 'nullable|string', + 'name_plural' => 'nullable|string|unique:relation_types,name|unique:relation_types,name_plural' + ]); + + if($validated['name_plural'] == null) { + $validated['name_plural'] = $validated['name']; + } + + RelationType::create($validated); + + return redirect()->route('relation-type.index') + ->with( + 'success', + __('relation-type-page.messages.created') + ); } /** @@ -43,24 +72,46 @@ public function show(RelationType $relationType) /** * Show the form for editing the specified resource. */ - public function edit(RelationType $relationType) + public function edit(string $relationType) { - // + Gate::authorize('redactor'); + $relationType = RelationType::findOrFail($relationType); + + return view('relation-type.edit', compact('relationType')); } /** * Update the specified resource in storage. */ - public function update(UpdateRelationTypeRequest $request, RelationType $relationType) + public function update(Request $request, string $relationType) { - // + Gate::authorize('redactor'); + $validated = $this->validate($request, [ + 'name' => 'required|string|unique:relation_types,name,' . $relationType.'|unique:relation_types,name_plural,'. $relationType, + 'description' => 'nullable|string', + 'name_plural' => 'nullable|string|unique:relation_types,name_plural,'. $relationType.'|unique:relation_types,name,' . $relationType + ]); + + RelationType::findOrFail($relationType)->update($validated); + + return redirect()->route('relation-type.index') + ->with( + 'success', + __('relation-type-page.messages.updated') + ); } /** * Remove the specified resource from storage. */ - public function destroy(RelationType $relationType) + public function destroy(string $relationType) { - // + Gate::authorize('admin'); + RelationType::findOrFail($relationType)->delete(); + return redirect()->route('relation-type.index') + ->with( + 'success', + __('relation-type-page.messages.deleted') + ); } } diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index 2917600..bada593 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -4,6 +4,7 @@ use App\Models\Tag; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Gate; class TagController extends Controller { @@ -54,6 +55,7 @@ public function store(Request $request) */ public function edit(Tag $tag) { + Gate::authorize('redactor'); return view('tag.edit', compact('tag')); } @@ -62,6 +64,8 @@ public function edit(Tag $tag) */ public function update(Request $request, Tag $tag) { + Gate::authorize('redactor'); + $validated = $request->validate([ 'name' => 'required|unique:tags,name,' . $tag->id . '|max:50', ]); @@ -80,6 +84,8 @@ public function update(Request $request, Tag $tag) */ public function destroy(Tag $tag) { + Gate::authorize('admin'); + $tag->deleteOrFail(); return redirect()->back() diff --git a/app/Models/Concept.php b/app/Models/Concept.php index 4343daa..820616e 100644 --- a/app/Models/Concept.php +++ b/app/Models/Concept.php @@ -69,6 +69,16 @@ public function conceptAttributes(): HasMany return $this->hasMany(ConceptAttribute::class, 'fk_concept_id'); } + public function relatedConcepts1(): BelongsToMany + { + return $this->belongsToMany(Concept::class, 'concept_relations', 'fk_concept_1_id', 'fk_concept_2_id'); + } + + public function relatedConcepts2(): BelongsToMany + { + return $this->belongsToMany(Concept::class, 'concept_relations', 'fk_concept_2_id', 'fk_concept_1_id'); + } + protected $fillable = [ 'name', 'definition', diff --git a/app/Models/ConceptRelation.php b/app/Models/ConceptRelation.php index a7d222f..be11f04 100644 --- a/app/Models/ConceptRelation.php +++ b/app/Models/ConceptRelation.php @@ -10,11 +10,11 @@ /** * @property string $id - * @property string $fkConcept1Id - * @property string $fkConcept2Id - * @property string $fkRelationTypeId - * @property Carbon $createdAt - * @property Carbon $updatedAt + * @property string $fk_concept_1_id + * @property string $fk_concept_2_id + * @property string $fk_relation_type_id + * @property Carbon $created_at + * @property Carbon $updated_at */ class ConceptRelation extends Model { diff --git a/app/Models/RelationType.php b/app/Models/RelationType.php index 65aa2b6..8e3ad7e 100644 --- a/app/Models/RelationType.php +++ b/app/Models/RelationType.php @@ -7,6 +7,11 @@ use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; +/** + * @property string $name + * @property string $description + * @property mixed $name_plural + */ class RelationType extends Model { use HasFactory, HasUuids; @@ -19,5 +24,6 @@ public function relatedConcepts(): HasMany protected $fillable = [ 'name', 'description', + 'name_plural', ]; } diff --git a/app/Models/User.php b/app/Models/User.php index 5b6e81c..411ab9f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -15,6 +15,8 @@ * @property string $id * @property string $name * @property string $email + * @property bool $is_admin + * @property bool $is_redactor */ class User extends Authenticatable { diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f1a4d93..965518b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -57,6 +57,15 @@ public function boot(): void Gate::define('must-be-owner', function (User $user, Dictionary $dictionary) { return $user->id === $dictionary->fk_user_id; }); + + Gate::define('redactor', function (User $user) { + return $user->is_redactor || $user->is_admin; + }); + + Gate::define('admin', function (User $user) { + return $user->is_admin; + }); + #endregion } } diff --git a/database/migrations/2024_04_02_153622_create_all_tables.php b/database/migrations/2024_04_02_153622_create_all_tables.php index cbc0665..74874e6 100644 --- a/database/migrations/2024_04_02_153622_create_all_tables.php +++ b/database/migrations/2024_04_02_153622_create_all_tables.php @@ -17,7 +17,7 @@ public function up(): void $table->uuid('id')->primary(); $table->foreignUuid('fk_dictionary_id')->references('id')->on('dictionaries')->cascadeOnDelete(); $table->string('name', 255); - $table->string('definition', 1000); + $table->string('definition', 1000)->nullable(); $table->timestampsTz(); $table->unique(['fk_dictionary_id','name']); @@ -28,7 +28,7 @@ public function up(): void Schema::create('relation_types', function (Blueprint $table) { $table->uuid('id')->primary(); $table->string('name', 255); - $table->string('description', 1000); + $table->string('description', 1000)->nullable(); $table->timestampsTz(); $table->unique('name'); diff --git a/database/migrations/2024_05_20_135318_user_roles.php b/database/migrations/2024_05_20_135318_user_roles.php new file mode 100644 index 0000000..52d5254 --- /dev/null +++ b/database/migrations/2024_05_20_135318_user_roles.php @@ -0,0 +1,29 @@ +boolean('is_admin')->default(false); + $table->boolean('is_redactor')->default(false); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn(['is_admin', 'is_redactor']); + }); + } +}; diff --git a/database/migrations/2024_05_22_090313_relation_plural.php b/database/migrations/2024_05_22_090313_relation_plural.php new file mode 100644 index 0000000..e8ac684 --- /dev/null +++ b/database/migrations/2024_05_22_090313_relation_plural.php @@ -0,0 +1,30 @@ +string('name_plural', 255)->nullable(); + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('relation_types', function (Blueprint $table) { + $table->dropColumn(['name_plural']); + }); + } +}; diff --git a/lang/ru/dashboard.php b/lang/ru/dashboard.php index 95bb6e9..e72e3e1 100644 --- a/lang/ru/dashboard.php +++ b/lang/ru/dashboard.php @@ -14,4 +14,33 @@ 'examples' => [ 'header' => 'Примеры ":concept"', ], + 'relation' => [ + 'create' => [ + 'title' => 'Добавление отношения', + 'form' => [ + 'title' => 'Добавление отношения понятию - :concept', + 'type' => 'Тип отношения', + 'concept' => 'Связанное понятие', + ], + 'errors' => [ + 'exist' => 'Такое отношение уже существует' + ] + ], + 'edit' => [ + 'title' => 'Редактирование отношения', + 'form' => [ + 'title' => 'Редактирование отношения понятия - :concept', + 'type' => 'Тип отношения', + 'concept' => 'Связанное понятие', + ], + 'errors' => [ + 'exist' => 'Такое отношение уже существует' + ] + ], + 'messages' => [ + 'deleted' => 'Отношение удалено', + 'created' => 'Отношение создано', + 'updated' => 'Отношение обновлено', + ] + ] ]; diff --git a/lang/ru/entities.php b/lang/ru/entities.php index 4006daf..7137ad1 100644 --- a/lang/ru/entities.php +++ b/lang/ru/entities.php @@ -25,4 +25,21 @@ 'singular' => 'Вложение', 'plural' => 'Вложения', ], + 'relation-types' => [ + 'singular' => 'Тип отношения', + 'plural' => 'Типы отношений', + 'props' => [ + 'name' => 'Название', + 'plural' => 'Название во мн.ч.', + 'description' => 'Описание', + ], + ], + 'relation' => [ + 'singular' => 'Отношение', + 'plural' => 'Отношения', + ], + 'concept' => [ + 'singular' => 'Понятие', + 'plural' => 'Понятия', + ] ]; diff --git a/lang/ru/relation-type-page.php b/lang/ru/relation-type-page.php new file mode 100644 index 0000000..87a1b5f --- /dev/null +++ b/lang/ru/relation-type-page.php @@ -0,0 +1,42 @@ + 'Отношения', + 'create' => [ + 'title' => 'Создание нового отношения', + 'form-title' => 'Создание нового отношения', + 'name' => [ + 'label' => 'Название отношения', + 'placeholder' => 'Название отношения', + ], + 'plural' => [ + 'label' => 'Название отношения во множественном числе', + 'placeholder' => 'Название отношения во множественном числе', + ], + 'description' => [ + 'label' => 'Описание отношения', + 'placeholder' => 'Описание отношения', + ], + ], + 'edit' => [ + 'title' => 'Редактирование отношения', + 'form-title' => 'Редактирование отношения', + 'name' => [ + 'label' => 'Название отношения', + 'placeholder' => 'Название отношения', + ], + 'plural' => [ + 'label' => 'Название отношения во множественном числе', + 'placeholder' => 'Название отношения во множественном числе', + ], + 'description' => [ + 'label' => 'Описание отношения', + 'placeholder' => 'Описание отношения', + ], + ], + 'messages' => [ + 'deleted' => 'Отношение успешно удалено', + 'updated' => 'Отношение успешно обновлено', + 'created' => 'Отношение успешно создано', + ], +]; diff --git a/resources/views/components/dashboard/sidebar/menu.blade.php b/resources/views/components/dashboard/sidebar/menu.blade.php index 753e942..5c8d64d 100644 --- a/resources/views/components/dashboard/sidebar/menu.blade.php +++ b/resources/views/components/dashboard/sidebar/menu.blade.php @@ -8,7 +8,7 @@ {{__('dashboard.sidebar.menu.create')}}
- + diff --git a/resources/views/components/dashboard/sidebar/sidebar.blade.php b/resources/views/components/dashboard/sidebar/sidebar.blade.php index c985705..19ba7b8 100644 --- a/resources/views/components/dashboard/sidebar/sidebar.blade.php +++ b/resources/views/components/dashboard/sidebar/sidebar.blade.php @@ -1,4 +1,5 @@