refactor: replace custom CRM with Monica fork
Some checks failed
Build & Push Monica Image to Gitea Registry / build-and-push (push) Failing after 9s

This commit is contained in:
Kroonk
2026-05-22 16:19:55 +02:00
parent 38cc4c09ca
commit a213a1dba0
2215 changed files with 242567 additions and 6015 deletions

View File

@@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers\Account\Activity;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Services\Account\Activity\ActivityTypeCategory\CreateActivityTypeCategory;
use App\Services\Account\Activity\ActivityTypeCategory\UpdateActivityTypeCategory;
use App\Services\Account\Activity\ActivityTypeCategory\DestroyActivityTypeCategory;
use App\Http\Resources\Activity\ActivityTypeCategory as ActivityTypeCategoryResource;
class ActivityTypeCategoriesController extends Controller
{
use JsonRespondController;
/**
* Get all the activity type categories.
*/
public function index()
{
$activityTypeCategoriesData = collect([]);
$activityTypeCategories = auth()->user()->account->activityTypeCategories;
foreach ($activityTypeCategories as $activityTypeCategory) {
$activityTypesData = collect([]);
$activityTypes = $activityTypeCategory->activityTypes;
foreach ($activityTypes as $activityType) {
$dataActivityType = [
'id' => $activityType->id,
'name' => $activityType->name,
];
$activityTypesData->push($dataActivityType);
}
$data = [
'id' => $activityTypeCategory->id,
'name' => $activityTypeCategory->name,
'activityTypes' => $activityTypesData,
];
$activityTypeCategoriesData->push($data);
}
return $activityTypeCategoriesData;
}
/**
* Store an activity type category.
*
* @param Request $request
* @return ActivityTypeCategoryResource
*/
public function store(Request $request)
{
$type = app(CreateActivityTypeCategory::class)->execute([
'account_id' => auth()->user()->account_id,
'name' => $request->input('name'),
'translation_key' => $request->input('translation_key'),
]);
return new ActivityTypeCategoryResource($type);
}
/**
* Update an activity type category.
*
* @param Request $request
* @param int $activityTypeCategoryId
* @return ActivityTypeCategoryResource
*/
public function update(Request $request, $activityTypeCategoryId)
{
$data = [
'account_id' => auth()->user()->account_id,
'activity_type_category_id' => $activityTypeCategoryId,
'name' => $request->input('name'),
'translation_key' => $request->input('translation_key'),
];
$type = app(UpdateActivityTypeCategory::class)->execute($data);
return new ActivityTypeCategoryResource($type);
}
/**
* Delete the activity type category.
*
* @param Request $request
* @param int $activityTypeCategoryId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $activityTypeCategoryId)
{
$data = [
'account_id' => auth()->user()->account_id,
'activity_type_category_id' => $activityTypeCategoryId,
];
try {
app(DestroyActivityTypeCategory::class)->execute($data);
} catch (\Exception $e) {
return $this->respondNotFound();
}
return $this->respondObjectDeleted($activityTypeCategoryId);
}
}

View File

@@ -0,0 +1,79 @@
<?php
namespace App\Http\Controllers\Account\Activity;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Services\Account\Activity\ActivityType\CreateActivityType;
use App\Services\Account\Activity\ActivityType\UpdateActivityType;
use App\Services\Account\Activity\ActivityType\DestroyActivityType;
use App\Http\Resources\Activity\ActivityType as ActivityTypeResource;
class ActivityTypesController extends Controller
{
use JsonRespondController;
/**
* Store an activity type category.
*
* @param Request $request
* @return ActivityTypeResource
*/
public function store(Request $request)
{
$type = app(CreateActivityType::class)->execute([
'account_id' => auth()->user()->account_id,
'activity_type_category_id' => $request->input('activity_type_category_id'),
'name' => $request->input('name'),
'translation_key' => $request->input('translation_key'),
]);
return new ActivityTypeResource($type);
}
/**
* Update an activity type.
*
* @param Request $request
* @param int $activityTypeId
* @return ActivityTypeResource
*/
public function update(Request $request, $activityTypeId)
{
$data = [
'account_id' => auth()->user()->account_id,
'activity_type_id' => $activityTypeId,
'activity_type_category_id' => $request->input('activity_type_category_id'),
'name' => $request->input('name'),
'translation_key' => $request->input('translation_key'),
];
$type = app(UpdateActivityType::class)->execute($data);
return new ActivityTypeResource($type);
}
/**
* Delete the activity type.
*
* @param Request $request
* @param int $activityTypeId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $activityTypeId)
{
$data = [
'account_id' => auth()->user()->account_id,
'activity_type_id' => $activityTypeId,
];
try {
app(DestroyActivityType::class)->execute($data);
} catch (\Exception $e) {
return $this->respondNotFound();
}
return $this->respondObjectDeleted($activityTypeId);
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace App\Http\Controllers\Account\LifeEvent;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
class LifeEventCategoriesController extends Controller
{
use JsonRespondController;
/**
* Get all the life event categories.
*/
public function index()
{
$lifeEventCategoriesData = collect([]);
$lifeEventCategories = auth()->user()->account->lifeEventCategories;
foreach ($lifeEventCategories as $lifeEventCategory) {
$lifeEventTypesData = collect([]);
$lifeEventTypes = $lifeEventCategory->lifeEventTypes;
foreach ($lifeEventTypes as $lifeEventType) {
$dataLifeEventType = [
'id' => $lifeEventType->id,
'name' => $lifeEventType->name,
'default_life_event_type_key' => $lifeEventType->default_life_event_type_key,
];
$lifeEventTypesData->push($dataLifeEventType);
}
$data = [
'id' => $lifeEventCategory->id,
'name' => $lifeEventCategory->name,
'default_life_event_category_key' => $lifeEventCategory->default_life_event_category_key,
'lifeEventTypes' => $lifeEventTypesData,
];
$lifeEventCategoriesData->push($data);
}
return $lifeEventCategoriesData;
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace App\Http\Controllers\Account\LifeEvent;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Services\Account\LifeEvent\LifeEventType\CreateLifeEventType;
use App\Services\Account\LifeEvent\LifeEventType\UpdateLifeEventType;
use App\Services\Account\LifeEvent\LifeEventType\DestroyLifeEventType;
use App\Http\Resources\LifeEvent\LifeEventType as LifeEventTypeResource;
class LifeEventTypesController extends Controller
{
use JsonRespondController;
/**
* Store a life event type.
*
* @param Request $request
* @return LifeEventTypeResource
*/
public function store(Request $request)
{
$type = app(CreateLifeEventType::class)->execute([
'account_id' => auth()->user()->account_id,
'life_event_category_id' => $request->input('life_event_category_id'),
'name' => $request->input('name'),
]);
return new LifeEventTypeResource($type);
}
/**
* Update a life event type.
*
* @param Request $request
* @param int $liveEventTypeId
* @return LifeEventTypeResource
*/
public function update(Request $request, $liveEventTypeId)
{
$data = [
'account_id' => auth()->user()->account_id,
'life_event_type_id' => $liveEventTypeId,
'life_event_category_id' => $request->input('life_event_category_id'),
'name' => $request->input('name'),
];
$type = app(UpdateLifeEventType::class)->execute($data);
return new LifeEventTypeResource($type);
}
/**
* Delete the life event type.
*
* @param Request $request
* @param int $lifeEventTypeId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $lifeEventTypeId)
{
$data = [
'account_id' => auth()->user()->account_id,
'life_event_type_id' => $lifeEventTypeId,
];
try {
app(DestroyLifeEventType::class)->execute($data);
} catch (\Exception $e) {
return $this->respondNotFound();
}
return $this->respondObjectDeleted($lifeEventTypeId);
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\Api\Account\Activity;
use Illuminate\Http\Request;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use App\Models\Account\ActivityTypeCategory;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Account\Activity\ActivityTypeCategory\CreateActivityTypeCategory;
use App\Services\Account\Activity\ActivityTypeCategory\UpdateActivityTypeCategory;
use App\Services\Account\Activity\ActivityTypeCategory\DestroyActivityTypeCategory;
use App\Http\Resources\Activity\ActivityTypeCategory as ActivityTypeCategoryResource;
class ApiActivityTypeCategoryController extends ApiController
{
/**
* Get the list of activity type categories.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$activityTypeCategories = auth()->user()->account->activityTypeCategories()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ActivityTypeCategoryResource::collection($activityTypeCategories);
}
/**
* Get the detail of a given activity type category.
*
* @param Request $request
* @param int $activityTypeCategoryId
* @return ActivityTypeCategoryResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $activityTypeCategoryId)
{
try {
$activityTypeCategory = ActivityTypeCategory::where('account_id', auth()->user()->account_id)
->where('id', $activityTypeCategoryId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ActivityTypeCategoryResource($activityTypeCategory);
}
/**
* Store the activity type category.
*
* @param Request $request
* @return ActivityTypeCategoryResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$activityTypeCategory = app(CreateActivityTypeCategory::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ActivityTypeCategoryResource($activityTypeCategory);
}
/**
* Update the activity type category.
*
* @param Request $request
* @param int $activityTypeCategoryId
* @return ActivityTypeCategoryResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $activityTypeCategoryId)
{
try {
$activityTypeCategory = app(UpdateActivityTypeCategory::class)->execute(
$request->except(['account_id', 'activity_type_category_id'])
+
[
'account_id' => auth()->user()->account_id,
'activity_type_category_id' => $activityTypeCategoryId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ActivityTypeCategoryResource($activityTypeCategory);
}
/**
* Delete an activity type category.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $activityTypeCategoryId)
{
try {
app(DestroyActivityTypeCategory::class)->execute([
'account_id' => auth()->user()->account_id,
'activity_type_category_id' => $activityTypeCategoryId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($activityTypeCategoryId);
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\Api\Account\Activity;
use Illuminate\Http\Request;
use App\Models\Account\ActivityType;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Account\Activity\ActivityType\CreateActivityType;
use App\Services\Account\Activity\ActivityType\UpdateActivityType;
use App\Services\Account\Activity\ActivityType\DestroyActivityType;
use App\Http\Resources\Activity\ActivityType as ActivityTypeResource;
class ApiActivityTypeController extends ApiController
{
/**
* Get the list of activity types.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$activityTypes = auth()->user()->account->activityTypes()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ActivityTypeResource::collection($activityTypes);
}
/**
* Get the detail of a given activity type.
*
* @param Request $request
* @return ActivityTypeResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $activityTypeId)
{
try {
$activityType = ActivityType::where('account_id', auth()->user()->account_id)
->where('id', $activityTypeId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ActivityTypeResource($activityType);
}
/**
* Store the activity type.
*
* @param Request $request
* @return ActivityTypeResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$activityType = app(CreateActivityType::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ActivityTypeResource($activityType);
}
/**
* Update the activity type.
*
* @param Request $request
* @param int $activityTypeId
* @return ActivityTypeResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $activityTypeId)
{
try {
$activityType = app(UpdateActivityType::class)->execute(
$request->except(['account_id', 'activity_type_id'])
+
[
'account_id' => auth()->user()->account_id,
'activity_type_id' => $activityTypeId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ActivityTypeResource($activityType);
}
/**
* Delete an activity type.
*
* @param Request $request
* @param int $activityTypeId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $activityTypeId)
{
try {
app(DestroyActivityType::class)->execute([
'account_id' => auth()->user()->account_id,
'activity_type_id' => $activityTypeId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($activityTypeId);
}
}

View File

@@ -0,0 +1,135 @@
<?php
namespace App\Http\Controllers\Api\Account;
use Illuminate\Http\Request;
use App\Models\Account\Company;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use App\Services\Account\Company\CreateCompany;
use App\Services\Account\Company\UpdateCompany;
use App\Services\Account\Company\DestroyCompany;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Company\Company as CompanyResource;
class ApiCompanyController extends ApiController
{
/**
* Get the list of companies.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$companies = auth()->user()->account->companies()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return CompanyResource::collection($companies);
}
/**
* Get the detail of a given company.
*
* @param Request $request
* @return CompanyResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $companyId)
{
try {
$company = Company::where('account_id', auth()->user()->account_id)
->where('id', $companyId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new CompanyResource($company);
}
/**
* Store the company.
*
* @param Request $request
* @return CompanyResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$company = app(CreateCompany::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
'author_id' => auth()->user()->id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new CompanyResource($company);
}
/**
* Update a company.
*
* @param Request $request
* @param int $companyId
* @return CompanyResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $companyId)
{
try {
$company = app(UpdateCompany::class)->execute(
$request->except(['account_id', 'company_id'])
+
[
'account_id' => auth()->user()->account_id,
'company_id' => $companyId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new CompanyResource($company);
}
/**
* Delete a company.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $companyId)
{
try {
app(DestroyCompany::class)->execute([
'account_id' => auth()->user()->account_id,
'company_id' => $companyId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($companyId);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Http\Controllers\Api\Account;
use Illuminate\Http\Request;
use App\Models\Contact\Gender;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use App\Services\Account\Gender\CreateGender;
use App\Services\Account\Gender\UpdateGender;
use App\Services\Account\Gender\DestroyGender;
use Illuminate\Validation\ValidationException;
use App\Http\Resources\Gender\Gender as GenderResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiGenderController extends ApiController
{
/**
* Get the list of genders.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$genders = auth()->user()->account->genders()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return GenderResource::collection($genders);
}
/**
* Get the detail of a given gender.
*
* @param Request $request
* @return GenderResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $genderId)
{
try {
$gender = Gender::where('account_id', auth()->user()->account_id)
->where('id', $genderId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new GenderResource($gender);
}
/**
* Store the gender.
*
* @param Request $request
* @return GenderResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$gender = app(CreateGender::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new GenderResource($gender);
}
/**
* Update a gender.
*
* @param Request $request
* @param int $genderId
* @return GenderResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $genderId)
{
try {
$gender = app(UpdateGender::class)->execute(
$request->except(['account_id', 'gender_id'])
+
[
'account_id' => auth()->user()->account_id,
'gender_id' => $genderId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new GenderResource($gender);
}
/**
* Delete a gender.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $genderId)
{
try {
app(DestroyGender::class)->execute([
'account_id' => auth()->user()->account_id,
'gender_id' => $genderId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($genderId);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Http\Controllers\Api\Account;
use Illuminate\Http\Request;
use App\Models\Account\Place;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use App\Services\Account\Place\CreatePlace;
use App\Services\Account\Place\UpdatePlace;
use App\Services\Account\Place\DestroyPlace;
use Illuminate\Validation\ValidationException;
use App\Http\Resources\Place\Place as PlaceResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiPlaceController extends ApiController
{
/**
* Get the list of places.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$places = auth()->user()->account->places()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return PlaceResource::collection($places);
}
/**
* Get the detail of a given place.
*
* @param Request $request
* @return PlaceResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $placeId)
{
try {
$place = Place::where('account_id', auth()->user()->account_id)
->where('id', $placeId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new PlaceResource($place);
}
/**
* Store the place.
*
* @param Request $request
* @return PlaceResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$place = app(CreatePlace::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new PlaceResource($place);
}
/**
* Update a place.
*
* @param Request $request
* @param int $placeId
* @return PlaceResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $placeId)
{
try {
$place = app(UpdatePlace::class)->execute(
$request->except(['account_id', 'place_id'])
+
[
'account_id' => auth()->user()->account_id,
'place_id' => $placeId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new PlaceResource($place);
}
/**
* Delete a place.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $placeId)
{
try {
app(DestroyPlace::class)->execute([
'account_id' => auth()->user()->account_id,
'place_id' => $placeId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($placeId);
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace App\Http\Controllers\Api\Account;
use App\Models\User\User;
use App\Helpers\DateHelper;
use Illuminate\Http\Request;
use App\Models\Settings\Term;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use App\Services\User\AcceptPolicy;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Account\User\User as UserResource;
use App\Http\Resources\Settings\Compliance\Compliance as ComplianceResource;
class ApiUserController extends ApiController
{
/**
* Get the detail of the authenticated user.
*
* @param Request $request
* @return UserResource
*/
public function show(Request $request): UserResource
{
return new UserResource(auth()->user());
}
/**
* Get the state of a specific term for the user.
*
* @param Request $request
* @param int $termId
* @return JsonResponse
*/
public function get(Request $request, $termId)
{
try {
$term = Term::findOrFail($termId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$termUser = DB::table('term_user')->where('user_id', auth()->user()->id)
->where('account_id', auth()->user()->account_id)
->where('term_id', $term->id)
->first();
if ($termUser) {
$data = [
'signed' => true,
'signed_date' => DateHelper::getTimestamp($termUser->created_at),
'ip_address' => $termUser->ip_address,
'user' => new UserResource(auth()->user()),
'term' => new ComplianceResource($term),
];
} else {
return $this->respondNotFound();
}
return $this->respond([
'data' => $data,
]);
}
/**
* Get all the policies ever signed by the authenticated user.
*
* @param Request $request
* @return JsonResponse
*/
public function getSignedPolicies(Request $request)
{
$terms = collect();
$termsForUser = DB::table('term_user')
->where('user_id', auth()->user()->id)
->get();
if ($termsForUser->count() == 0) {
return $this->respondNotFound();
}
foreach ($termsForUser as $termUser) {
$term = Term::findOrFail($termUser->term_id);
$terms->push([
'signed' => true,
'signed_date' => DateHelper::getTimestamp($termUser->created_at),
'ip_address' => $termUser->ip_address,
'user' => new UserResource(auth()->user()),
'term' => new ComplianceResource($term),
]);
}
return $this->respond([
'data' => $terms,
]);
}
/**
* Sign the latest policy for the authenticated user.
*
* @param Request $request
* @return JsonResponse
*/
public function set(Request $request)
{
$validator = Validator::make($request->all(), [
'ip_address' => 'required',
]);
if ($validator->fails()) {
return $this->respondValidatorFailed($validator);
}
try {
$term = app(AcceptPolicy::class)->execute([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
'ip_address' => $request->input('ip_address'),
]);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
try {
$termUser = DB::table('term_user')->where('user_id', auth()->user()->id)
->where('account_id', auth()->user()->account_id)
->where('term_id', $term->id)
->first();
} catch (ModelNotFoundException $e) {
return $this->respondInvalidQuery();
}
return $this->respond([
'data' => [
'signed' => true,
'signed_date' => DateHelper::getTimestamp($termUser->created_at),
'ip_address' => $termUser->ip_address,
'user' => new UserResource(auth()->user()),
'term' => new ComplianceResource($term),
],
]);
}
}

View File

@@ -0,0 +1,161 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Account\Activity\Activity\CreateActivity;
use App\Services\Account\Activity\Activity\UpdateActivity;
use App\Services\Account\Activity\Activity\DestroyActivity;
use App\Http\Resources\Activity\Activity as ActivityResource;
class ApiActivitiesController extends ApiController
{
/**
* Get the list of activities.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$activities = auth()->user()->account->activities()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ActivityResource::collection($activities)->additional(['meta' => [
'statistics' => AccountHelper::getYearlyActivitiesStatistics(auth()->user()->account),
]]);
}
/**
* Get the detail of a given activity.
*
* @param Request $request
* @return ActivityResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $activityId)
{
try {
$activity = Activity::where('account_id', auth()->user()->account_id)
->findOrFail($activityId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ActivityResource($activity);
}
/**
* Store the activity.
*
* @param Request $request
* @return ActivityResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$activity = app(CreateActivity::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ActivityResource($activity);
}
/**
* Update the activity.
*
* @param Request $request
* @param int $activityId
* @return ActivityResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $activityId)
{
try {
$activity = app(UpdateActivity::class)->execute(
$request->except(['account_id', 'activity_id'])
+
[
'account_id' => auth()->user()->account_id,
'activity_id' => $activityId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ActivityResource($activity);
}
/**
* Delete an activity.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $activityId)
{
try {
app(DestroyActivity::class)->execute([
'account_id' => auth()->user()->account_id,
'activity_id' => $activityId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return $this->respondObjectDeleted($activityId);
}
/**
* Get the list of activities for the given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function activities(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->findOrFail($contactId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
$activities = $contact->activities()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ActivityResource::collection($activities)->additional(['meta' => [
'statistics' => AccountHelper::getYearlyActivitiesStatistics(auth()->user()->account),
]]);
}
}

View File

@@ -0,0 +1,260 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Helpers\SearchHelper;
use App\Models\Contact\Contact;
use Illuminate\Http\JsonResponse;
use App\Jobs\UpdateLastConsultedDate;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Contact\CreateContact;
use App\Services\Contact\Contact\UpdateContact;
use App\Services\Contact\Contact\DestroyContact;
use Illuminate\Http\Resources\Json\JsonResource;
use App\Services\Contact\Contact\UpdateWorkInformation;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Contact\Contact as ContactResource;
use App\Services\Contact\Contact\UpdateContactIntroduction;
use App\Services\Contact\Contact\UpdateContactFoodPreferences;
class ApiContactController extends ApiController
{
/**
* Instantiate a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('limitations')->only('setMe');
parent::__construct();
}
/**
* Get the list of the contacts.
* We will only retrieve the contacts that are "real", not the partials
* ones.
*
* @param Request $request
* @return JsonResource|JsonResponse
*/
public function index(Request $request)
{
if ($request->input('query')) {
$needle = rawurldecode($request->input('query'));
try {
$contacts = SearchHelper::searchContacts(
$needle,
$this->sort,
$this->sortDirection
)
->real()
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ContactResource::collection($contacts)->additional([
'meta' => [
'query' => $needle,
],
]);
}
try {
$contacts = auth()->user()->account->contacts()
->real()
->active()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ContactResource::collection($contacts);
}
/**
* Get the detail of a given contact.
*
* @param Request $request
* @param int $id
* @return ContactResource|JsonResponse
*/
public function show(Request $request, int $id)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $id)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
UpdateLastConsultedDate::dispatch($contact);
return new ContactResource($contact);
}
/**
* Store the contact.
*
* @param Request $request
* @return ContactResource|JsonResponse
*/
public function store(Request $request)
{
try {
$contact = app(CreateContact::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
'author_id' => auth()->user()->id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ContactResource($contact);
}
/**
* Update the contact.
*
* @param Request $request
* @return ContactResource|JsonResponse
*/
public function update(Request $request, $contactId)
{
try {
$contact = app(UpdateContact::class)->execute(
$request->except(['account_id', 'contact_id'])
+
[
'contact_id' => $contactId,
'account_id' => auth()->user()->account_id,
'author_id' => auth()->user()->id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ContactResource($contact);
}
/**
* Delete a contact.
*
* @param Request $request
* @return JsonResponse
*/
public function destroy(Request $request, $contactId)
{
$data = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contactId,
];
DestroyContact::dispatch($data);
return $this->respondObjectDeleted($contactId);
}
/**
* Set the contact career.
*
* @param Request $request
* @param int $contactId
* @return ContactResource|JsonResponse
*/
public function updateWork(Request $request, $contactId)
{
try {
$contact = app(UpdateWorkInformation::class)->execute(
$request->except(['account_id', 'contact_id'])
+ [
'contact_id' => $contactId,
'account_id' => auth()->user()->account_id,
'author_id' => auth()->user()->id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ContactResource($contact);
}
/**
* Set the contact food preferences.
*
* @param Request $request
* @param int $contactId
* @return ContactResource|JsonResponse
*/
public function updateFoodPreferences(Request $request, $contactId)
{
try {
$contact = app(UpdateContactFoodPreferences::class)->execute(
$request->except(['account_id', 'contact_id'])
+ [
'contact_id' => $contactId,
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ContactResource($contact);
}
/**
* Set how you met the contact.
*
* @param Request $request
* @param int $contactId
* @return ContactResource|JsonResponse
*/
public function updateIntroduction(Request $request, $contactId)
{
try {
$contact = app(UpdateContactIntroduction::class)->execute(
$request->except(['account_id', 'contact_id'])
+ [
'contact_id' => $contactId,
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ContactResource($contact);
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Models\Contact\ContactField;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Contact\ContactField\CreateContactField;
use App\Services\Contact\ContactField\UpdateContactField;
use App\Services\Contact\ContactField\DestroyContactField;
use App\Http\Resources\ContactField\ContactField as ContactFieldResource;
class ApiContactFieldController extends ApiController
{
/**
* Get the detail of a given contactField.
*
* @param Request $request
* @param int $contactFieldId
* @return ContactFieldResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $contactFieldId)
{
try {
$contactField = ContactField::where('account_id', auth()->user()->account_id)
->findOrFail($contactFieldId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ContactFieldResource($contactField);
}
/**
* Store the contactField.
*
* @param Request $request
* @return ContactFieldResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$contactField = app(CreateContactField::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ContactFieldResource($contactField);
}
/**
* Update the contactField.
*
* @param Request $request
* @param int $contactFieldId
* @return ContactFieldResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $contactFieldId)
{
try {
$contactField = app(UpdateContactField::class)->execute(
$request->except(['account_id', 'address_id'])
+
[
'account_id' => auth()->user()->account_id,
'contact_field_id' => $contactFieldId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ContactFieldResource($contactField);
}
/**
* Delete a contactField.
*
* @param Request $request
* @param int $contactFieldId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $contactFieldId)
{
try {
app(DestroyContactField::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_field_id' => $contactFieldId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($contactFieldId);
}
/**
* Get the list of contact fields for the given contact.
*
* @param Request $request
* @param int $contactId
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function contactFields(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$contactFields = $contact->contactFields()
->paginate($this->getLimitPerPage());
return ContactFieldResource::collection($contactFields);
}
}

View File

@@ -0,0 +1,129 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Contact\Tag;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Services\Contact\Tag\DetachTag;
use Illuminate\Support\Facades\Validator;
use App\Services\Contact\Tag\AssociateTag;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Contact\Contact as ContactResource;
class ApiContactTagController extends ApiController
{
/**
* Associate one or more tags to the contact.
*
* @param Request $request
* @param int $contactId
*/
public function setTags(Request $request, $contactId)
{
$contact = $this->validateTag($request, $contactId);
if (! $contact instanceof Contact) {
return $contact;
}
$tags = collect($request->input('tags'))
->filter(function ($tag) {
return ! empty($tag);
});
foreach ($tags as $tag) {
app(AssociateTag::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'name' => $tag,
]);
}
return new ContactResource($contact);
}
/**
* Remove all the tags associated with the contact.
*
* @param Request $request
* @param int $contactId
*/
public function unsetTags(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$contactTags = $contact->tags()->get();
foreach ($contactTags as $tag) {
app(DetachTag::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'tag_id' => $tag->id,
]);
}
return new ContactResource($contact);
}
/**
* Remove one or more specific tags associated with the contact.
*
* @param Request $request
* @param int $contactId
*/
public function unsetTag(Request $request, $contactId)
{
$contact = $this->validateTag($request, $contactId);
if (! $contact instanceof Contact) {
return $contact;
}
$tags = collect($request->input('tags'))
->filter(function ($tag) {
return ! empty($tag);
});
foreach ($tags as $tag) {
app(DetachTag::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'tag_id' => $tag,
]);
}
return new ContactResource($contact);
}
/**
* Validate the request for update tag.
*
* @param Request $request
* @param int $contactId
* @return mixed
*/
private function validateTag(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->findOrFail($contactId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$validator = Validator::make($request->all(), [
'tags' => 'required|array',
]);
if ($validator->fails()) {
return $this->respondValidatorFailed($validator);
}
return $contact;
}
}

View File

@@ -0,0 +1,207 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use function Safe\json_decode;
use App\Models\Account\ApiUsage;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
class ApiController extends Controller
{
use JsonRespondController;
/**
* @var int
*/
protected $limitPerPage = 0;
/**
* @var string
*/
protected $sort = 'created_at';
/**
* @var string
*/
protected $withParameter = null;
/**
* @var string
*/
protected $sortDirection = 'asc';
public function __construct()
{
$this->middleware(function ($request, $next) {
(new ApiUsage)->log($request);
if ($request->has('sort')) {
$this->setSortCriteria($request->input('sort'));
// It has a sort criteria, but is it a valid one?
if (empty($this->getSortCriteria())) {
return $this->setHTTPStatusCode(400)
->setErrorCode(39)
->respondWithError();
}
}
if ($request->has('limit')) {
if ($request->input('limit') > config('api.max_limit_per_page')) {
return $this->setHTTPStatusCode(400)
->setErrorCode(30)
->respondWithError();
}
$this->setLimitPerPage($request->input('limit'));
}
if ($request->has('with')) {
$this->setWithParameter($request->input('with'));
}
// make sure the JSON is well formatted if the call sends a JSON
// if the call contains a JSON, the call must not be a GET or
// a DELETE
// TODO: there is probably a much better way to do that
try {
if ($request->method() != 'GET' && $request->method() != 'DELETE'
&& is_null(json_decode($request->getContent()))) {
return $this->setHTTPStatusCode(400)
->setErrorCode(37)
->respondWithError();
}
} catch (\Safe\Exceptions\JsonException $e) {
// no error
}
return $next($request);
});
}
/**
* Default request to the API.
*
* @return \Illuminate\Http\JsonResponse
*/
public function success()
{
return $this->respond([
'success' => [
'message' => 'Welcome to Monica',
],
'links' => [
'activities_url' => route('api.activities'),
'addresses_url' => route('api.addresses'),
'calls_url' => route('api.calls'),
'contacts_url' => route('api.contacts'),
'conversations_url' => route('api.conversations'),
'countries_url' => route('api.countries'),
'currencies_url' => route('api.currencies'),
'documents_url' => route('api.documents'),
'journal_url' => route('api.journal'),
'notes_url' => route('api.notes'),
'relationships_url' => route('api.relationships', ['contact' => ':contactId']),
'reminders_url' => route('api.reminders'),
'statistics_url' => route('api.statistics'),
],
]);
}
/**
* @return string
*/
public function getWithParameter()
{
return $this->withParameter;
}
/**
* @param string $with
* @return self
*/
public function setWithParameter($with)
{
$this->withParameter = $with;
return $this;
}
/**
* @return int
*/
public function getLimitPerPage()
{
return $this->limitPerPage;
}
/**
* @param int $limit
* @return self
*/
public function setLimitPerPage($limit)
{
$this->limitPerPage = $limit;
return $this;
}
/**
* Get the sort direction parameter.
*
* @return string
*/
public function getSortDirection()
{
return $this->sortDirection;
}
/**
* @return string
*/
public function getSortCriteria()
{
return $this->sort;
}
/**
* @param string $criteria
* @return self
*/
public function setSortCriteria($criteria)
{
$acceptedCriteria = [
'created_at',
'updated_at',
'-created_at',
'-updated_at',
'completed_at',
'-completed_at',
'called_at',
'-called_at',
'favorited_at',
'-favorited_at',
];
if (in_array($criteria, $acceptedCriteria)) {
$this->setSQLOrderByQuery($criteria);
return $this;
}
$this->sort = '';
return $this;
}
/**
* Set both the column and order necessary to perform an orderBy.
*/
public function setSQLOrderByQuery($criteria)
{
$this->sortDirection = $criteria[0] == '-' ? 'desc' : 'asc';
$this->sort = ltrim($criteria, '-');
}
}

View File

@@ -0,0 +1,191 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Contact\Debt;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use Illuminate\Validation\Rule;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Validator;
use App\Http\Resources\Debt\Debt as DebtResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiDebtController extends ApiController
{
/**
* Get the list of debts.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$debts = auth()->user()->account->debts()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return DebtResource::collection($debts);
}
/**
* Get the detail of a given debt.
*
* @param Request $request
* @return DebtResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $debtId)
{
try {
$debt = Debt::where('account_id', auth()->user()->account_id)
->where('id', $debtId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new DebtResource($debt);
}
/**
* Store the debt.
*
* @param Request $request
* @return DebtResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$debt = Debt::create(
$request->except(['account_id'])
+ ['account_id' => auth()->user()->account_id]
);
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
return new DebtResource($debt);
}
/**
* Update the debt.
*
* @param Request $request
* @param int $debtId
* @return DebtResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $debtId)
{
try {
$debt = Debt::where('account_id', auth()->user()->account_id)
->where('id', $debtId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$debt->update($request->only(['in_debt', 'status', 'amount', 'reason', 'contact_id']));
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
return new DebtResource($debt);
}
/**
* Validate the request for update.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse|true
*/
private function validateUpdate(Request $request)
{
// Validates basic fields to create the entry
$validator = Validator::make($request->all(), [
'in_debt' => [
'required',
'string',
Rule::in(['yes', 'no']),
],
'status' => [
'required',
'string',
Rule::in(['inprogress', 'completed']),
],
'amount' => 'required|numeric',
'reason' => 'string|max:1000000|nullable',
'contact_id' => 'required|integer',
]);
if ($validator->fails()) {
return $this->respondValidatorFailed($validator);
}
try {
Contact::where('account_id', auth()->user()->account_id)
->where('id', $request->input('contact_id'))
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return true;
}
/**
* Delete a debt.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $debtId)
{
try {
$debt = Debt::where('account_id', auth()->user()->account_id)
->where('id', $debtId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$debt->delete();
return $this->respondObjectDeleted($debt->id);
}
/**
* Get the list of debts for the given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function debts(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$debts = $contact->debts()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
return DebtResource::collection($debts);
}
}

View File

@@ -0,0 +1,174 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Contact\Gift;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use Illuminate\Database\QueryException;
use App\Services\Contact\Gift\CreateGift;
use App\Services\Contact\Gift\UpdateGift;
use App\Services\Contact\Gift\DestroyGift;
use Illuminate\Validation\ValidationException;
use App\Http\Resources\Gift\Gift as GiftResource;
use App\Services\Contact\Gift\AssociatePhotoToGift;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiGiftController extends ApiController
{
/**
* Get the list of gifts.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$gifts = auth()->user()->account->gifts()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
return GiftResource::collection($gifts);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
}
/**
* Get the detail of a given gift.
*
* @param Request $request
* @return GiftResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
try {
$gift = Gift::where('account_id', auth()->user()->account_id)
->findOrFail($id);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new GiftResource($gift);
}
/**
* Store the gift.
*
* @param Request $request
* @return GiftResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$gift = app(CreateGift::class)->execute(
$request->except(['account_id'])
+ ['account_id' => auth()->user()->account_id]
);
return new GiftResource($gift);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
}
/**
* Update the gift.
*
* @param Request $request
* @param int $giftId
* @return GiftResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $giftId)
{
try {
$gift = app(UpdateGift::class)->execute(
$request->except(['account_id', 'gift_id'])
+ [
'account_id' => auth()->user()->account_id,
'gift_id' => $giftId,
]
);
return new GiftResource($gift);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
}
/**
* Associate a photo to the gift.
*
* @param Request $request
* @param int $giftId
* @param int $photoId
* @return GiftResource|\Illuminate\Http\JsonResponse
*/
public function associate(Request $request, $giftId, $photoId)
{
try {
$gift = app(AssociatePhotoToGift::class)->execute([
'account_id' => auth()->user()->account_id,
'gift_id' => $giftId,
'photo_id' => $photoId,
]);
return new GiftResource($gift);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
}
/**
* Delete a gift.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $giftId)
{
try {
app(DestroyGift::class)->execute([
'account_id' => auth()->user()->account_id,
'gift_id' => $giftId,
]);
return $this->respondObjectDeleted($giftId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
}
/**
* Get the list of gifts for the given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function gifts(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->findOrFail($contactId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
$gifts = $contact->gifts()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
return GiftResource::collection($gifts);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
}
}

View File

@@ -0,0 +1,148 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\Journal\Entry;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Validator;
use App\Http\Resources\Journal\Entry as JournalResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiJournalController extends ApiController
{
/**
* Get the list of journal entries.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$entries = auth()->user()->account->entries()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return JournalResource::collection($entries);
}
/**
* Get the detail of a given journal entry.
*
* @param Request $request
* @return JournalResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $entryId)
{
try {
$entry = Entry::where('account_id', auth()->user()->account_id)
->where('id', $entryId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new JournalResource($entry);
}
/**
* Store the call.
*
* @param Request $request
* @return JournalResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$entry = Entry::create(
$request->except(['account_id'])
+ ['account_id' => auth()->user()->account_id]
);
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
return new JournalResource($entry);
}
/**
* Update the note.
*
* @param Request $request
* @param int $entryId
* @return JournalResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $entryId)
{
try {
$entry = Entry::where('account_id', auth()->user()->account_id)
->where('id', $entryId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$entry->update($request->only(['title', 'post']));
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
return new JournalResource($entry);
}
/**
* Validate the request for update.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse|true
*/
private function validateUpdate(Request $request)
{
// Validates basic fields to create the entry
$validator = Validator::make($request->all(), [
'title' => 'required|max:255',
'post' => 'required|max:1000000',
]);
if ($validator->fails()) {
return $this->respondValidatorFailed($validator);
}
return true;
}
/**
* Delete a journal entry.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $entryId)
{
try {
$entry = Entry::where('account_id', auth()->user()->account_id)
->where('id', $entryId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$entry->delete();
return $this->respondObjectDeleted($entry->id);
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Services\Contact\Contact\SetMeContact;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Contact\DeleteMeContact;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiMeController extends ApiController
{
/**
* Instantiate a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('limitations')->only('store');
parent::__construct();
}
/**
* Set a contact as 'me'.
*
* @param Request $request
* @return string
*/
public function store(Request $request)
{
$data = [
'contact_id' => $request->input('contact_id'),
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
];
try {
app(SetMeContact::class)->execute($data);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return $this->respond(['true']);
}
/**
* Removes contact as 'me' association.
*
* @param Request $request
* @return string
*/
public function destroy(Request $request)
{
$data = [
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
];
app(DeleteMeContact::class)->execute($data);
return $this->respond(['true']);
}
}

View File

@@ -0,0 +1,192 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Contact\Note;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Validator;
use App\Http\Resources\Note\Note as NoteResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiNoteController extends ApiController
{
/**
* Get the list of notes.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$notes = auth()->user()->account->notes()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return NoteResource::collection($notes);
}
/**
* Get the detail of a given note.
*
* @param Request $request
* @return NoteResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
try {
$note = Note::where('account_id', auth()->user()->account_id)
->where('id', $id)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new NoteResource($note);
}
/**
* Store the note.
*
* @param Request $request
* @return NoteResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$note = Note::create(
$request->except(['account_id'])
+ ['account_id' => auth()->user()->account_id]
);
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
if ($request->input('is_favorited')) {
$note->favorited_at = now();
$note->save();
}
return new NoteResource($note);
}
/**
* Update the note.
*
* @param Request $request
* @param int $noteId
* @return NoteResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $noteId)
{
try {
$note = Note::where('account_id', auth()->user()->account_id)
->where('id', $noteId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$note->update($request->only(['body', 'contact_id', 'is_favorited']));
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
if ($request->input('is_favorited')) {
$note->favorited_at = now();
} else {
$note->favorited_at = null;
}
$note->save();
return new NoteResource($note);
}
/**
* Validate the request for update.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse|true
*/
private function validateUpdate(Request $request)
{
// Validates basic fields to create the entry
$validator = Validator::make($request->all(), [
'body' => 'required|max:100000',
'contact_id' => 'required|integer',
'is_favorited' => 'boolean',
]);
if ($validator->fails()) {
return $this->respondValidatorFailed($validator);
}
try {
Contact::where('account_id', auth()->user()->account_id)
->where('id', $request->input('contact_id'))
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return true;
}
/**
* Delete a note.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $noteId)
{
try {
$note = Note::where('account_id', auth()->user()->account_id)
->where('id', $noteId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$note->delete();
return $this->respondObjectDeleted($note->id);
}
/**
* Get the list of notes for the given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function notes(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$notes = $contact->notes()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
return NoteResource::collection($notes);
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Contact\Pet;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use Illuminate\Database\QueryException;
use Illuminate\Support\Facades\Validator;
use App\Http\Resources\Pet\Pet as PetResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiPetController extends ApiController
{
/**
* Get the list of pet.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$pets = Pet::where('account_id', auth()->user()->account_id)
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return PetResource::collection($pets);
}
/**
* Get the detail of a given pet.
*
* @param Request $request
* @return PetResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
try {
$pet = Pet::where('account_id', auth()->user()->account_id)
->where('id', $id)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new PetResource($pet);
}
/**
* Store the pet.
*
* @param Request $request
* @return PetResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$pet = Pet::create(
$request->except(['account_id'])
+ ['account_id' => auth()->user()->account_id]
);
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
return new PetResource($pet);
}
/**
* Update the pet.
*
* @param Request $request
* @param int $petId
* @return PetResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $petId)
{
try {
$pet = Pet::where('account_id', auth()->user()->account_id)
->where('id', $petId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$pet->update($request->only(['pet_category_id', 'contact_id', 'name']));
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
return new PetResource($pet);
}
/**
* Validate the request for update.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse|true
*/
private function validateUpdate(Request $request)
{
// Validates basic fields to create the entry
$validator = Validator::make($request->all(), [
'pet_category_id' => 'integer|required|exists:pet_categories,id',
'contact_id' => 'required|integer',
'name' => 'max:255',
]);
if ($validator->fails()) {
return $this->respondValidatorFailed($validator);
}
try {
Contact::where('account_id', auth()->user()->account_id)
->where('id', $request->input('contact_id'))
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return true;
}
/**
* Delete a pet.
*
* @param Request $request
* @param int $petId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $petId)
{
try {
$pet = Pet::where('account_id', auth()->user()->account_id)
->where('id', $petId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$pet->delete();
return $this->respondObjectDeleted($pet->id);
}
/**
* Get the list of pets for the given contact.
*
* @param Request $request
* @param int $contactId
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function pets(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$pets = $contact->pets()
->paginate($this->getLimitPerPage());
return PetResource::collection($pets);
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Models\Relationship\Relationship;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Contact\Relationship\CreateRelationship;
use App\Services\Contact\Relationship\UpdateRelationship;
use App\Services\Contact\Relationship\DestroyRelationship;
use App\Http\Resources\Relationship\Relationship as RelationshipResource;
class ApiRelationshipController extends ApiController
{
/**
* Get all of relationships of a contact.
*
* @param Request $request
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request, $contactId)
{
try {
$relationships = Relationship::where('account_id', auth()->user()->account_id)
->where('contact_is', $contactId)
->get();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return RelationshipResource::collection($relationships);
}
/**
* Get the detail of a given relationship.
*
* @param Request $request
* @return RelationshipResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
try {
$relationship = Relationship::where('account_id', auth()->user()->account_id)
->findOrFail($id);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new RelationshipResource($relationship);
}
/**
* Create a new relationship.
*
* @param Request $request
* @return RelationshipResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$relationship = app(CreateRelationship::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_is' => $request->input('contact_is'),
'of_contact' => $request->input('of_contact'),
'relationship_type_id' => $request->input('relationship_type_id'),
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return new RelationshipResource($relationship);
}
/**
* Update an existing relationship.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse|RelationshipResource
*/
public function update(Request $request, $relationshipId)
{
try {
$relationship = app(UpdateRelationship::class)->execute([
'account_id' => auth()->user()->account_id,
'relationship_id' => $relationshipId,
'relationship_type_id' => $request->input('relationship_type_id'),
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
$relationship->refresh();
return new RelationshipResource($relationship);
}
/**
* Delete a relationship.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $relationshipId)
{
try {
app(DestroyRelationship::class)->execute([
'account_id' => auth()->user()->account_id,
'relationship_id' => $relationshipId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return $this->respondObjectDeleted($relationshipId);
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\Relationship\RelationshipType;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\RelationshipType\RelationshipType as RelationshipTypeResource;
class ApiRelationshipTypeController extends ApiController
{
/**
* Get all relationship types in an instance.
*
* @param Request $request
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$relationshipTypes = auth()->user()->account->relationshipTypes()
->paginate($this->getLimitPerPage());
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return RelationshipTypeResource::collection($relationshipTypes);
}
/**
* Get the detail of a given relationship type.
*
* @param Request $request
* @return RelationshipTypeResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
try {
$relationshipType = RelationshipType::where('account_id', auth()->user()->account_id)
->findOrFail($id);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new RelationshipTypeResource($relationshipType);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Models\Relationship\RelationshipTypeGroup;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\RelationshipTypeGroup\RelationshipTypeGroup as RelationshipTypeGroupResource;
class ApiRelationshipTypeGroupController extends ApiController
{
/**
* Account ID column name.
*/
const ACCOUNT_ID = 'account_id';
/**
* Get all relationship type groups in an instance.
*
* @param Request $request
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$relationshipTypeGroups = auth()->user()->account->relationshipTypeGroups()
->paginate($this->getLimitPerPage());
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return RelationshipTypeGroupResource::collection($relationshipTypeGroups);
}
/**
* Get the detail of a given relationship type group.
*
* @param Request $request
* @return RelationshipTypeGroupResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
try {
$relationshipTypeGroup = RelationshipTypeGroup::where(static::ACCOUNT_ID, auth()->user()->account_id)
->where('id', $id)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new RelationshipTypeGroupResource($relationshipTypeGroup);
}
}

View File

@@ -0,0 +1,182 @@
<?php
namespace App\Http\Controllers\Api;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use App\Models\Contact\Contact;
use App\Models\Contact\Reminder;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Reminder\CreateReminder;
use App\Services\Contact\Reminder\UpdateReminder;
use App\Services\Contact\Reminder\DestroyReminder;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Reminder\Reminder as ReminderResource;
use App\Http\Resources\Reminder\ReminderOutbox as ReminderOutboxResource;
class ApiReminderController extends ApiController
{
/**
* Get the list of reminders.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$reminders = auth()->user()->account->reminders()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ReminderResource::collection($reminders);
}
/**
* Get the detail of a given reminder.
*
* @param Request $request
* @return ReminderResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $reminderId)
{
try {
$reminder = Reminder::where('account_id', auth()->user()->account_id)
->where('id', $reminderId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ReminderResource($reminder);
}
/**
* Store the reminder.
*
* @param Request $request
* @return ReminderResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$reminder = app(CreateReminder::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ReminderResource($reminder);
}
/**
* Update the reminder.
*
* @param Request $request
* @param int $reminderId
* @return ReminderResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $reminderId)
{
try {
$reminder = app(UpdateReminder::class)->execute(
$request->except(['account_id', 'reminder_id'])
+
[
'account_id' => auth()->user()->account_id,
'reminder_id' => $reminderId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ReminderResource($reminder);
}
/**
* Delete a reminder.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $reminderId)
{
try {
app(DestroyReminder::class)->execute([
'account_id' => auth()->user()->account_id,
'reminder_id' => $reminderId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($reminderId);
}
/**
* Get the list of reminders for the given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function reminders(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$reminders = $contact->reminders()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
return ReminderResource::collection($reminders);
}
/**
* Get the reminders for the month given in parameter.
* - 0 means current month
* - 1 means month+1
* - 2 means month+2...
*
* @param Request $request
* @param int $month
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function upcoming(Request $request, int $month = 0)
{
try {
$reminders = AccountHelper::getUpcomingRemindersForMonth(
auth()->user()->account,
$month
);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ReminderOutboxResource::collection($reminders);
}
}

View File

@@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Contact\Tag;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use App\Services\Contact\Tag\CreateTag;
use App\Services\Contact\Tag\UpdateTag;
use Illuminate\Database\QueryException;
use App\Services\Contact\Tag\DestroyTag;
use Illuminate\Database\Eloquent\Builder;
use App\Http\Resources\Tag\Tag as TagResource;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
use App\Http\Resources\Contact\ContactWithContactFields as ContactWithContactFieldsResource;
class ApiTagController extends ApiController
{
/**
* Get the list of the contacts.
* We will only retrieve the contacts that are "real", not the partials
* ones.
*
* @return AnonymousResourceCollection|JsonResponse
*/
public function index(Request $request)
{
try {
$tags = auth()->user()->account->tags()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return TagResource::collection($tags);
}
/**
* Get the detail of a given tag.
*
* @param Request $request
* @return TagResource|JsonResponse
*/
public function show(Request $request, $id)
{
try {
$tag = Tag::where('account_id', auth()->user()->account_id)
->where('id', $id)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new TagResource($tag);
}
/**
* Store the tag.
*
* @param Request $request
* @return TagResource|JsonResponse
*/
public function store(Request $request)
{
try {
$tag = app(CreateTag::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return new TagResource($tag);
}
/**
* Update the tag.
*
* @param Request $request
* @param int $id
* @return TagResource|JsonResponse
*/
public function update(Request $request, int $id)
{
try {
$tag = app(UpdateTag::class)->execute(
$request->except(['account_id', 'tag_id'])
+
[
'tag_id' => $id,
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return new TagResource($tag);
}
/**
* Delete a tag.
*
* @param Request $request
* @return JsonResponse
*/
public function destroy(Request $request, $id)
{
try {
app(DestroyTag::class)->execute([
'tag_id' => $id,
'account_id' => auth()->user()->account_id,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return $this->respondObjectDeleted($id);
}
/**
* Show all the contacts for a given tag.
*
* @param Request $request
* @param int $tagId
* @return JsonResponse|AnonymousResourceCollection
*/
public function contacts(Request $request, int $tagId)
{
try {
$contacts = auth()->user()->account->contacts()
->real()
->active()
->whereHas('tags', function (Builder $query) use ($tagId) {
$query->where('id', $tagId);
})
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ContactWithContactFieldsResource::collection($contacts);
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\Contact\Task;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Services\Task\CreateTask;
use App\Services\Task\UpdateTask;
use App\Services\Task\DestroyTask;
use Illuminate\Database\QueryException;
use Illuminate\Validation\ValidationException;
use App\Http\Resources\Task\Task as TaskResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiTaskController extends ApiController
{
/**
* Get the list of task.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$tasks = auth()->user()->account->tasks()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return TaskResource::collection($tasks);
}
/**
* Get the detail of a given task.
*
* @param Request $request
* @return TaskResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $taskId)
{
try {
$task = Task::where('account_id', auth()->user()->account_id)
->where('id', $taskId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new TaskResource($task);
}
/**
* Store the task.
*
* @param Request $request
* @return TaskResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$task = app(CreateTask::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => ($request->input('contact_id') == '' ? null : $request->input('contact_id')),
'title' => $request->input('title'),
'description' => ($request->input('description') == '' ? null : $request->input('description')),
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return new TaskResource($task);
}
/**
* Update the task.
*
* @param Request $request
* @param int $taskId
* @return TaskResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $taskId)
{
try {
$task = app(UpdateTask::class)->execute(
$request->except(['account_id', 'task_id'])
+
[
'task_id' => $taskId,
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return new TaskResource($task);
}
/**
* Delete a task.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $taskId)
{
try {
app(DestroyTask::class)->execute([
'task_id' => $taskId,
'account_id' => auth()->user()->account_id,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return $this->respondObjectDeleted($taskId);
}
/**
* Get the list of tasks for the given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function tasks(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$tasks = $contact->tasks()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
return TaskResource::collection($tasks);
}
}

View File

@@ -0,0 +1,212 @@
<?php
namespace App\Http\Controllers\Api\Auth;
use App\Models\User\User;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use function Safe\json_decode;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\App;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use App\Traits\JsonRespondController;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\Facades\Route;
use Barryvdh\Debugbar\Facades\Debugbar;
use Illuminate\Support\Facades\Redirect;
use Illuminate\Support\Facades\Validator;
use Illuminate\Contracts\Encryption\Encrypter;
use Symfony\Component\HttpFoundation\Response;
class OAuthController extends Controller
{
use JsonRespondController;
/**
* The encrypter implementation.
*
* @var \Illuminate\Contracts\Encryption\Encrypter
*/
protected $encrypter;
/**
* Create a new controller instance.
*
* @param \Illuminate\Contracts\Encryption\Encrypter $encrypter
* @return void
*/
public function __construct(Encrypter $encrypter)
{
$this->encrypter = $encrypter;
if (config('app.debug')) {
Debugbar::disable();
}
}
/**
* Display a log in form for oauth accessToken.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
$request->session()->flush();
return view('auth.oauthlogin');
}
/**
* Log in a user and returns an accessToken.
*
* @param Request $request
* @return \Symfony\Component\HttpFoundation\Response|null
*/
public function login(Request $request): ?Response
{
$isvalid = $this->validateRequest($request);
if ($isvalid !== true) {
return $isvalid;
}
$email = $request->input('email');
$password = $request->input('password');
if (Auth::attempt(['email' => $email, 'password' => $password])) {
// The user is active, not suspended, and exists.
$request->session()->put('oauth', true);
$request->session()->put('email', $email);
$request->session()->put('password', $this->encrypter->encrypt($password));
$this->fixRequest($request);
// add intendedUrl for WebAuthn
Redirect::setIntendedUrl(route('oauth.verify'));
return Route::respondWithRoute('oauth.verify');
}
return $this->respondUnauthorized();
}
/**
* Fix request parameters.
*
* @param Request $request
* @return void
*/
private function fixRequest(Request $request)
{
$request->setMethod('GET');
$cookie = $request->cookies->get(config('session.cookie'));
$request->cookies->set(config('session.cookie'), $this->encrypter->encrypt($cookie));
}
/**
* Validate the request.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse|true
*/
private function validateRequest(Request $request)
{
$validator = Validator::make($request->all(), [
'email' => 'email|required',
'password' => 'required',
]);
if ($validator->fails()) {
return $this->respondValidatorFailed($validator);
}
// Check if email exists. If not respond with an Unauthorized, this way a hacker
// doesn't know if the login email exist or not, or if the password is wrong
$count = User::where('email', $request->input('email'))->count();
if ($count === 0) {
return $this->respondUnauthorized();
}
return true;
}
/**
* Log in a user and returns an accessToken.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function verify(Request $request): JsonResponse
{
$response = $this->handleVerify($request);
Auth::logout();
$request->session()->flush();
return $response ?: $this->respondUnauthorized();
}
/**
* Handle the verify request.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse|null
*/
private function handleVerify(Request $request): ?JsonResponse
{
if (! $request->session()->has('email') || ! $request->session()->has('password')) {
return null;
}
$request->query->set('email', $request->session()->pull('email'));
$request->query->set('password', $this->encrypter->decrypt($request->session()->pull('password')));
$isvalid = $this->validateRequest($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$token = $this->proxy([
'username' => $request->input('email'),
'password' => $request->input('password'),
'grantType' => 'password',
]);
return $this->respond($token);
} catch (\Exception $e) {
return null;
}
}
/**
* Proxy a request to the OAuth server.
*
* @param array $data the data to send to the server
* @return array
*
* @throws \Safe\Exceptions\JsonException
*/
private function proxy(array $data = []): array
{
$url = App::runningUnitTests() ? Str::of(config('app.url'))->ltrim('/').'/oauth/token' : route('passport.token');
/** @var \Illuminate\Http\Response */
$response = app(Kernel::class)->handle(Request::create($url, 'POST', [
'grant_type' => $data['grantType'],
'client_id' => config('passport.password_grant_client.id'),
'client_secret' => config('passport.password_grant_client.secret'),
'username' => $data['username'],
'password' => $data['password'],
'scope' => '',
]));
$data = json_decode($response->content());
return [
'access_token' => $data->access_token,
'expires_in' => $data->expires_in,
];
}
}

View File

@@ -0,0 +1,156 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use App\Models\Contact\Address;
use App\Models\Contact\Contact;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Address\CreateAddress;
use App\Services\Contact\Address\UpdateAddress;
use App\Services\Contact\Address\DestroyAddress;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Address\Address as AddressResource;
class ApiAddressController extends ApiController
{
/**
* Get the list of addresses.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$addresses = auth()->user()->account->addresses()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return AddressResource::collection($addresses);
}
/**
* Get the detail of a given address.
*
* @param Request $request
* @return AddressResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $id)
{
try {
$address = Address::where('account_id', auth()->user()->account_id)
->where('id', $id)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new AddressResource($address);
}
/**
* Store the address.
*
* @param Request $request
* @return AddressResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$address = app(CreateAddress::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new AddressResource($address);
}
/**
* Update the address.
*
* @param Request $request
* @param int $addressId
* @return AddressResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $addressId)
{
try {
$address = app(UpdateAddress::class)->execute(
$request->except(['account_id', 'address_id'])
+
[
'account_id' => auth()->user()->account_id,
'address_id' => $addressId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new AddressResource($address);
}
/**
* Delete an address.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $addressId)
{
try {
app(DestroyAddress::class)->execute([
'account_id' => auth()->user()->account_id,
'address_id' => $addressId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($addressId);
}
/**
* Get the list of addresses for the given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function addresses(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$addresses = $contact->addresses()
->paginate($this->getLimitPerPage());
return AddressResource::collection($addresses);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use Illuminate\Http\JsonResponse;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\AuditLog\AuditLog as AuditLogResource;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class ApiAuditLogController extends ApiController
{
/**
* Get the list of the audit logs for the given contact.
*
* @param Request $request
* @param int $contactId
* @return JsonResponse|AnonymousResourceCollection
*/
public function index(Request $request, int $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
$logs = $contact->logs()
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return AuditLogResource::collection($logs);
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use App\Services\Contact\Avatar\UpdateAvatar;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Contact\Contact as ContactResource;
class ApiAvatarController extends ApiController
{
/**
* Update a contact's avatar.
*
* @param Request $request
* @param int $contactId
* @return ContactResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $contactId)
{
try {
$contact = app(UpdateAvatar::class)->execute(
$request->except(['account_id', 'contact_id'])
+
[
'account_id' => auth()->user()->account_id,
'contact_id' => $contactId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ContactResource($contact);
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use App\Models\Contact\Call;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use App\Models\Contact\Contact;
use Illuminate\Database\QueryException;
use App\Services\Contact\Call\CreateCall;
use App\Services\Contact\Call\UpdateCall;
use App\Services\Contact\Call\DestroyCall;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use App\Http\Resources\Call\Call as CallResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiCallController extends ApiController
{
/**
* Get the list of calls.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$calls = auth()->user()->account->calls()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return CallResource::collection($calls)->additional(['meta' => [
'statistics' => AccountHelper::getYearlyCallStatistics(auth()->user()->account),
]]);
}
/**
* Get the detail of a given call.
*
* @param Request $request
* @return CallResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $callId)
{
try {
$call = Call::where('account_id', auth()->user()->account_id)
->where('id', $callId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new CallResource($call);
}
/**
* Store the call.
*
* @param Request $request
* @return CallResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$call = app(CreateCall::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new CallResource($call);
}
/**
* Update a call.
*
* @param Request $request
* @param int $callId
* @return CallResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $callId)
{
try {
$call = app(UpdateCall::class)->execute(
$request->except(['account_id', 'call_id'])
+
[
'account_id' => auth()->user()->account_id,
'call_id' => $callId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new CallResource($call);
}
/**
* Delete a call.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $callId)
{
try {
app(DestroyCall::class)->execute([
'account_id' => auth()->user()->account_id,
'call_id' => $callId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($callId);
}
/**
* Get the list of calls for a given contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function calls(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$calls = $contact->calls()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
return CallResource::collection($calls)->additional(['meta' => [
'statistics' => AccountHelper::getYearlyCallStatistics(auth()->user()->account),
]]);
}
}

View File

@@ -0,0 +1,162 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Models\Contact\Conversation;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Contact\Conversation\CreateConversation;
use App\Services\Contact\Conversation\UpdateConversation;
use App\Services\Contact\Conversation\DestroyConversation;
use App\Http\Resources\Conversation\Conversation as ConversationResource;
class ApiConversationController extends ApiController
{
/**
* Get the list of conversations.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$conversations = auth()->user()->account->conversations()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ConversationResource::collection($conversations);
}
/**
* Get the list of conversations for a specific contact.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function conversations(Request $request, $contactId)
{
try {
Contact::where('account_id', auth()->user()->account_id)
->where('id', $contactId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
$conversations = auth()->user()->account->conversations()
->where('contact_id', $contactId)
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return ConversationResource::collection($conversations);
}
/**
* Get the detail of a given conversation.
*
* @param Request $request
* @return ConversationResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $conversationId)
{
try {
$conversation = Conversation::where('account_id', auth()->user()->account_id)
->findOrFail($conversationId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ConversationResource($conversation);
}
/**
* Store the conversation.
*
* @param Request $request
* @return ConversationResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$conversation = app(CreateConversation::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ConversationResource($conversation);
}
/**
* Update the conversation.
*
* @param Request $request
* @param int $conversationId
* @return ConversationResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $conversationId)
{
try {
$conversation = app(UpdateConversation::class)->execute(
$request->except(['account_id', 'conversation_id'])
+
[
'account_id' => auth()->user()->account_id,
'conversation_id' => $conversationId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ConversationResource($conversation);
}
/**
* Destroy the conversation.
*
* @param Request $request
* @param int $conversationId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $conversationId)
{
try {
app(DestroyConversation::class)->execute([
'account_id' => auth()->user()->account_id,
'conversation_id' => $conversationId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($conversationId);
}
}

View File

@@ -0,0 +1,146 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Models\Contact\Document;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Document\UploadDocument;
use App\Services\Contact\Document\DestroyDocument;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Document\Document as DocumentResource;
class ApiDocumentController extends ApiController
{
/**
* Instantiate a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('limitations')->only('store');
parent::__construct();
}
/**
* Get the list of documents.
*
* @param Request $request
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$documents = auth()->user()->account->documents()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return DocumentResource::collection($documents);
}
/**
* Get the list of documents for a specific contact.
*
* @param Request $request
* @param int $contactId
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function contact(Request $request, $contactId)
{
try {
Contact::where('account_id', auth()->user()->account_id)
->findOrFail($contactId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
$documents = auth()->user()->account->documents()
->where('contact_id', $contactId)
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return DocumentResource::collection($documents);
}
/**
* Get the detail of a given document.
*
* @param Request $request
* @param int $documentId
* @return DocumentResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $documentId)
{
try {
$document = Document::where('account_id', auth()->user()->account_id)
->findOrFail($documentId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new DocumentResource($document);
}
/**
* Store a document.
*
* @param Request $request
* @return DocumentResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$document = app(UploadDocument::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new DocumentResource($document);
}
/**
* Destroy a document.
*
* @param Request $request
* @param int $documentId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $documentId)
{
try {
app(DestroyDocument::class)->execute([
'account_id' => auth()->user()->account_id,
'document_id' => $documentId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($documentId);
}
}

View File

@@ -0,0 +1,121 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use App\Models\Contact\LifeEvent;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\LifeEvent\CreateLifeEvent;
use App\Services\Contact\LifeEvent\UpdateLifeEvent;
use App\Services\Contact\LifeEvent\DestroyLifeEvent;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\LifeEvent\LifeEvent as LifeEventResource;
class ApiLifeEventController extends ApiController
{
/**
* Get the list of life events.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request)
{
$lifeEvents = auth()->user()->account->lifeEvents()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
return LifeEventResource::collection($lifeEvents);
}
/**
* Get the detail of a given life event.
*
* @param Request $request
* @return LifeEventResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $lifeEventId)
{
try {
$lifeEvent = LifeEvent::where('account_id', auth()->user()->account_id)
->findOrFail($lifeEventId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new LifeEventResource($lifeEvent);
}
/**
* Store the life event.
*
* @param Request $request
* @return LifeEventResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$lifeEvent = app(CreateLifeEvent::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return new LifeEventResource($lifeEvent);
}
/**
* Update the life event.
*
* @param Request $request
* @param int $lifeEventId
* @return LifeEventResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $lifeEventId)
{
try {
$lifeEvent = app(UpdateLifeEvent::class)->execute(
$request->except(['account_id', 'life_event_id'])
+
[
'account_id' => auth()->user()->account_id,
'life_event_id' => $lifeEventId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
}
return new LifeEventResource($lifeEvent);
}
/**
* Destroy the life event.
*
* @param Request $request
* @param int $lifeEventId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $lifeEventId)
{
try {
app(DestroyLifeEvent::class)->execute([
'account_id' => auth()->user()->account_id,
'life_event_id' => $lifeEventId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return $this->respondObjectDeleted($lifeEventId);
}
}

View File

@@ -0,0 +1,126 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use App\Models\Contact\Message;
use App\Models\Contact\Conversation;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Conversation\UpdateMessage;
use App\Services\Contact\Conversation\DestroyMessage;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Services\Contact\Conversation\AddMessageToConversation;
use App\Http\Resources\Conversation\Conversation as ConversationResource;
class ApiMessageController extends ApiController
{
/**
* Store the message.
*
* @param Request $request
* @return ConversationResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request, int $conversationId)
{
try {
$conversation = Conversation::findOrFail($conversationId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
app(AddMessageToConversation::class)->execute(
$request->except(['account_id', 'conversation_id', 'contact_id'])
+
[
'account_id' => auth()->user()->account_id,
'conversation_id' => $conversation->id,
'contact_id' => $conversation->contact_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ConversationResource($conversation);
}
/**
* Update the message.
*
* @param Request $request
* @param int $conversationId
* @param int $messageId
* @return ConversationResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, int $conversationId, int $messageId)
{
try {
$conversation = Conversation::findOrFail($conversationId);
$message = Message::findOrFail($messageId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
app(UpdateMessage::class)->execute(
$request->except(['account_id', 'conversation_id', 'message_id', 'contact_id'])
+
[
'account_id' => auth()->user()->account_id,
'conversation_id' => $conversationId,
'message_id' => $message->id,
'contact_id' => $conversation->contact_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new ConversationResource($conversation);
}
/**
* Destroy the message.
*
* @param Request $request
* @param int $conversationId
* @param int $messageId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $conversationId, int $messageId)
{
try {
Conversation::findOrFail($conversationId);
Message::findOrFail($messageId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
app(DestroyMessage::class)->execute([
'account_id' => auth()->user()->account_id,
'conversation_id' => $conversationId,
'message_id' => $messageId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($messageId);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use App\Models\Contact\Occupation;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Occupation\CreateOccupation;
use App\Services\Contact\Occupation\UpdateOccupation;
use App\Services\Contact\Occupation\DestroyOccupation;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Occupation\Occupation as OccupationResource;
class ApiOccupationController extends ApiController
{
/**
* Get the list of occupations.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$occupations = auth()->user()->account->occupations()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return OccupationResource::collection($occupations);
}
/**
* Get the detail of a given occupation.
*
* @param Request $request
* @return OccupationResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $occupationId)
{
try {
$occupation = Occupation::where('account_id', auth()->user()->account_id)
->where('id', $occupationId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new OccupationResource($occupation);
}
/**
* Store the occupation.
*
* @param Request $request
* @return OccupationResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$occupation = app(CreateOccupation::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new OccupationResource($occupation);
}
/**
* Update an occupation.
*
* @param Request $request
* @param int $occupationId
* @return OccupationResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $occupationId)
{
try {
$occupation = app(UpdateOccupation::class)->execute(
$request->except(['account_id', 'occupation_id'])
+
[
'account_id' => auth()->user()->account_id,
'occupation_id' => $occupationId,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new OccupationResource($occupation);
}
/**
* Delete an occupation.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $occupationId)
{
try {
app(DestroyOccupation::class)->execute([
'account_id' => auth()->user()->account_id,
'occupation_id' => $occupationId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($occupationId);
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace App\Http\Controllers\Api\Contact;
use Illuminate\Http\Request;
use App\Models\Account\Photo;
use App\Models\Contact\Contact;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use App\Services\Account\Photo\UploadPhoto;
use App\Services\Account\Photo\DestroyPhoto;
use Illuminate\Validation\ValidationException;
use App\Http\Resources\Photo\Photo as PhotoResource;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class ApiPhotoController extends ApiController
{
/**
* Get the list of photos.
*
* @param Request $request
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
try {
$photos = auth()->user()->account->photos()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return PhotoResource::collection($photos);
}
/**
* Get the list of photos for a specific contact.
*
* @param Request $request
* @param int $contactId
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function contact(Request $request, $contactId)
{
try {
$contact = Contact::where('account_id', auth()->user()->account_id)
->findOrFail($contactId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
try {
$photos = $contact->photos()
->orderBy($this->sort, $this->sortDirection)
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return PhotoResource::collection($photos);
}
/**
* Get the detail of a given photo.
*
* @param Request $request
* @param int $photoId
* @return PhotoResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $photoId)
{
try {
$photo = Photo::where('account_id', auth()->user()->account_id)
->findOrFail($photoId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new PhotoResource($photo);
}
/**
* Store a photo.
*
* @param Request $request
* @return PhotoResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
try {
$photo = app(UploadPhoto::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return new PhotoResource($photo);
}
/**
* Destroy a photo.
*
* @param Request $request
* @param int $photoId
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, int $photoId)
{
try {
app(DestroyPhoto::class)->execute([
'account_id' => auth()->user()->account_id,
'photo_id' => $photoId,
]);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
} catch (ValidationException $e) {
return $this->respondValidatorFailed($e->validator);
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return $this->respondObjectDeleted($photoId);
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace App\Http\Controllers\Api\Misc;
use Illuminate\Http\Request;
use App\Helpers\CountriesHelper;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Api\ApiController;
use App\Http\Resources\Country\Country as CountryResource;
class ApiCountryController extends ApiController
{
/**
* Get the list of countries.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request)
{
$key = 'countries.'.App::getLocale();
$countries = Cache::rememberForever($key, function () {
return CountriesHelper::getAll();
});
return CountryResource::collection($countries);
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Api\Settings;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Database\QueryException;
use App\Http\Controllers\Api\ApiController;
use App\Http\Resources\AuditLog\AuditLog as AuditLogResource;
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
class ApiAuditLogController extends ApiController
{
/**
* Get the list of the audit logs.
*
* @param Request $request
* @return JsonResponse|AnonymousResourceCollection
*/
public function index(Request $request)
{
try {
$logs = auth()->user()->account->auditLogs()
->paginate($this->getLimitPerPage());
} catch (QueryException $e) {
return $this->respondInvalidQuery();
}
return AuditLogResource::collection($logs);
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Api\Settings;
use Illuminate\Http\Request;
use App\Models\Settings\Term;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Settings\Compliance\Compliance as ComplianceResource;
class ApiComplianceController extends ApiController
{
/**
* Get the list of terms and privacy policies.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request)
{
$terms = Term::orderBy('term_version', 'desc')->paginate($this->getLimitPerPage());
return ComplianceResource::collection($terms);
}
/**
* Get the detail of a given term.
*
* @param Request $request
* @return ComplianceResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $termId)
{
try {
$term = Term::where('id', $termId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ComplianceResource($term);
}
}

View File

@@ -0,0 +1,163 @@
<?php
namespace App\Http\Controllers\Api\Settings;
use Illuminate\Http\Request;
use Illuminate\Database\QueryException;
use App\Models\Contact\ContactFieldType;
use Illuminate\Support\Facades\Validator;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Settings\ContactFieldType\ContactFieldType as ContactFieldTypeResource;
class ApiContactFieldTypeController extends ApiController
{
/**
* Get the list of contact field types.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request)
{
$contactFieldTypes = auth()->user()->account->contactFieldTypes()
->paginate($this->getLimitPerPage());
return ContactFieldTypeResource::collection($contactFieldTypes);
}
/**
* Get the detail of a given contact field type.
*
* @param Request $request
* @return ContactFieldTypeResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $contactFieldTypeId)
{
try {
$contactFieldType = ContactFieldType::where('account_id', auth()->user()->account_id)
->where('id', $contactFieldTypeId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new ContactFieldTypeResource($contactFieldType);
}
/**
* Store the contactfieldtype.
*
* @param Request $request
* @return ContactFieldTypeResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request)
{
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
try {
$contactFieldType = ContactFieldType::create(
$request->except(['account_id'])
+ ['account_id' => auth()->user()->account_id]
);
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
return new ContactFieldTypeResource($contactFieldType);
}
/**
* Update the contact field type.
*
* @param Request $request
* @param int $contactFieldTypeId
* @return ContactFieldTypeResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, $contactFieldTypeId)
{
try {
$contactFieldType = ContactFieldType::where('account_id', auth()->user()->account_id)
->where('id', $contactFieldTypeId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$isvalid = $this->validateUpdate($request);
if ($isvalid !== true) {
return $isvalid;
}
// Update the contactfieldtype itself
try {
$contactFieldType->update(
$request->only([
'name',
'fontawesome_icon',
'protocol',
'delible',
'type',
])
);
} catch (QueryException $e) {
return $this->respondNotTheRightParameters();
}
return new ContactFieldTypeResource($contactFieldType);
}
/**
* Validate the request for update.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse|true
*/
private function validateUpdate(Request $request)
{
// Validates basic fields to create the entry
$validator = Validator::make($request->all(), [
'name' => 'required|max:255',
'fontawesome_icon' => 'nullable|max:255',
'protocol' => 'nullable|max:255',
'delible' => 'integer',
'type' => 'nullable|max:255',
]);
if ($validator->fails()) {
return $this->respondValidatorFailed($validator);
}
return true;
}
/**
* Delete an contactfieldtype.
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, $contactFieldTypeId)
{
try {
$contactFieldType = ContactFieldType::where('account_id', auth()->user()->account_id)
->where('id', $contactFieldTypeId)
->firstOrFail();
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
$contactFields = auth()->user()->account->contactFields
->where('contact_field_type_id', $contactFieldTypeId);
foreach ($contactFields as $contactField) {
$contactField->delete();
}
$contactFieldType->delete();
return $this->respondObjectDeleted($contactFieldType->id);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Api\Settings;
use Illuminate\Http\Request;
use App\Models\Settings\Currency;
use App\Http\Controllers\Api\ApiController;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use App\Http\Resources\Settings\Currency\Currency as CurrencyResource;
class ApiCurrencyController extends ApiController
{
/**
* Get the list of currencies.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request)
{
$currencies = Currency::paginate($this->getLimitPerPage());
return CurrencyResource::collection($currencies);
}
/**
* Get the detail of a given currency.
*
* @param Request $request
* @return CurrencyResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, $currencyId)
{
try {
$currency = Currency::findOrFail($currencyId);
} catch (ModelNotFoundException $e) {
return $this->respondNotFound();
}
return new CurrencyResource($currency);
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace App\Http\Controllers\Api\Statistics;
use App\Helpers\DateHelper;
use Illuminate\Http\Request;
use App\Models\Instance\Instance;
use App\Models\Instance\Statistic;
use App\Http\Controllers\Api\ApiController;
class ApiStatisticsController extends ApiController
{
/**
* Get the list of general, public statistics.
*
* @return \Illuminate\Support\Collection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request)
{
if (config('monica.allow_statistics_through_public_api_access') == false) {
return $this->respondNotFound();
}
// Collecting statistics
$statistic = Statistic::orderBy('created_at', 'desc')->first();
$instance = Instance::first();
// Get the date of the monday of last week
$dateMondayLastWeek = now()->subDays(7);
$dateMondayLastWeek = $dateMondayLastWeek->startOfWeek();
// Get the date of the sunday of last week
$dateSundayLastWeek = now()->subDays(7);
$dateSundayLastWeek = $dateSundayLastWeek->endOfWeek();
// Get the number of users last monday
$instanceLastMonday = Statistic::whereDate('created_at', '=', $dateMondayLastWeek->toDateString())->first();
$instanceLastSunday = Statistic::whereDate('created_at', '=', $dateSundayLastWeek->toDateString())->first();
$numberNewUsers = 0;
if ($instanceLastMonday && $instanceLastSunday) {
$numberNewUsers = $instanceLastSunday->number_of_users - $instanceLastMonday->number_of_users;
}
$statistics = collect();
$statistics->push([
'instance_creation_date' => DateHelper::getTimestamp($instance->created_at),
'number_of_contacts' => ($statistic ? $statistic->number_of_contacts : 0),
'number_of_users' => ($statistic ? $statistic->number_of_users : 0),
'number_of_activities' => ($statistic ? $statistic->number_of_activities : 0),
'number_of_reminders' => ($statistic ? $statistic->number_of_reminders : 0),
'number_of_new_users_last_week' => $numberNewUsers,
]);
return $statistics;
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Models\User\User;
use Illuminate\Http\Request;
use App\Services\User\EmailChange;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use App\Http\Requests\EmailChangeRequest;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class EmailChangeController extends Controller
{
use AuthenticatesUsers;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/settings/emailchange2';
/**
* Show the application's login form.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
*/
public function showLoginFormSpecial(Request $request)
{
$user = $request->user();
if ($user &&
$user instanceof User &&
! $user->hasVerifiedEmail()) {
return view('auth.emailchange1')
->with('email', $user->email);
}
return redirect()->route('login');
}
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request): \Illuminate\View\View
{
$user = auth()->user();
return view('auth.emailchange2')
->with('email', $user->email);
}
/**
* Change user email.
*
* @param EmailChangeRequest $request
* @return \Illuminate\Http\RedirectResponse
*/
public function save(EmailChangeRequest $request)
{
$response = $this->validateAndEmailChange($request);
return $response == 'auth.email_changed'
? $this->sendChangedResponse($response)
: $this->sendChangedFailedResponse($response);
}
/**
* Validate a password change request and update password of the user.
*
* @param EmailChangeRequest $request
* @return mixed
*/
protected function validateAndEmailChange(EmailChangeRequest $request)
{
$user = $request->user();
app(EmailChange::class)->execute([
'account_id' => $user->account_id,
'email' => $request->input('newmail'),
'user_id' => $user->id,
]);
// Logout the user
Auth::guard()->logout();
$request->session()->invalidate();
return 'auth.email_changed';
}
/**
* Get the response for a successful password changed.
*
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendChangedResponse($response)
{
return redirect()->route('login')
->with('status', trans($response));
}
/**
* Get the response for a failed password.
*
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendChangedFailedResponse($response)
{
return redirect()->route('login')
->withErrors(trans($response));
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
class ForgotPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset emails and
| includes a trait which assists in sending these notifications from
| your application to your users. Feel free to explore this trait.
|
*/
use SendsPasswordResetEmails;
}

View File

@@ -0,0 +1,150 @@
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Helpers\RequestHelper;
use App\Jobs\SendNewUserAlert;
use App\Services\User\CreateUser;
use App\Models\Account\Invitation;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Auth\Events\Registered;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RedirectsUsers;
use Illuminate\Validation\Rules\Password as PasswordRules;
class InvitationController extends Controller
{
use RedirectsUsers;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/dashboard';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Display the specified resource.
*
* @param string $key
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse
*/
public function show($key)
{
if (Auth::check()) {
return redirect()->route('loginRedirect');
}
$invitation = Invitation::where('invitation_key', $key)
->firstOrFail();
return view('settings.users.accept')
->withKey($key)
->withEmail($invitation->email);
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'last_name' => 'required|max:255',
'first_name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'email_security' => 'required',
'password' => ['required', 'confirmed', PasswordRules::defaults()],
'policy' => 'required',
]);
}
/**
* Store the specified resource.
*
* @param Request $request
* @param string $key
* @return null|\Illuminate\Http\RedirectResponse
*/
public function store(Request $request, $key)
{
$this->validator($request->all())->validate();
$invitation = Invitation::where('invitation_key', $key)
->firstOrFail();
// as a security measure, make sure that the new user provides the email
// of the person who has invited him/her.
if ($request->input('email_security') != $invitation->invitedBy->email) {
return redirect()->back()->withErrors(trans('settings.users_error_email_not_similar'))->withInput();
}
event(new Registered($user = $this->create($request->all(), $invitation)));
$invitation->delete();
/** @var \Illuminate\Contracts\Auth\StatefulGuard */
$guard = Auth::guard();
$guard->login($user);
$this->registered($request, $user);
return redirect($this->redirectPath());
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @param mixed $invitation
* @return \App\Models\User\User
*/
protected function create(array $data, $invitation)
{
$user = app(CreateUser::class)->execute([
'account_id' => $invitation->account_id,
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'email' => $data['email'],
'password' => $data['password'],
'locale' => $invitation->invitedBy->locale,
'ip_address' => RequestHelper::ip(),
]);
$user->invited_by_user_id = $invitation->invited_by_user_id;
$user->save();
// send me an alert
SendNewUserAlert::dispatch($user);
return $user;
}
/**
* The user has been registered.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return void
*/
protected function registered(Request $request, $user)
{
if (! config('monica.signup_double_optin')) {
// if signup_double_optin is disabled, skip the confirm email part
$user->markEmailAsVerified();
}
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Helpers\InstanceHelper;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\AuthenticatesUsers;
class LoginController extends Controller
{
/*
|--------------------------------------------------------------------------
| Login Controller
|--------------------------------------------------------------------------
|
| This controller handles authenticating users for the application and
| redirecting them to your home screen. The controller uses a trait
| to conveniently provide its functionality to your applications.
|
*/
use AuthenticatesUsers;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/dashboard';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest', ['except' => 'logout']);
}
public function showLoginOrRegister()
{
$first = ! InstanceHelper::hasAtLeastOneAccount();
if ($first) {
return redirect()->route('register');
}
return $this->showLoginForm();
}
}

View File

@@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Models\User\User;
use Illuminate\Support\Str;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Auth\Events\PasswordReset;
use App\Http\Requests\PasswordChangeRequest;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Foundation\Auth\RedirectsUsers;
use Illuminate\Contracts\Auth\CanResetPassword;
class PasswordChangeController extends Controller
{
use RedirectsUsers;
/**
* @var string
*/
protected $redirectTo = '/settings/security';
/**
* Get useful parameters from request.
*
* @param \App\Http\Requests\PasswordChangeRequest $request
* @return array
*/
protected function credentials(PasswordChangeRequest $request)
{
return $request->only(
'password_current', 'password', 'password_confirmation'
);
}
/**
* Change user password.
*
* @param \App\Http\Requests\PasswordChangeRequest $request
*/
public function passwordChange(PasswordChangeRequest $request)
{
$credentials = $this->credentials($request);
$response = $this->validateAndPasswordChange($credentials);
return $response === 'passwords.changed'
? $this->sendChangedResponse($response)
: $this->sendChangedFailedResponse($response);
}
/**
* Validate a password change request and update password of the user.
*
* @param array $credentials
* @return string|Authenticatable
*/
protected function validateAndPasswordChange($credentials)
{
$user = $this->validateChange($credentials);
if (! $user instanceof CanResetPassword) {
return $user;
}
if ($user instanceof User) {
$this->setNewPassword($user, $credentials['password']);
}
return 'passwords.changed';
}
/**
* Validate a password change request with the given credentials.
*
* @param array $credentials
* @return string|Authenticatable
*
* @throws \UnexpectedValueException
*/
protected function validateChange(array $credentials)
{
if (is_null($user = $this->getUser($credentials))) {
return 'passwords.invalid';
}
return $user;
}
/**
* Get the user with the given credentials.
*
* @param array $credentials
* @return null|Authenticatable
*/
protected function getUser(array $credentials): ?Authenticatable
{
/** @var User */
$user = Auth::user();
// Using current email from user, and current password sent with the request to authenticate the user
if (! Auth::attempt([
'email' => $user->getEmailForPasswordReset(),
'password' => $credentials['password_current'],
])) {
// authentication fails
return null;
}
return $user;
}
/**
* Set the new password if all validation has passed.
*
* @param User $user
* @param string $password
* @return void
*/
protected function setNewPassword($user, $password)
{
$user->password = Hash::make($password);
$user->setRememberToken(Str::random(60));
$user->save();
event(new PasswordReset($user));
Auth::guard()->login($user);
}
/**
* Get the response for a successful password change.
*
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendChangedResponse($response)
{
return redirect($this->redirectPath())
->with('status', trans($response));
}
/**
* Get the response for a failed password change.
*
* @param string $response
* @return \Illuminate\Http\RedirectResponse
*/
protected function sendChangedFailedResponse($response)
{
return redirect($this->redirectPath())
->withErrors(['password' => trans($response)]);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Events\RecoveryLogin;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RedirectsUsers;
class RecoveryLoginController extends Controller
{
use RedirectsUsers;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/dashboard';
/**
* Display a listing of the resource.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function get(Request $request)
{
return view('auth.recovery.login');
}
/**
* Validate recovery login.
*
* @param Request $request
* @return \Illuminate\Routing\Redirector|\Illuminate\Http\RedirectResponse
*/
public function store(Request $request)
{
Validator::make($request->all(), [
'recovery' => 'required',
])->validate();
$user = auth()->user();
$recovery = $request->input('recovery');
if ($user instanceof \App\Models\User\User &&
$user->recoveryChallenge($recovery)) {
$this->fireLoginEvent($user);
} else {
abort(403);
}
return redirect($this->redirectPath());
}
/**
* Fire the login event.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @return void
*/
protected function fireLoginEvent($user)
{
Event::dispatch(new RecoveryLogin($user));
}
}

View File

@@ -0,0 +1,140 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Models\User\User;
use Illuminate\Http\Request;
use App\Helpers\LocaleHelper;
use App\Helpers\RequestHelper;
use App\Jobs\SendNewUserAlert;
use App\Helpers\InstanceHelper;
use App\Models\Account\Account;
use Illuminate\Support\Facades\Log;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Validation\Rules\Password as PasswordRules;
class RegisterController extends Controller
{
/*
|--------------------------------------------------------------------------
| Register Controller
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users as well as their
| validation and creation. By default this controller uses a trait to
| provide this functionality without requiring any additional code.
|
*/
use RegistersUsers;
/**
* Where to redirect users after login / registration.
*
* @var string
*/
protected $redirectTo = '/dashboard';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Show the application registration form.
*
* @return \Illuminate\View\View
*/
public function showRegistrationForm(Request $request)
{
$first = ! InstanceHelper::hasAtLeastOneAccount();
if (config('monica.disable_signup') == 'true' && ! $first) {
abort(403, trans('auth.signup_disabled'));
}
return view('auth.register')
->withFirst($first)
->withLocales(LocaleHelper::getLocaleList()->sortByCollator('lang'));
}
/**
* Get a validator for an incoming registration request.
*
* @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
{
return Validator::make($data, [
'last_name' => 'required|max:255',
'first_name' => 'required|max:255',
'email' => 'required|email|max:255|unique:users',
'password' => ['required', 'confirmed', PasswordRules::defaults()],
'policy' => 'required',
]);
}
/**
* Create a new user instance after a valid registration.
*
* @param array $data
* @return User|null
*/
protected function create(array $data): ?User
{
$first = ! InstanceHelper::hasAtLeastOneAccount();
if (config('monica.disable_signup') == 'true' && ! $first) {
abort(403, trans('auth.signup_disabled'));
}
try {
$account = Account::createDefault(
$data['first_name'],
$data['last_name'],
$data['email'],
$data['password'],
RequestHelper::ip(),
$data['lang']
);
/** @var User */
$user = $account->users()->first();
if (! $first) {
// send me an alert
SendNewUserAlert::dispatch($user);
}
return $user;
} catch (\Exception $e) {
Log::error($e);
abort(500, trans('auth.signup_error'));
}
}
/**
* The user has been registered.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function registered(Request $request, $user)
{
if (! is_null($user)) {
/** @var int $count */
$count = Account::count();
if (! config('monica.signup_double_optin') || $count == 1) {
// if signup_double_optin is disabled, skip the confirm email part
$user->markEmailAsVerified();
}
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Validation\Rules\Password as PasswordRules;
class ResetPasswordController extends Controller
{
/*
|--------------------------------------------------------------------------
| Password Reset Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling password reset requests
| and uses a simple trait to include this behavior. You're free to
| explore this trait and override any methods you wish to tweak.
|
*/
use ResetsPasswords;
/**
* @var string
*/
protected $redirectTo = '/dashboard';
/**
* Get the password reset validation rules.
*
* @return array
*/
protected function rules()
{
return [
'token' => 'required',
'email' => 'required|email',
'password' => ['required', 'confirmed', PasswordRules::defaults()],
];
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace App\Http\Controllers\Auth;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Route;
use PragmaRX\Google2FALaravel\Facade as Google2FA;
class Validate2faController extends Controller
{
/**
* Redirect the user after 2fa form has been submitted.
*
* @param \Illuminate\Http\Request $request
* @return \Symfony\Component\HttpFoundation\Response|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\Http\JsonResponse|\Illuminate\Http\Response
*/
public function index(Request $request)
{
if ($request->session()->get('oauth')) {
return Route::respondWithRoute('oauth.verify');
}
if ($request->has('url')) {
return redirect(urldecode($request->input('url')));
}
return redirect()->route('login');
}
public static function loginCallback()
{
app('pragmarx.google2fa')->setStateless(false);
Google2FA::login();
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\VerifiesEmails;
class VerificationController extends Controller
{
/*
|--------------------------------------------------------------------------
| Email Verification Controller
|--------------------------------------------------------------------------
|
| This controller is responsible for handling email verification for any
| user that recently registered with the application. Emails may also
| be re-sent if the user didn't receive the original email message.
|
*/
use VerifiesEmails;
/**
* Where to redirect users after verification.
*
* @var string
*/
protected $redirectTo = '/dashboard';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('auth');
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Helpers\InstanceHelper;
class ChangelogController extends Controller
{
/**
* Display a listing of the resource.
*
* @param Request $request
* @return \Illuminate\View\View
*/
public function index(Request $request)
{
$changelogs = InstanceHelper::getChangelogEntries();
return view('changelog.index')->withChangelogs($changelogs);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
use Illuminate\Http\Request;
use App\Services\User\AcceptPolicy;
use Illuminate\Http\RedirectResponse;
use Illuminate\Contracts\View\Factory;
class ComplianceController extends Controller
{
/**
* Display a listing of the resource.
*
* @param Request $request
* @return View|Factory
*/
public function index(Request $request)
{
return view('compliance.index');
}
/**
* @param Request $request
* @return RedirectResponse
*/
public function store(Request $request)
{
app(AcceptPolicy::class)->execute([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
'ip_address' => \Request::ip(),
]);
return redirect()->route('dashboard.index');
}
}

View File

@@ -0,0 +1,207 @@
<?php
namespace App\Http\Controllers\Contacts;
use Carbon\Carbon;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use App\Models\Contact\Contact;
use App\Models\Account\Activity;
use Illuminate\Support\Collection;
use App\Http\Controllers\Controller;
use App\Models\Account\ActivityType;
use App\Traits\JsonRespondController;
use App\Services\Account\Activity\Activity\CreateActivity;
use App\Services\Account\Activity\Activity\UpdateActivity;
use App\Services\Account\Activity\Activity\DestroyActivity;
use App\Services\Account\Activity\ActivityStatisticService;
use App\Http\Resources\Activity\Activity as ActivityResource;
class ActivitiesController extends Controller
{
use JsonRespondController;
/**
* Get the list of activities.
*
* @param Request $request
* @param Contact $contact
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*/
public function index(Request $request, Contact $contact)
{
$activities = $contact->activities()
->orderBy('happened_at', 'desc')
->limit(10)
->get();
return ActivityResource::collection($activities)->additional(['meta' => [
'statistics' => AccountHelper::getYearlyActivitiesStatistics($contact->account),
]]);
}
/**
* Get the list of contacts available to associate the activity with
* participants.
* We could have chosen to query `/people` to get the full list of contacts
* but some accounts have thousands of contacts. Thus for performance
* purposes we have to create our own collection containing just the
* necessary information.
* Also we need to filter out the current contact from the list.
*
* @param Request $request
* @param Contact $contact
* @return Collection
*/
public function contacts(Request $request, Contact $contact)
{
return auth()->user()->account->contacts
->filter(function ($c) use ($contact) {
return $contact->id !== $c->id;
})
->map(function (Contact $c): array {
return [
'id' => $c->id,
'name' => $c->name,
];
});
}
/**
* Get the list of activity categories.
*
* @param Request $request
* @return Collection
*/
public function categories(Request $request)
{
$categories = auth()->user()->account->activityTypeCategories;
$array = collect([]);
foreach ($categories as $category) {
$types = ActivityType::where('activity_type_category_id', $category->id)->get();
$typeCollection = collect([]);
foreach ($types as $type) {
$typeCollection->push([
'id' => $type->id,
'name' => $type->name,
]);
}
$array->push([
'id' => $category->id,
'name' => $category->name,
'types' => $typeCollection,
]);
}
return $array;
}
/**
* Display the summary of activities for a given contact.
*
* @param Request $request
* @param Contact $contact
* @return \Illuminate\Http\RedirectResponse
*/
public function summary(Request $request, Contact $contact)
{
// get the year of the most recent activity done with the contact
$year = $contact->activities->sortByDesc('happened_at')
->first()
->happened_at
->year;
return redirect()->route('people.activities.year', [$contact, $year]);
}
/**
* Get all the activities for this contact for a specific year.
*/
public function year(ActivityStatisticService $activityStatisticService, Contact $contact, int $year)
{
$startDate = Carbon::create($year, 1, 1);
$endDate = Carbon::create($year, 12, 31);
$activitiesLastTwelveMonths = $activityStatisticService
->activitiesWithContactInTimeRange($contact, now()->subMonths(12), now())
->count();
$uniqueActivityTypes = $activityStatisticService
->uniqueActivityTypesInTimeRange($contact, $startDate, $endDate);
$activitiesPerYear = $activityStatisticService->activitiesPerYearWithContact($contact);
$activitiesPerMonthForYear = $activityStatisticService
->activitiesPerMonthForYear($contact, $year)
->sortByDesc('month');
return view('people.activities.year')
->withTotalActivities($contact->activities->count())
->withActivitiesLastTwelveMonths($activitiesLastTwelveMonths)
->withUniqueActivityTypes($uniqueActivityTypes)
->withActivitiesPerYear($activitiesPerYear)
->withActivitiesPerMonthForYear($activitiesPerMonthForYear)
->withYear($year)
->withContact($contact);
}
/**
* Store the activity.
*
* @param Request $request
* @return \Illuminate\Contracts\Support\Responsable
*/
public function store(Request $request)
{
$activity = app(CreateActivity::class)->execute(
$request->except(['account_id'])
+
[
'account_id' => auth()->user()->account_id,
]
);
return new ActivityResource($activity);
}
/**
* Update the activity.
*
* @param Request $request
* @param Activity $activity
* @return \Illuminate\Contracts\Support\Responsable
*/
public function update(Request $request, Activity $activity)
{
$activity = app(UpdateActivity::class)->execute(
$request->except(['account_id', 'activity_id'])
+
[
'account_id' => auth()->user()->account_id,
'activity_id' => $activity->id,
]
);
return new ActivityResource($activity);
}
/**
* Delete an activity.
*
* @param Request $request
* @param Activity $activity
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, Activity $activity)
{
app(DestroyActivity::class)->execute([
'account_id' => auth()->user()->account_id,
'activity_id' => $activity->id,
]);
return $this->respondObjectDeleted($activity->id);
}
}

View File

@@ -0,0 +1,144 @@
<?php
namespace App\Http\Controllers\Contacts;
use Illuminate\Http\Request;
use App\Models\Contact\Address;
use App\Models\Contact\Contact;
use App\Helpers\CountriesHelper;
use Illuminate\Support\Facades\App;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use Illuminate\Support\Facades\Cache;
use App\Services\Contact\Address\CreateAddress;
use App\Services\Contact\Address\UpdateAddress;
use App\Services\Contact\Address\DestroyAddress;
class AddressesController extends Controller
{
use JsonRespondController;
/**
* Get all the addresses for this contact.
*/
public function index(Contact $contact)
{
$addresses = collect([]);
foreach ($contact->addresses as $address) {
$addresses->push($this->addressObject($address));
}
return $addresses;
}
/**
* Get all the countries.
*/
public function getCountries()
{
$key = 'countries.'.App::getLocale();
$countries = Cache::rememberForever($key, function () {
return CountriesHelper::getAll();
});
return response()->json($countries->all());
}
/**
* Store the address.
*/
public function store(Request $request, Contact $contact)
{
$datas = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
] + $request->only([
'name',
'country',
'street',
'city',
'province',
'postal_code',
'latitude',
'longitude',
]);
$address = app(CreateAddress::class)->execute($datas);
return $this->setHTTPStatusCode(201)
->respond($this->addressObject($address));
}
/**
* Edit the contact field.
*/
public function edit(Request $request, Contact $contact, Address $address)
{
$datas = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'address_id' => $address->id,
] + $request->only([
'name',
'country',
'street',
'city',
'province',
'postal_code',
'latitude',
'longitude',
]);
$address = app(UpdateAddress::class)->execute($datas);
return $this->respond($this->addressObject($address));
}
/**
* Destroy the address.
*
* @param Request $request
* @param Contact $contact
* @param Address $address
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, Contact $contact, Address $address)
{
$datas = [
'account_id' => auth()->user()->account_id,
'address_id' => $address->id,
];
if (app(DestroyAddress::class)->execute($datas)) {
return $this->respondObjectDeleted($address->id);
}
return $this->setHTTPStatusCode(400)
->setErrorCode(32)
->respondWithError();
}
private function addressObject($address)
{
$place = $address->place;
return [
'id' => $address->id,
'name' => $address->name,
'googleMapAddress' => $place->getGoogleMapAddress(),
'googleMapAddressLatitude' => $place->getGoogleMapsAddressWithLatitude(),
'address' => $place->getAddressAsString(),
'country' => $place->country,
'country_name' => $place->country_name,
'street' => $place->street,
'city' => $place->city,
'province' => $place->province,
'postal_code' => $place->postal_code,
'latitude' => $place->latitude,
'longitude' => $place->longitude,
'edit' => false,
];
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Contacts;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Validator;
use App\Services\Account\Photo\UploadPhoto;
use App\Services\Contact\Avatar\UpdateAvatar;
class AvatarController extends Controller
{
/**
* Display the Edit avatar screen.
*/
public function edit(Contact $contact)
{
$contact->throwInactive();
return view('people.avatar.edit')
->withContact($contact);
}
/**
* Update the avatar of the contact.
*
* @param Request $request
* @param Contact $contact
*/
public function update(Request $request, Contact $contact)
{
// update the avatar
$data = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'source' => $request->input('avatar'),
];
switch ($request->input('avatar')) {
case 'upload':
// if it's a new photo, we need to upload it
$validator = Validator::make($request->all(), [
'file' => 'image|max:'.config('monica.max_upload_size'),
]);
if ($validator->fails()) {
return back()
->withInput()
->withErrors($validator);
}
$photo = app(UploadPhoto::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'photo' => $request->photo,
]);
$data['photo_id'] = $photo->id;
$data['source'] = 'photo';
break;
case 'photo':
$data['photo_id'] = $contact->avatar_photo_id;
break;
}
app(UpdateAvatar::class)->execute($data);
return redirect()->route('people.show', $contact)
->with('success', trans('people.information_edit_success'));
}
/**
* Set the given photo as avatar.
*
* @param Request $request
* @param Contact $contact
* @param int $photoId
*/
public function photo(Request $request, Contact $contact, $photoId)
{
// update the avatar
$data = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'source' => 'photo',
'photo_id' => $photoId,
];
return app(UpdateAvatar::class)->execute($data);
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Helpers\DateHelper;
use App\Models\Contact\Call;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Services\Contact\Call\CreateCall;
use App\Services\Contact\Call\UpdateCall;
use App\Services\Contact\Call\DestroyCall;
use App\Http\Resources\Call\Call as CallResource;
class CallsController extends Controller
{
use JsonRespondController;
/**
* Display the list of calls.
*
* @param Contact $contact
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request, Contact $contact)
{
$calls = $contact->calls()->get();
return CallResource::collection($calls);
}
/**
* Display the timestamp of the last phone contact.
*
* @param Contact $contact
* @return JsonResponse
*/
public function lastCalled(Contact $contact): JsonResponse
{
$lastTalkedTo = $contact->last_talked_to;
if ($lastTalkedTo !== null) {
$lastTalkedTo = DateHelper::getShortDate($contact->last_talked_to);
}
return $this->respond([
'last_talked_to' => $lastTalkedTo,
]);
}
/**
* Store a call.
*
* @param Contact $contact
* @return Call
*/
public function store(Request $request, Contact $contact)
{
return app(CreateCall::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'content' => $request->input('content'),
'called_at' => $request->input('called_at'),
'contact_called' => $request->input('contact_called'),
'emotions' => $request->input('emotions'),
]);
}
/**
* Update a call.
*
* @param Contact $contact
* @param Call $call
* @return Call
*/
public function update(Request $request, Contact $contact, Call $call)
{
return app(UpdateCall::class)->execute([
'account_id' => auth()->user()->account_id,
'call_id' => $call->id,
'content' => $request->input('content'),
'called_at' => $request->input('called_at'),
'contact_called' => $request->input('contact_called'),
'emotions' => $request->input('emotions'),
]);
}
/**
* Delete the call.
*
* @param Request $request
* @param Contact $contact
* @param Call $call
* @return null|\Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, Contact $contact, Call $call): ?JsonResponse
{
$data = [
'account_id' => auth()->user()->account_id,
'call_id' => $call->id,
];
if (app(DestroyCall::class)->execute($data)) {
return $this->respondObjectDeleted($call->id);
}
return null;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Helpers\AuditLogHelper;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
class ContactAuditLogController extends Controller
{
/**
* Display the page listing all the audit logs.
*/
public function index(Contact $contact)
{
$logs = $contact->logs()
->with('author')
->orderBy('created_at', 'desc')
->paginate(15);
return view('people.auditlogs.index')
->withContact($contact)
->withLogsCollection(AuditLogHelper::getCollectionOfAudits($logs))
->withLogsPagination($logs);
}
}

View File

@@ -0,0 +1,92 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
use App\Models\Contact\ContactField;
use App\Jobs\Avatars\GetAvatarsFromInternet;
use App\Http\Requests\People\ContactFieldsRequest;
class ContactFieldsController extends Controller
{
/**
* Get all the contact information for this contact.
*/
public function getContactFields(Contact $contact)
{
$contactInformationData = collect([]);
foreach ($contact->contactFields as $contactField) {
$data = [
'id' => $contactField->id,
'data' => $contactField->data,
'name' => $contactField->contactFieldType->name,
'fontawesome_icon' => (is_null($contactField->contactFieldType->fontawesome_icon) ? null : $contactField->contactFieldType->fontawesome_icon),
'protocol' => (is_null($contactField->contactFieldType->protocol) ? null : $contactField->contactFieldType->protocol),
'contact_field_type_id' => $contactField->contact_field_type_id,
'edit' => false,
];
$contactInformationData->push($data);
}
return $contactInformationData;
}
/**
* Get all the contact field types.
*
* @param Contact $contact
*/
public function getContactFieldTypes(Contact $contact)
{
return auth()->user()->account->contactFieldTypes;
}
/**
* Store the contact field.
*/
public function storeContactField(ContactFieldsRequest $request, Contact $contact)
{
$contactField = $contact->contactFields()->create(
$request->only([
'contact_field_type_id',
'data',
])
+ [
'account_id' => auth()->user()->account_id,
]
);
GetAvatarsFromInternet::dispatch($contact);
return $contactField;
}
/**
* Edit the contact field.
*/
public function editContactField(ContactFieldsRequest $request, Contact $contact, ContactField $contactField)
{
$contactField->update(
$request->only([
'contact_field_type_id',
'data',
])
+ [
'account_id' => auth()->user()->account_id,
]
);
GetAvatarsFromInternet::dispatch($contact);
return $contactField;
}
public function destroyContactField(Contact $contact, ContactField $contactField)
{
$contactField->delete();
GetAvatarsFromInternet::dispatch($contact);
}
}

View File

@@ -0,0 +1,289 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Helpers\DateHelper;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use Illuminate\Support\Collection;
use App\Http\Controllers\Controller;
use App\Models\Contact\Conversation;
use App\Traits\JsonRespondController;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Conversation\DestroyMessage;
use App\Services\Contact\Conversation\CreateConversation;
use App\Services\Contact\Conversation\UpdateConversation;
use App\Services\Contact\Conversation\DestroyConversation;
use App\Services\Contact\Conversation\AddMessageToConversation;
class ConversationsController extends Controller
{
use JsonRespondController;
/**
* Display the Create conversation page.
*
* @param Contact $contact
* @return \Illuminate\View\View
*/
public function create(Request $request, Contact $contact)
{
return view('people.conversations.new')
->withContact($contact)
->withContactFieldTypes(auth()->user()->account->contactFieldTypes);
}
/**
* Display the list of conversations.
*
* @param Contact $contact
* @return Collection
*/
public function index(Request $request, Contact $contact)
{
$conversationsCollection = collect([]);
$conversations = $contact->conversations()->get();
foreach ($conversations as $conversation) {
$message = $conversation->messages->last();
$data = [
'id' => $conversation->id,
'message_count' => $conversation->messages->count(),
'contact_field_type' => $conversation->contactFieldType->name,
'icon' => $conversation->contactFieldType->fontawesome_icon,
'content' => ! is_null($message) ? mb_strimwidth($message->content, 0, 50, '…') : '',
'happened_at' => DateHelper::getShortDate($conversation->happened_at),
'route' => route('people.conversations.edit', [$contact, $conversation]),
];
$conversationsCollection->push($data);
}
return $conversationsCollection;
}
/**
* Store the conversation.
*
* @param Request $request
* @param Contact $contact
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request, Contact $contact)
{
$data = $this->validateAndGetDatas($request);
if ($data instanceof \Illuminate\Contracts\Validation\Validator) {
return back()
->withInput()
->withErrors($data);
}
$date = $data['happened_at'];
$data['contact_id'] = $contact->id;
// create the conversation
try {
$conversation = app(CreateConversation::class)->execute($data);
} catch (ValidationException $e) {
return back()
->withInput()
->withErrors($e->validator);
} catch (\Exception $e) {
return back()
->withInput()
->withErrors(trans('app.error_save'));
}
// add the messages to the conversation
$result = $this->updateMessages($request, $conversation, $date);
if ($result !== true) {
return back()
->withInput()
->withErrors($result);
}
return redirect()->route('people.show', $contact)
->with('success', trans('people.conversation_add_success'));
}
/**
* Display a specific conversation.
*
* @param Contact $contact
* @return \Illuminate\View\View
*/
public function edit(Request $request, Contact $contact, Conversation $conversation)
{
$contact->throwInactive();
// preparing the messages for the Vue component
$messages = collect([]);
foreach ($conversation->messages as $message) {
$messages->push([
'uid' => $message->id,
'content' => $message->content,
'author' => ($message->written_by_me ? 'me' : 'other'),
]);
}
return view('people.conversations.edit')
->withContact($contact)
->withConversation($conversation)
->withMessages($messages)
->withContactFieldTypes(auth()->user()->account->contactFieldTypes);
}
/**
* Update the conversation.
*
* @param Request $request
* @param Contact $contact
* @param Conversation $conversation
* @return \Illuminate\Http\RedirectResponse
*/
public function update(Request $request, Contact $contact, Conversation $conversation)
{
$data = $this->validateAndGetDatas($request);
if ($data instanceof \Illuminate\Contracts\Validation\Validator) {
return back()
->withInput()
->withErrors($data);
}
$date = $data['happened_at'];
$data['conversation_id'] = $conversation->id;
// update the conversation
try {
$conversation = app(UpdateConversation::class)->execute($data);
} catch (ValidationException $e) {
return back()
->withInput()
->withErrors($e->validator);
} catch (\Exception $e) {
return back()
->withInput()
->withErrors(trans('app.error_save'));
}
// delete all current messages
foreach ($conversation->messages as $message) {
$data = [
'account_id' => auth()->user()->account_id,
'conversation_id' => $conversation->id,
'message_id' => $message->id,
];
app(DestroyMessage::class)->execute($data);
}
// and create all new ones
$result = $this->updateMessages($request, $conversation, $date);
if ($result !== true) {
return back()
->withInput()
->withErrors($result);
}
return redirect()->route('people.show', $contact)
->with('success', trans('people.conversation_edit_success'));
}
/**
* Validate datas and get an array for create or update a conversation.
*
* @param Request $request
* @return array|\Illuminate\Contracts\Validation\Validator
*/
private function validateAndGetDatas(Request $request)
{
$validator = Validator::make($request->all(), [
'conversationDateRadio' => 'required',
'conversationDate' => 'required_unless:conversationDateRadio,today,yesterday',
'messages' => 'required',
'contactFieldTypeId' => 'required|integer|exists:contact_field_types,id',
], [
'messages.required' => trans('people.conversation_add_error'),
]);
if ($validator->fails()) {
return $validator;
}
// find out what the date is
$chosenDate = $request->input('conversationDateRadio');
if ($chosenDate == 'today') {
$date = DateHelper::getDate(now($request->user()->timezone));
} elseif ($chosenDate == 'yesterday') {
$date = DateHelper::getDate(now($request->user()->timezone)->subDay());
} else {
$date = $request->input('conversationDate');
}
return [
'account_id' => auth()->user()->account_id,
'happened_at' => $date,
'contact_field_type_id' => $request->input('contactFieldTypeId'),
];
}
/**
* Update messages for conversation.
*
* @param Request $request
* @param Conversation $conversation
* @param string $date
* @return bool|string|\Illuminate\Contracts\Validation\Validator
* @psalm-return bool|array|string|\Illuminate\Contracts\Validation\Validator
*/
private function updateMessages(Request $request, Conversation $conversation, string $date)
{
$messages = explode(',', $request->input('messages'));
foreach ($messages as $messageId) {
$data = [
'account_id' => auth()->user()->account_id,
'conversation_id' => $conversation->id,
'contact_id' => $conversation->contact_id,
'written_at' => $date,
'written_by_me' => ($request->input('who_wrote_'.$messageId) === 'me'),
'content' => $request->input('content_'.$messageId),
];
try {
app(AddMessageToConversation::class)->execute($data);
} catch (ValidationException $e) {
return $e->validator;
} catch (\Exception $e) {
return trans('app.error_save');
}
}
return true;
}
/**
* Delete the conversation.
*
* @param Request $request
* @param Contact $contact
* @param Conversation $conversation
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, Contact $contact, Conversation $conversation)
{
$data = [
'account_id' => auth()->user()->account_id,
'conversation_id' => $conversation->id,
];
try {
app(DestroyConversation::class)->execute($data);
} catch (\Exception $e) {
return $this->respondNotFound();
}
return redirect()->route('people.show', $contact)
->with('success', trans('people.conversation_delete_success'));
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Models\Contact\Debt;
use App\Helpers\AccountHelper;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
use App\Http\Requests\People\DebtRequest;
class DebtController extends Controller
{
/**
* Display a listing of the resource.
*
* @param Contact $contact
* @return \Illuminate\View\View
*/
public function index(Contact $contact)
{
return view('people.debt.index')
->withContact($contact);
}
/**
* Show the form for creating a new resource.
*
* @param Contact $contact
* @return \Illuminate\View\View
*/
public function create(Contact $contact)
{
return view('people.debt.add')
->withContact($contact)
->withAccountHasLimitations(AccountHelper::hasLimitations(auth()->user()->account))
->withDebt(new Debt);
}
/**
* Store a newly created resource in storage.
*
* @param DebtRequest $request
* @param Contact $contact
* @return \Illuminate\Http\RedirectResponse
*/
public function store(DebtRequest $request, Contact $contact)
{
$contact->throwInactive();
$contact->debts()->create(
$request->only([
'in_debt',
'amount',
'reason',
])
+ [
'account_id' => $contact->account_id,
'status' => 'inprogress',
]
);
return redirect()->route('people.show', $contact)
->with('success', trans('people.debt_add_success'));
}
/**
* Display the specified resource.
*
* @param Contact $contact
* @param Debt $debt
* @return void
*/
public function show(Contact $contact, Debt $debt): void
{
//
}
/**
* Show the form for editing the specified resource.
*
* @param Contact $contact
* @param Debt $debt
* @return \Illuminate\View\View
*/
public function edit(Contact $contact, Debt $debt)
{
$contact->throwInactive();
return view('people.debt.edit')
->withContact($contact)
->withAccountHasLimitations(AccountHelper::hasLimitations(auth()->user()->account))
->withDebt($debt);
}
/**
* Update the specified resource in storage.
*
* @param DebtRequest $request
* @param Contact $contact
* @param Debt $debt
* @return \Illuminate\Http\RedirectResponse
*/
public function update(DebtRequest $request, Contact $contact, Debt $debt)
{
$contact->throwInactive();
$debt->update(
$request->only([
'in_debt',
'amount',
'reason',
])
+ [
'account_id' => $contact->account_id,
'status' => 'inprogress',
]
);
return redirect()->route('people.show', $contact)
->with('success', trans('people.debt_edit_success'));
}
/**
* Remove the specified resource from storage.
*
* @param Contact $contact
* @param Debt $debt
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Contact $contact, Debt $debt)
{
$contact->throwInactive();
$debt->delete();
return redirect()->route('people.show', $contact)
->with('success', trans('people.debt_delete_success'));
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace App\Http\Controllers\Contacts;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Models\Contact\Document;
use Illuminate\Http\JsonResponse;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Services\Contact\Document\UploadDocument;
use App\Services\Contact\Document\DestroyDocument;
use App\Http\Resources\Document\Document as DocumentResource;
class DocumentsController extends Controller
{
use JsonRespondController;
/**
* Instantiate a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('limitations')->only('store');
}
/**
* Display the list of documents.
*
* @param Contact $contact
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request, Contact $contact)
{
$documents = $contact->documents()->get();
return DocumentResource::collection($documents);
}
/**
* Store the document.
*
* @param Request $request
* @param Contact $contact
* @return Document
*/
public function store(Request $request, Contact $contact): Document
{
$contact->throwInactive();
return app(UploadDocument::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'document' => $request->document,
]);
}
/**
* Delete the document.
*
* @param Request $request
* @param Contact $contact
* @param Document $document
* @return null|\Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, Contact $contact, Document $document): ?JsonResponse
{
$data = [
'account_id' => auth()->user()->account_id,
'document_id' => $document->id,
];
try {
if (app(DestroyDocument::class)->execute($data)) {
return $this->respondObjectDeleted($document->id);
}
} catch (\Exception $e) {
return $this->respondNotFound();
}
return null;
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Models\Contact\Gift;
use Illuminate\Http\Request;
use App\Models\Account\Photo;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Services\Contact\Gift\CreateGift;
use App\Services\Contact\Gift\UpdateGift;
use App\Services\Contact\Gift\DestroyGift;
use App\Http\Resources\Gift\Gift as GiftResource;
use App\Services\Contact\Gift\AssociatePhotoToGift;
class GiftController extends Controller
{
use JsonRespondController;
/**
* Get the list of gifts for the given contact.
*
* @param Request $request
* @param Contact $contact
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection|\Illuminate\Http\JsonResponse
*/
public function index(Request $request, Contact $contact)
{
$gifts = $contact->gifts()
->orderBy('created_at', 'asc')
->paginate();
return GiftResource::collection($gifts);
}
/**
* Get the detail of a given gift.
*
* @param Request $request
* @param Gift $gift
* @return GiftResource|\Illuminate\Http\JsonResponse
*/
public function show(Request $request, Contact $contact, Gift $gift)
{
return new GiftResource($gift);
}
/**
* Store the gift.
*
* @param Request $request
* @return GiftResource|\Illuminate\Http\JsonResponse
*/
public function store(Request $request, Contact $contact)
{
$gift = app(CreateGift::class)->execute(
$request->except(['account_id', 'contact_id']) +
[
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
]
);
return new GiftResource($gift);
}
/**
* Update the gift.
*
* @param Request $request
* @param Gift $gift
* @return GiftResource|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, Contact $contact, Gift $gift)
{
$gift = app(UpdateGift::class)->execute(
$request->except(['account_id', 'contact_id', 'gift_id']) +
[
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'gift_id' => $gift->id,
]
);
return new GiftResource($gift);
}
/**
* Associate a photo to the gift.
*
* @param Request $request
* @param Gift $gift
* @param Photo $photo
* @return GiftResource|\Illuminate\Http\JsonResponse
*/
public function associate(Request $request, Contact $contact, Gift $gift, Photo $photo)
{
$gift = app(AssociatePhotoToGift::class)->execute([
'account_id' => auth()->user()->account_id,
'gift_id' => $gift->id,
'photo_id' => $photo->id,
]);
return new GiftResource($gift);
}
/**
* Delete a gift.
*
* @param Request $request
* @param Gift $gift
* @return \Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, Contact $contact, Gift $gift)
{
app(DestroyGift::class)->execute([
'account_id' => auth()->user()->account_id,
'gift_id' => $gift->id,
]);
return $this->respondObjectDeleted($gift->id);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace App\Http\Controllers\Contacts;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Services\Contact\Contact\UpdateContactIntroduction;
use App\Http\Resources\Contact\ContactShort as ContactResource;
class IntroductionsController extends Controller
{
use JsonRespondController;
/**
* Show the form for editing the specified resource.
*
* @param Contact $contact
* @return \Illuminate\View\View
*/
public function edit(Contact $contact)
{
$contact->throwInactive();
$contacts = $contact->siblingContacts()
->real()
->active()
->orderByUserPreference()
->paginate(20);
$introducer = $contact->getIntroducer();
if ($introducer !== null) {
$introducer = new ContactResource($introducer);
}
return view('people.introductions.edit')
->withContact($contact)
->withContacts(ContactResource::collection($contacts))
->withIntroducer($introducer);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param Contact $contact
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function update(Request $request, Contact $contact)
{
$contact->throwInactive();
$contact = app(UpdateContactIntroduction::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'met_through_contact_id' => $request->input('metThroughId'),
'general_information' => $request->input('first_met_additional_info'),
'is_date_known' => $request->input('is_first_met_date_known') == 'known',
'day' => $request->input('first_met_day'),
'month' => $request->input('first_met_month'),
'year' => $request->input('first_met_year'),
'add_reminder' => $request->addReminder == 'on',
]);
return redirect()->route('people.show', $contact)
->with('success', trans('people.introductions_update_success'));
}
}

View File

@@ -0,0 +1,138 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Helpers\DateHelper;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Models\Contact\LifeEvent;
use Illuminate\Support\Collection;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Models\Contact\LifeEventCategory;
use App\Services\Contact\LifeEvent\CreateLifeEvent;
use App\Services\Contact\LifeEvent\DestroyLifeEvent;
use App\Http\Resources\LifeEvent\LifeEventType as LifeEventTypeResource;
use App\Http\Resources\LifeEvent\LifeEventCategory as LifeEventCategoryResource;
class LifeEventsController extends Controller
{
use JsonRespondController;
/**
* Get the list of life event categories.
*
* @param Request $request
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*/
public function categories(Request $request)
{
$lifeEventCategories = auth()->user()->account->lifeEventCategories;
return LifeEventCategoryResource::collection($lifeEventCategories);
}
/**
* Get the list of life event types for a given life event category.
*
* @param Request $request
* @param int $lifeEventCategoryId
* @return \Illuminate\Http\Resources\Json\ResourceCollection
*/
public function types(Request $request, int $lifeEventCategoryId)
{
$lifeEventCategory = LifeEventCategory::findOrFail($lifeEventCategoryId);
$lifeEventTypes = $lifeEventCategory->lifeEventTypes;
return LifeEventTypeResource::collection($lifeEventTypes);
}
/**
* Display the list of life events.
*
* @param Request $request
* @param Contact $contact
* @return Collection
*/
public function index(Request $request, Contact $contact)
{
$lifeEventsCollection = collect([]);
$lifeEvents = $contact->lifeEvents()->get();
foreach ($lifeEvents as $lifeEvent) {
$data = [
'id' => $lifeEvent->id,
'life_event_type' => $lifeEvent->lifeEventType->name,
'default_life_event_type_key' => $lifeEvent->lifeEventType->default_life_event_type_key,
'life_event_type_name' => $lifeEvent->lifeEventType->name,
'name' => $lifeEvent->name,
'note' => $lifeEvent->note,
'happened_at' => DateHelper::getShortDate($lifeEvent->happened_at),
];
$lifeEventsCollection->push($data);
}
return $lifeEventsCollection;
}
/**
* Store the life event.
*
* @param Request $request
* @param Contact $contact
* @return LifeEvent|\Illuminate\Http\RedirectResponse
*/
public function store(Request $request, Contact $contact)
{
$data = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'life_event_type_id' => $request->input('life_event_type_id'),
'happened_at' => $request->input('happened_at'),
'name' => $request->input('name'),
'note' => $request->input('note'),
'has_reminder' => $request->input('has_reminder'),
'happened_at_month_unknown' => $request->input('happened_at_month_unknown'),
'happened_at_day_unknown' => $request->input('happened_at_day_unknown'),
];
// create the conversation
try {
$lifeEvent = app(CreateLifeEvent::class)->execute($data);
} catch (\Exception $e) {
return back()
->withInput()
->withErrors(trans('app.error_save'));
}
return $lifeEvent;
}
/**
* Destroy the life event.
*
* @param Request $request
* @param LifeEvent $lifeEvent
* @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request, LifeEvent $lifeEvent)
{
$data = [
'account_id' => auth()->user()->account_id,
'life_event_id' => $lifeEvent->id,
];
try {
app(DestroyLifeEvent::class)->execute($data);
} catch (\Exception $e) {
// We have to redirect with HTTP status 303 or the browser will issue a
// DELETE request to the new location. This may result in deleting other
// resources as well. Refer to Github issue #2415
return back(303)
->withInput()
->withErrors(trans('app.error_save'));
}
return $this->respondObjectDeleted($lifeEvent->id);
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Helpers\DateHelper;
use App\Models\Contact\Note;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
use App\Http\Requests\People\NotesRequest;
use App\Http\Requests\People\NoteToggleRequest;
class NotesController extends Controller
{
/**
* Get all the tasks of this contact.
*/
public function index(Contact $contact)
{
$notesCollection = collect([]);
$notes = $contact->notes()->latest()->get();
foreach ($notes as $note) {
$data = [
'id' => $note->id,
'body' => $note->body,
'is_favorited' => $note->is_favorited,
'favorited_at' => $note->favorited_at,
'favorited_at_short' => $note->favorited_at ? DateHelper::getShortDate($note->favorited_at) : null,
'created_at' => $note->created_at,
'created_at_short' => DateHelper::getShortDate($note->created_at),
'edit' => false,
];
$notesCollection->push($data);
}
return $notesCollection;
}
/**
* Store the task.
*/
public function store(NotesRequest $request, Contact $contact)
{
$contact->throwInactive();
return $contact->notes()->create([
'account_id' => auth()->user()->account_id,
'body' => $request->input('body'),
]);
}
public function toggle(NoteToggleRequest $request, Contact $contact, Note $note)
{
// check if the state of the note has changed
if ($note->is_favorited) {
$note->favorited_at = null;
$note->is_favorited = false;
} else {
$note->is_favorited = true;
$note->favorited_at = now();
}
$note->save();
}
/**
* Update the specified resource in storage.
*
* @param NotesRequest $request
* @param Contact $contact
* @param Note $note
* @return Note
*/
public function update(NotesRequest $request, Contact $contact, Note $note): Note
{
$contact->throwInactive();
$note->update(
$request->only([
'body',
])
+ ['account_id' => $contact->account_id]
);
return $note;
}
/**
* Remove the specified resource from storage.
*
* @param Contact $contact
* @param Note $note
* @return void
*/
public function destroy(Contact $contact, Note $note): void
{
$contact->throwInactive();
$note->delete();
}
}

View File

@@ -0,0 +1,114 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Models\Contact\Pet;
use App\Models\Contact\Contact;
use App\Models\Contact\PetCategory;
use App\Http\Controllers\Controller;
use App\Http\Requests\People\PetsRequest;
class PetsController extends Controller
{
/**
* Get all the pet categories.
*/
public function getPetCategories()
{
$petCategoriesData = collect([]);
$petCategories = PetCategory::all();
foreach ($petCategories as $petCategory) {
$data = [
'id' => $petCategory->id,
'name' => $petCategory->name,
'edit' => false,
];
$petCategoriesData->push($data);
}
return $petCategoriesData;
}
/**
* Get all the pets for this contact.
*
* @param Contact $contact
*/
public function index(Contact $contact)
{
$petsCollection = collect([]);
$pets = $contact->pets;
foreach ($pets as $pet) {
$data = [
'id' => $pet->id,
'name' => $pet->name,
'pet_category_id' => $pet->pet_category_id,
'category_name' => $pet->petCategory->name,
'edit' => false,
];
$petsCollection->push($data);
}
return $petsCollection;
}
/**
* Store the pet.
*/
public function store(PetsRequest $request, Contact $contact)
{
$contact->throwInactive();
$pet = $contact->pets()->create(
$request->only([
'pet_category_id',
'name',
])
+ [
'account_id' => auth()->user()->account_id,
]
);
return [
'id' => $pet->id,
'name' => $pet->name,
'pet_category_id' => $pet->pet_category_id,
'category_name' => $pet->petCategory->name,
'edit' => false,
];
}
/**
* Update the pet.
*/
public function update(PetsRequest $request, Contact $contact, Pet $pet)
{
$contact->throwInactive();
$pet->update(
$request->only([
'pet_category_id',
'name',
])
+ [
'account_id' => auth()->user()->account_id,
]
);
return [
'id' => $pet->id,
'name' => $pet->name,
'pet_category_id' => $pet->pet_category_id,
'category_name' => $pet->petCategory->name,
'edit' => false,
];
}
public function destroy(Contact $contact, Pet $pet)
{
$pet->delete();
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace App\Http\Controllers\Contacts;
use Illuminate\Http\Request;
use App\Models\Account\Photo;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use App\Services\Account\Photo\UploadPhoto;
use App\Services\Account\Photo\DestroyPhoto;
use App\Services\Contact\Avatar\UpdateAvatar;
use App\Http\Resources\Photo\Photo as PhotoResource;
class PhotosController extends Controller
{
use JsonRespondController;
/**
* Display the list of photos.
*
* @param Contact $contact
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request, Contact $contact)
{
$photos = $contact->photos()->orderBy('created_at', 'desc')->get();
return PhotoResource::collection($photos);
}
/**
* Store the Photo.
*
* @param Request $request
* @param Contact $contact
* @return PhotoResource
*/
public function store(Request $request, Contact $contact): PhotoResource
{
$photo = app(UploadPhoto::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'photo' => $request->photo,
]);
return new PhotoResource($photo);
}
/**
* Delete the Photo.
* Also, if this photo was the current avatar of the contact, change the
* avatar to the default one.
*
* @param Request $request
* @param Contact $contact
* @param Photo $photo
* @return null|\Illuminate\Http\JsonResponse
*/
public function destroy(Request $request, Contact $contact, Photo $photo)
{
$data = [
'account_id' => auth()->user()->account_id,
'photo_id' => $photo->id,
];
try {
app(DestroyPhoto::class)->execute($data);
} catch (\Exception $e) {
return $this->respondNotFound();
}
if ($contact->avatar_source == 'photo'
&& $contact->avatar_photo_id == $photo->id) {
app(UpdateAvatar::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'source' => 'adorable',
]);
}
return $this->respondObjectDeleted($photo->id);
}
}

View File

@@ -0,0 +1,270 @@
<?php
namespace App\Http\Controllers\Contacts;
use Illuminate\View\View;
use App\Helpers\DateHelper;
use App\Helpers\FormHelper;
use Illuminate\Http\Request;
use App\Helpers\GenderHelper;
use App\Models\Contact\Contact;
use Illuminate\Support\Collection;
use App\Http\Controllers\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Http\RedirectResponse;
use App\Models\Relationship\Relationship;
use Illuminate\Support\Facades\Validator;
use App\Services\Contact\Contact\CreateContact;
use App\Services\Contact\Contact\UpdateContact;
use App\Services\Contact\Relationship\CreateRelationship;
use App\Services\Contact\Relationship\UpdateRelationship;
use App\Services\Contact\Relationship\DestroyRelationship;
use App\Http\Resources\Contact\ContactShort as ContactResource;
class RelationshipsController extends Controller
{
/**
* Display the Create relationship page.
*
* @param Contact $contact
* @return View
*/
public function create(Request $request, Contact $contact)
{
$existingContacts = Contact::search('', Auth::user()->account_id, 'updated_at')
->whereNotIn('id', [$contact->id])
->paginate(20);
return view('people.relationship.new')
->withContact($contact)
->withPartner(new Contact)
->withGenders(GenderHelper::getGendersInput())
->withRelationshipTypes($this->getRelationshipTypesList($contact))
->withDefaultGender(auth()->user()->account->default_gender_id)
->withDays(DateHelper::getListOfDays())
->withMonths(DateHelper::getListOfMonths())
->withBirthdate(now(DateHelper::getTimezone())->toDateString())
->withExistingContacts(ContactResource::collection($existingContacts))
->withType($request->input('type'))
->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user()));
}
/**
* Store a newly created resource in storage.
*
* @param Request $request
* @param Contact $contact
* @return RedirectResponse
*/
public function store(Request $request, Contact $contact)
{
// case of linking to an existing contact
if ($request->input('relationship_type') == 'existing') {
$partnerId = $request->input('existing_contact_id');
} else {
// case of creating a new contact
$datas = $this->validateAndGetDatas($request);
if ($datas instanceof \Illuminate\Contracts\Validation\Validator) {
return back()
->withInput()
->withErrors($datas);
}
$partner = app(CreateContact::class)->execute($datas);
$partnerId = $partner->id;
}
app(CreateRelationship::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_is' => $contact->id,
'of_contact' => $partnerId,
'relationship_type_id' => $request->input('relationship_type_id'),
]);
return redirect()->route('people.show', $contact)
->with('success', trans('people.relationship_form_add_success'));
}
/**
* Show the form for editing the specified resource.
*
* @param Contact $contact
* @param Relationship $relationship
* @return View
*/
public function edit(Contact $contact, Relationship $relationship)
{
$contact->throwInactive();
$otherContact = $relationship->ofContact;
$now = now();
$age = (string) (! is_null($otherContact->birthdate) ? $otherContact->birthdate->getAge() : 0);
$birthdate = ! is_null($otherContact->birthdate) ? $otherContact->birthdate->date->toDateString() : $now->toDateString();
$day = ! is_null($otherContact->birthdate) ? $otherContact->birthdate->date->day : $now->day;
$month = ! is_null($otherContact->birthdate) ? $otherContact->birthdate->date->month : $now->month;
$hasBirthdayReminder = is_null($otherContact->birthday_reminder_id) ? 0 : 1;
return view('people.relationship.edit')
->withContact($contact)
->withPartner($otherContact)
->withGenders(auth()->user()->account->genders)
->withRelationshipTypes($this->getRelationshipTypesList($contact))
->withDays(DateHelper::getListOfDays())
->withMonths(DateHelper::getListOfMonths())
->withBirthdate($birthdate)
->withRelationshipId($relationship->id)
->withType($relationship->relationship_type_id)
->withBirthdayState($otherContact->getBirthdayState())
->withDay($day)
->withMonth($month)
->withAge($age)
->withGenders(GenderHelper::getGendersInput())
->withHasBirthdayReminder($hasBirthdayReminder)
->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user()));
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param Contact $contact
* @param Relationship $relationship
* @return RedirectResponse
*/
public function update(Request $request, Contact $contact, Relationship $relationship)
{
$otherContact = $relationship->ofContact;
if ($otherContact->is_partial) {
$datas = $this->validateAndGetDatas($request);
if ($datas instanceof \Illuminate\Contracts\Validation\Validator) {
return back()
->withInput()
->withErrors($datas);
}
app(UpdateContact::class)->execute($datas + [
'contact_id' => $otherContact->id,
'author_id' => auth()->user()->id,
]);
}
// update the relationship
app(UpdateRelationship::class)->execute([
'account_id' => auth()->user()->account_id,
'relationship_id' => $relationship->id,
'relationship_type_id' => $request->input('relationship_type_id'),
]);
return redirect()->route('people.show', $contact)
->with('success', trans('people.relationship_form_add_success'));
}
/**
* Validate datas and get an array for create or update a contact.
*
* @param Request $request
* @return array|\Illuminate\Contracts\Validation\Validator
*/
private function validateAndGetDatas(Request $request)
{
$validator = Validator::make($request->all(), [
'first_name' => 'required|max:255',
'last_name' => 'max:255',
'gender_id' => 'nullable|integer',
'birthdayDate' => 'date_format:Y-m-d',
]);
if ($validator->fails()) {
return $validator;
}
// this is really ugly. it should be changed
if ($request->input('birthdate') == 'exact') {
$birthdate = $request->input('birthdayDate');
$birthdate = DateHelper::parseDate($birthdate);
$day = $birthdate->day;
$month = $birthdate->month;
$year = $birthdate->year;
} else {
$day = $request->input('day');
$month = $request->input('month');
$year = $request->input('year');
}
return [
'account_id' => auth()->user()->account_id,
'author_id' => auth()->user()->id,
'first_name' => $request->input('first_name'),
'last_name' => $request->input('last_name'),
'gender_id' => $request->input('gender_id'),
'is_birthdate_known' => ! empty($request->input('birthdate')) && $request->input('birthdate') !== 'unknown',
'birthdate_day' => $day,
'birthdate_month' => $month,
'birthdate_year' => $year,
'birthdate_is_age_based' => $request->input('birthdate') === 'approximate',
'birthdate_age' => $request->input('age'),
'birthdate_add_reminder' => ! empty($request->input('addReminder')),
'is_partial' => ! $request->input('realContact'),
'is_deceased' => false,
'is_deceased_date_known' => false,
];
}
/**
* Remove the specified resource from storage.
*
* @param Contact $contact
* @param Relationship $relationship
* @return RedirectResponse
*/
public function destroy(Contact $contact, Relationship $relationship)
{
if ($contact->account_id != auth()->user()->account_id) {
return redirect()->route('people.index');
}
if ($relationship->account_id != auth()->user()->account_id) {
return redirect()->route('people.index');
}
app(DestroyRelationship::class)->execute([
'account_id' => auth()->user()->account_id,
'relationship_id' => $relationship->id,
]);
return redirect()->route('people.show', $contact)
->with('success', trans('people.relationship_form_deletion_success'));
}
/**
* Building the list of relationship types specifically for the dropdown which asks
* for an id and a name.
*
* @return Collection
*/
private function getRelationshipTypesList(Contact $contact)
{
$relationshipTypes = collect();
foreach (auth()->user()->account->relationshipTypes as $relationshipType) {
$types = $relationshipTypes->get($relationshipType->relationshipTypeGroup->name, [
'name' => trans('app.relationship_type_group_'.$relationshipType->relationshipTypeGroup->name),
'options' => [],
]);
$types['options'][] = [
'id' => $relationshipType->id,
'name' => $relationshipType->getLocalizedName($contact, true),
];
$relationshipTypes->put($relationshipType->relationshipTypeGroup->name, $types);
}
return $relationshipTypes;
}
}

View File

@@ -0,0 +1,127 @@
<?php
namespace App\Http\Controllers\Contacts;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use App\Models\Contact\Contact;
use App\Models\Contact\Reminder;
use App\Http\Controllers\Controller;
use App\Services\Contact\Reminder\CreateReminder;
use App\Services\Contact\Reminder\UpdateReminder;
use App\Services\Contact\Reminder\DestroyReminder;
class RemindersController extends Controller
{
/**
* Show the form for creating a new reminder.
*
* @param Contact $contact
* @return \Illuminate\View\View
*/
public function create(Contact $contact)
{
return view('people.reminders.add')
->withContact($contact)
->withAccountHasLimitations(AccountHelper::hasLimitations(auth()->user()->account))
->withReminder(new Reminder);
}
/**
* Store a reminder.
*
* @param Request $request
* @param Contact $contact
* @return \Illuminate\Http\RedirectResponse
*/
public function store(Request $request, Contact $contact)
{
$frequency_type = $request->input('frequency_type');
if ($frequency_type === 'recurrent') {
$frequency_type = $request->input('frequency_number_select');
}
$data = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'initial_date' => $request->input('initial_date'),
'frequency_type' => $frequency_type,
'frequency_number' => is_null($request->input('frequency_number')) ? 1 : $request->input('frequency_number'),
'title' => $request->input('title'),
'description' => $request->input('description'),
];
app(CreateReminder::class)->execute($data);
return redirect()->route('people.show', $contact)
->with('success', trans('people.reminders_create_success'));
}
/**
* Show the form for editing the specified resource.
*
* @param Contact $contact
* @param Reminder $reminder
* @return \Illuminate\View\View
*/
public function edit(Contact $contact, Reminder $reminder)
{
return view('people.reminders.edit')
->withContact($contact)
->withAccountHasLimitations(AccountHelper::hasLimitations(auth()->user()->account))
->withReminder($reminder);
}
/**
* Update the reminder.
*
* @param Request $request
* @param Contact $contact
* @param Reminder $reminder
* @return \Illuminate\Http\RedirectResponse
*/
public function update(Request $request, Contact $contact, Reminder $reminder)
{
$frequency_type = $request->input('frequency_type');
if ($frequency_type === 'recurrent') {
$frequency_type = $request->input('frequency_number_select');
}
$data = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'reminder_id' => $reminder->id,
'initial_date' => $request->input('initial_date'),
'frequency_type' => $frequency_type,
'frequency_number' => is_null($request->input('frequency_number')) ? 1 : $request->input('frequency_number'),
'title' => $request->input('title'),
'description' => $request->input('description'),
];
app(UpdateReminder::class)->execute($data);
return redirect()->route('people.show', $contact)
->with('success', trans('people.reminders_update_success'));
}
/**
* Destroy the reminder.
*
* @param Request $request
* @param Contact $contact
* @param Reminder $reminder
* @return \Illuminate\Http\RedirectResponse
*/
public function destroy(Request $request, Contact $contact, Reminder $reminder)
{
$data = [
'account_id' => $reminder->account_id,
'reminder_id' => $reminder->id,
];
app(DestroyReminder::class)->execute($data);
return redirect()->route('people.show', $contact)
->with('success', trans('people.reminders_delete_success'));
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace App\Http\Controllers\Contacts;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
use App\Services\Contact\Tag\DetachTag;
use App\Services\Contact\Tag\AssociateTag;
use App\Http\Resources\Tag\Tag as TagResource;
class TagsController extends Controller
{
/**
* Get the list of all the tags in the account.
*
* @param Request $request
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function index(Request $request)
{
$tags = auth()->user()->account->tags()->get();
return TagResource::collection($tags);
}
/**
* Get the list of all the tags for this contact.
*
* @param Request $request
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function get(Request $request, Contact $contact)
{
$tags = $contact->tags()->get();
return TagResource::collection($tags);
}
/**
* Update the specified resource in storage.
*
* @param Request $request
* @param Contact $contact
* @return void
*/
public function update(Request $request, Contact $contact): void
{
$contact->throwInactive();
$tags = $request->all();
// detaching all the tags
$contactTags = $contact->tags()->get();
foreach ($contactTags as $tag) {
app(DetachTag::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'tag_id' => $tag->id,
]);
}
// attach all the new/updated tags
foreach ($tags as $tag) {
if (! empty($tag['name'])) {
app(AssociateTag::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'name' => $tag['name'],
]);
}
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace App\Http\Controllers\Contacts;
use App\Helpers\DateHelper;
use App\Models\Contact\Contact;
use App\Http\Controllers\Controller;
class TasksController extends Controller
{
/**
* Get all the tasks of this contact.
*/
public function index(Contact $contact)
{
$tasks = collect([]);
foreach ($contact->tasks as $task) {
$data = [
'id' => $task->id,
'title' => $task->title,
'description' => $task->description,
'completed' => $task->completed,
'completed_at' => ($task->completed_at) ? DateHelper::getShortDate($task->completed_at) : null,
'edit' => false,
];
$tasks->push($data);
}
return $tasks;
}
}

View File

@@ -0,0 +1,755 @@
<?php
namespace App\Http\Controllers;
use Illuminate\View\View;
use App\Helpers\DateHelper;
use App\Helpers\FormHelper;
use App\Models\Contact\Tag;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use App\Helpers\GenderHelper;
use App\Helpers\LocaleHelper;
use App\Helpers\SearchHelper;
use App\Helpers\AccountHelper;
use App\Helpers\StorageHelper;
use App\Models\Contact\Contact;
use App\Services\VCard\ExportVCard;
use Illuminate\Support\Facades\Log;
use App\Jobs\UpdateLastConsultedDate;
use Illuminate\Http\RedirectResponse;
use Illuminate\Contracts\View\Factory;
use Barryvdh\Debugbar\Facades\Debugbar;
use App\Services\User\UpdateViewPreference;
use Illuminate\Validation\ValidationException;
use App\Services\Contact\Contact\CreateContact;
use App\Services\Contact\Contact\UpdateContact;
use App\Services\Contact\Contact\DestroyContact;
use App\Services\Contact\Contact\UpdateWorkInformation;
use App\Services\Contact\Contact\UpdateContactFoodPreferences;
use App\Http\Resources\Contact\ContactSearch as ContactResource;
class ContactsController extends Controller
{
/**
* Display a listing of the resource.
*
* @param Request $request
* @return View|RedirectResponse
*/
public function index(Request $request)
{
return $this->contacts($request, true);
}
/**
* Display a listing of the resource.
*
* @param Request $request
* @return View|RedirectResponse
*/
public function archived(Request $request)
{
return $this->contacts($request, false);
}
/**
* Display contacts.
*
* @param Request $request
* @param bool $active
* @return View|RedirectResponse
*/
private function contacts(Request $request, bool $active)
{
$user = $request->user();
$sort = $request->input('sort') ?? $user->contacts_sort_order;
$showDeceased = $request->input('show_dead');
if ($user->contacts_sort_order !== $sort) {
app(UpdateViewPreference::class)->execute([
'account_id' => $user->account_id,
'user_id' => $user->id,
'preference' => $sort,
]);
}
$contacts = $user->account->contacts()->real();
if ($active) {
$archived = (clone $contacts)->notActive();
$contacts = (clone $contacts)->active();
$nbArchived = $archived->count();
} else {
$contacts = $contacts->notActive();
$nbArchived = $contacts->count();
}
$tagsCount = Tag::contactsCount();
$contactsWithoutTagsCount = (clone $contacts)->doesntHave('tags')->count();
$tags = null;
$url = null;
$count = 1;
if ($request->input('tags')) {
$tagsInput = $request->input('tags');
$tags = $tagsCount->filter(function ($tag) use ($tagsInput) {
return in_array($tag->name, $tagsInput);
});
$url = $tags->map(function ($tag): string {
return 'tags[]='.urlencode($tag->name);
})->join('&');
if ('' !== $url) {
$url .= '&';
}
if ($tags->count() === 0) {
return redirect()->route('people.index');
} else {
$contacts = $contacts->tags($tags);
}
} elseif ($request->input('no_tag')) {
$contacts = $contacts->tags('NONE');
}
$contactsCount = (clone $contacts)->alive()->count();
$deceasedCount = (clone $contacts)->dead()->count();
if ($showDeceased === 'true') {
$contactsCount += $deceasedCount;
}
$accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account);
return view('people.index')
->withAccountHasLimitations($accountHasLimitations)
->withHidingDeceased($showDeceased !== 'true')
->withDeceasedCount($deceasedCount)
->withActive($active)
->withContactsCount($contactsCount)
->withHasArchived($nbArchived > 0)
->withArchivedContacts($nbArchived)
->withTags($tags)
->withSort($sort)
->withTagsCount($tagsCount)
->withUrl($url)
->withTagCount($count)
->withTagLess($request->input('no_tag') ?? false)
->with('contactsWithoutTagsCount', $contactsWithoutTagsCount);
}
/**
* Show the form to add a new contact.
*
* @param Request $request
* @return View|Factory|RedirectResponse
*/
public function create(Request $request)
{
return $this->createForm($request, false);
}
/**
* Show the form in case the contact is missing.
*
* @param Request $request
* @return View|Factory|RedirectResponse
*/
public function missing(Request $request)
{
return $this->createForm($request, true);
}
/**
* Show the Add user form unless the contact has limitations.
*
* @param Request $request
* @param bool $isContactMissing
* @return View|Factory|RedirectResponse
*/
private function createForm(Request $request, bool $isContactMissing = false)
{
$accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account);
if ($accountHasLimitations
&& AccountHelper::hasReachedContactLimit(auth()->user()->account)
&& ! auth()->user()->account->legacy_free_plan_unlimited_contacts) {
return redirect()->route('settings.subscriptions.index');
}
return view('people.create')
->withAccountHasLimitations($accountHasLimitations)
->withIsContactMissing($isContactMissing)
->withGenders(GenderHelper::getGendersInput())
->withDefaultGender(auth()->user()->account->default_gender_id)
->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user()))
->withFirstName($request->input('first_name'))
->withLastName($request->input('last_name'))
->withEmail($request->input('email'));
}
/**
* Store the contact.
*
* @param Request $request
* @return RedirectResponse
*/
public function store(Request $request)
{
try {
$contact = app(CreateContact::class)->execute([
'account_id' => auth()->user()->account_id,
'author_id' => auth()->user()->id,
'first_name' => $request->input('first_name'),
'middle_name' => $request->input('middle_name', null),
'last_name' => $request->input('last_name', null),
'nickname' => $request->input('nickname', null),
'email' => $request->input('email', null),
'gender_id' => $request->input('gender'),
'is_birthdate_known' => false,
'is_deceased' => false,
'is_deceased_date_known' => false,
]);
} catch (ValidationException $e) {
return back()
->withInput()
->withErrors($e->validator);
}
// Did the user press "Save" or "Submit and add another person"
if (! is_null($request->input('save'))) {
return redirect()->route('people.show', $contact);
} else {
return redirect()->route('people.create')
->with('status', trans('people.people_add_success', ['name' => $contact->name]));
}
}
/**
* Display the contact profile.
*
* @param Contact $contact
* @return View|RedirectResponse
*/
public function show(Contact $contact)
{
// make sure we don't display a partial contact
if ($contact->is_partial) {
$realContact = $contact->getRelatedRealContact();
if (is_null($realContact)) {
return redirect()->route('people.index')
->withErrors(trans('people.people_not_found'));
}
return redirect()->route('people.show', $realContact);
}
$contact->load(['notes' => function ($query) {
$query->orderBy('updated_at', 'desc');
}]);
UpdateLastConsultedDate::dispatch($contact);
$relationships = $contact->relationships;
// get love relationship type
$loveRelationships = $relationships->filter(function ($item) {
$item->relationshipTypeLocalized = $item->relationshipType->getLocalizedName(null, false, $item->ofContact->gender->type ?? null);
return $item->relationshipType->relationshipTypeGroup->name == 'love';
});
$loveRelationships->sortByCollator('relationshipTypeLocalized');
// get family relationship type
$familyRelationships = $relationships->filter(function ($item) {
$item->relationshipTypeLocalized = $item->relationshipType->getLocalizedName(null, false, $item->ofContact->gender->type ?? null);
return $item->relationshipType->relationshipTypeGroup->name == 'family';
});
$familyRelationships->sortByCollator('relationshipTypeLocalized');
// get friend relationship type
$friendRelationships = $relationships->filter(function ($item) {
$item->relationshipTypeLocalized = $item->relationshipType->getLocalizedName(null, false, $item->ofContact->gender->type ?? null);
return $item->relationshipType->relationshipTypeGroup->name == 'friend';
});
$friendRelationships->sortByCollator('relationshipTypeLocalized');
// get work relationship type
$workRelationships = $relationships->filter(function ($item) {
$item->relationshipTypeLocalized = $item->relationshipType->getLocalizedName(null, false, $item->ofContact->gender->type ?? null);
return $item->relationshipType->relationshipTypeGroup->name == 'work';
});
$workRelationships->sortByCollator('relationshipTypeLocalized');
// reminders
$reminders = $contact->reminders()->active()->get();
$relevantRemindersFromRelatedContacts = $contact->getBirthdayRemindersAboutRelatedContacts();
$reminders = $reminders->merge($relevantRemindersFromRelatedContacts);
// now we need to sort the reminders by next date they will be triggered
foreach ($reminders as $reminder) {
$next_expected_date = $reminder->calculateNextExpectedDateOnTimezone();
$reminder->next_expected_date_human_readable = DateHelper::getShortDate($next_expected_date);
$reminder->next_expected_date = DateHelper::getDate($next_expected_date);
}
$reminders = $reminders->sortBy('next_expected_date');
// list of active features
$modules = $contact->account->modules()->active()->get();
// add `---` at the top of the dropdowns
$days = DateHelper::getListOfDays();
$days->prepend([
'id' => 0,
'name' => '---',
]);
$months = DateHelper::getListOfMonths();
$months->prepend([
'id' => 0,
'name' => '---',
]);
$hasReachedAccountStorageLimit = StorageHelper::hasReachedAccountStorageLimit($contact->account);
$accountHasLimitations = AccountHelper::hasLimitations($contact->account);
return view('people.profile')
->withHasReachedAccountStorageLimit($hasReachedAccountStorageLimit)
->withAccountHasLimitations($accountHasLimitations)
->withLoveRelationships($loveRelationships)
->withFamilyRelationships($familyRelationships)
->withFriendRelationships($friendRelationships)
->withWorkRelationships($workRelationships)
->withReminders($reminders)
->withModules($modules)
->withContact($contact)
->withWeather($contact->getWeather())
->withDays($days)
->withMonths($months)
->withYears(DateHelper::getListOfYears());
}
/**
* Display the Edit people's view.
*
* @param Contact $contact
* @return View|RedirectResponse
*/
public function edit(Contact $contact)
{
$contact->throwInactive();
$now = now();
$age = (string) (! is_null($contact->birthdate) ? $contact->birthdate->getAge() : 0);
$birthdate = ! is_null($contact->birthdate) ? $contact->birthdate->date->toDateString() : $now->toDateString();
$deceaseddate = ! is_null($contact->deceasedDate) ? $contact->deceasedDate->date->toDateString() : '';
$day = ! is_null($contact->birthdate) ? $contact->birthdate->date->day : $now->day;
$month = ! is_null($contact->birthdate) ? $contact->birthdate->date->month : $now->month;
$hasBirthdayReminder = ! is_null($contact->birthday_reminder_id);
$hasDeceasedReminder = ! is_null($contact->deceased_reminder_id);
$accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account);
return view('people.edit')
->withAccountHasLimitations($accountHasLimitations)
->withContact($contact)
->withDays(DateHelper::getListOfDays())
->withMonths(DateHelper::getListOfMonths())
->withBirthdayState($contact->getBirthdayState())
->withBirthdate($birthdate)
->withDeceaseddate($deceaseddate)
->withDay($day)
->withMonth($month)
->withAge($age)
->withHasBirthdayReminder($hasBirthdayReminder)
->withHasDeceasedReminder($hasDeceasedReminder)
->withGenders(GenderHelper::getGendersInput())
->withFormNameOrder(FormHelper::getNameOrderForForms(auth()->user()));
}
/**
* Update the contact.
*
* @param Request $request
* @param Contact $contact
* @return RedirectResponse
*/
public function update(Request $request, Contact $contact)
{
$contact->throwInactive();
// process birthday dates
// TODO: remove this part entirely when we redo this whole SpecialDate
// thing
if ($request->input('birthdate') == 'exact') {
$birthdate = $request->input('birthdayDate');
$birthdate = DateHelper::parseDate($birthdate);
$day = $birthdate->day;
$month = $birthdate->month;
$year = $birthdate->year;
} else {
$day = $request->input('day');
$month = $request->input('month');
$year = $request->input('year');
}
$is_deceased_date_known = false;
if ($request->input('is_deceased_date_known') === 'true' && $request->input('deceased_date')) {
$is_deceased_date_known = true;
$deceased_date = $request->input('deceased_date');
$deceased_date = DateHelper::parseDate($deceased_date);
$deceased_date_day = $deceased_date->day;
$deceased_date_month = $deceased_date->month;
$deceased_date_year = $deceased_date->year;
} else {
$deceased_date_day = $deceased_date_month = $deceased_date_year = null;
}
if (! empty($request->input('is_deceased'))) {
//if the contact has died, disable StayInTouch
$contact->updateStayInTouchFrequency(0);
$contact->setStayInTouchTriggerDate(0);
}
$data = [
'account_id' => auth()->user()->account_id,
'author_id' => auth()->user()->id,
'contact_id' => $contact->id,
'first_name' => $request->input('firstname'),
'middle_name' => $request->input('middlename', null),
'last_name' => $request->input('lastname', null),
'nickname' => $request->input('nickname', null),
'gender_id' => $request->input('gender'),
'description' => $request->input('description', null),
'is_birthdate_known' => ! empty($request->input('birthdate')) && $request->input('birthdate') !== 'unknown',
'birthdate_day' => $day,
'birthdate_month' => $month,
'birthdate_year' => $year,
'birthdate_is_age_based' => $request->input('birthdate') === 'approximate',
'birthdate_age' => $request->input('age'),
'birthdate_add_reminder' => ! empty($request->input('addReminder')),
'is_deceased' => ! empty($request->input('is_deceased')),
'is_deceased_date_known' => $is_deceased_date_known,
'deceased_date_day' => $deceased_date_day,
'deceased_date_month' => $deceased_date_month,
'deceased_date_year' => $deceased_date_year,
'deceased_date_add_reminder' => ! empty($request->input('add_reminder_deceased')),
];
$contact = app(UpdateContact::class)->execute($data);
if ($request->file('avatar') != '') {
if ($contact->has_avatar) {
try {
$contact->deleteAvatars();
} catch (\Exception $e) {
Log::warning(__CLASS__.' update: Failed to delete avatars', [
'contact' => $contact,
$e,
]);
}
}
$contact->has_avatar = true;
$contact->avatar_location = config('filesystems.default');
$contact->avatar_file_name = $request->file('avatar')->store('avatars', [
'disk' => $contact->avatar_location,
'visibility' => config('filesystems.default_visibility'),
]);
$contact->save();
}
return redirect()->route('people.show', $contact)
->with('success', trans('people.information_edit_success'));
}
/**
* Delete the contact.
*
* @param Request $request
* @param Contact $contact
* @return RedirectResponse
*/
public function destroy(Request $request, Contact $contact)
{
if ($contact->account_id != auth()->user()->account_id) {
return redirect()->route('people.index');
}
$data = [
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
];
DestroyContact::dispatch($data);
return redirect()->route('people.index')
->with('success', trans('people.people_delete_success'));
}
/**
* Show the Edit work view.
*
* @param Request $request
* @param Contact $contact
* @return View|RedirectResponse
*/
public function editWork(Request $request, Contact $contact)
{
$contact->throwInactive();
return view('people.work.edit')
->withContact($contact);
}
/**
* Save the work information.
*
* @param Request $request
* @param Contact $contact
* @return RedirectResponse
*/
public function updateWork(Request $request, Contact $contact)
{
$contact->throwInactive();
$contact = app(UpdateWorkInformation::class)->execute([
'account_id' => auth()->user()->account_id,
'author_id' => auth()->user()->id,
'contact_id' => $contact->id,
'job' => $request->input('job'),
'company' => $request->input('company'),
]);
return redirect()->route('people.show', $contact)
->with('success', trans('people.work_edit_success'));
}
/**
* Show the Edit food preferences view.
*
* @param Request $request
* @param Contact $contact
* @return View|RedirectResponse
*/
public function editFoodPreferences(Request $request, Contact $contact)
{
$contact->throwInactive();
$accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account);
return view('people.food-preferences.edit')
->withAccountHasLimitations($accountHasLimitations)
->withContact($contact);
}
/**
* Save the food preferences.
*
* @param Request $request
* @param Contact $contact
* @return RedirectResponse
*/
public function updateFoodPreferences(Request $request, Contact $contact)
{
$contact->throwInactive();
$contact = app(UpdateContactFoodPreferences::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
'food_preferences' => $request->input('food'),
]);
return redirect()->route('people.show', $contact)
->with('success', trans('people.food_preferences_add_success'));
}
/**
* Search used in the header.
*
* @param Request $request
*/
public function search(Request $request)
{
$needle = $request->needle;
if ($needle == null) {
return;
}
$results = SearchHelper::searchContacts($needle, 'created_at')
->paginate(20);
if ($results->total() > 0) {
return ContactResource::collection($results);
} else {
return ['noResults' => trans('people.people_search_no_results')];
}
}
/**
* Download the contact as vCard.
*
* @param Contact $contact
* @return \Illuminate\Http\Response
*/
public function vCard(Contact $contact)
{
if (config('app.debug') && class_exists('\Barryvdh\Debugbar\Facade')) {
Debugbar::disable();
}
$vcard = app(ExportVCard::class)->execute([
'account_id' => auth()->user()->account_id,
'contact_id' => $contact->id,
]);
return response($vcard->serialize())
->header('Content-type', 'text/x-vcard')
->header('Content-Disposition', 'attachment; filename='.Str::slug($contact->name, '-', LocaleHelper::getLang()).'.vcf');
}
/**
* Set or change the frequency of which the user wants to stay in touch with
* the given contact.
*
* @param Request $request
* @param Contact $contact
* @return array
*/
public function stayInTouch(Request $request, Contact $contact)
{
$contact->throwInactive();
$frequency = intval($request->input('frequency'));
$state = $request->input('state');
if (AccountHelper::hasLimitations(auth()->user()->account)) {
throw new \LogicException(trans('people.stay_in_touch_premium'));
}
// if not active, set frequency to 0
if (! $state) {
$frequency = 0;
}
$result = $contact->updateStayInTouchFrequency($frequency);
if (! $result) {
throw new \LogicException(trans('people.stay_in_touch_invalid'));
}
$contact->setStayInTouchTriggerDate($frequency);
return [
'frequency' => $frequency,
'trigger_date' => $contact->stay_in_touch_trigger_date,
];
}
/**
* Toggle favorites of a contact.
*
* @param Request $request
* @param Contact $contact
* @return array
*/
public function favorite(Request $request, Contact $contact)
{
$bool = (bool) $request->input('toggle');
$contact->is_starred = $bool;
$contact->save();
return [
'is_starred' => $bool,
];
}
/**
* Toggle archive state of a contact.
*
* @param Request $request
* @param Contact $contact
* @return array
*/
public function archive(Request $request, Contact $contact)
{
if (! $contact->is_active
&& AccountHelper::hasReachedContactLimit(auth()->user()->account)
&& AccountHelper::hasLimitations(auth()->user()->account)
&& ! auth()->user()->account->legacy_free_plan_unlimited_contacts) {
abort(402);
}
$contact->is_active = ! $contact->is_active;
$contact->save();
return [
'is_active' => $contact->is_active,
];
}
/**
* Display the list of contacts.
*
* @param Request $request
* @return array
*/
public function list(Request $request)
{
$accountId = auth()->user()->account_id;
$user = $request->user();
$sort = $request->input('sort') ?? $user->contacts_sort_order;
if ($user->contacts_sort_order !== $sort) {
app(UpdateViewPreference::class)->execute([
'account_id' => $user->account_id,
'user_id' => $user->id,
'preference' => $sort,
]);
}
$tags = null;
$contacts = $user->account->contacts()->real();
// filter out archived contacts if necessary
if ($request->input('show_archived') != 'true') {
$contacts = $contacts->active();
} else {
$contacts = $contacts->notActive();
}
// filter out deceased if necessary
if ($request->input('show_dead') != 'true') {
$contacts = $contacts->alive();
}
if ($request->input('tags')) {
$tags = Tag::where('account_id', $accountId)
->whereIn('name', $request->input('tags'))
->get();
if ($tags->count() > 0) {
$contacts = $contacts->tags($tags);
}
} elseif ($request->input('no_tag')) {
// get tag less contacts
$contacts = $contacts->tags('NONE');
}
// get the number of contacts per page
$perPage = $request->has('perPage') ? $request->input('perPage') : config('monica.number_of_contacts_pagination');
// search contacts
$contacts = $contacts->search($request->input('search') ?? '', $accountId, 'is_starred', 'desc', $sort)
->paginate($perPage);
return [
'totalRecords' => $contacts->total(),
'contacts' => ContactResource::collection($contacts),
];
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}

View File

@@ -0,0 +1,80 @@
<?php
namespace App\Http\Controllers\DAV\Auth;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
use Illuminate\Support\Facades\Auth;
use Sabre\DAV\Auth\Backend\BackendInterface;
use App\Http\Controllers\DAV\DAVACL\PrincipalBackend;
class AuthBackend implements BackendInterface
{
/**
* Authentication Realm.
*
* The realm is often displayed by browser clients when showing the
* authentication dialog.
*
* @var string
*/
protected $realm = 'sabre/dav';
/**
* Sets the authentication realm for this backend.
*
* @param string $realm
* @return void
*/
public function setRealm($realm)
{
$this->realm = $realm;
}
/**
* Check Laravel authentication.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return array
*/
public function check(RequestInterface $request, ResponseInterface $response)
{
if (! Auth::check()) {
return [false, 'User is not authenticated'];
}
return [true, PrincipalBackend::getPrincipalUser(Auth::user())];
}
/**
* This method is called when a user could not be authenticated, and
* authentication was required for the current request.
*
* This gives you the opportunity to set authentication headers. The 401
* status code will already be set.
*
* In this case of Bearer Auth, this would for example mean that the
* following header needs to be set:
*
* $response->addHeader('WWW-Authenticate', 'Bearer realm=SabreDAV');
*
* Keep in mind that in the case of multiple authentication backends, other
* WWW-Authenticate headers may already have been set, and you'll want to
* append your own WWW-Authenticate header instead of overwriting the
* existing one.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return void
*/
public function challenge(RequestInterface $request, ResponseInterface $response)
{
$auth = new \Sabre\HTTP\Auth\Bearer(
$this->realm,
$request,
$response
);
$auth->requireLogin();
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CalDAV;
use Sabre\DAV\Server as SabreServer;
use Sabre\CalDAV\Plugin as CalDAVPlugin;
use Sabre\DAV\Sync\Plugin as DAVSyncPlugin;
use App\Http\Controllers\DAV\Backend\IDAVBackend;
use App\Http\Controllers\DAV\Backend\SyncDAVBackend;
use App\Http\Controllers\DAV\DAVACL\PrincipalBackend;
abstract class AbstractCalDAVBackend implements ICalDAVBackend, IDAVBackend
{
use SyncDAVBackend;
/**
* Get description array.
*
* @return array
*/
public function getDescription()
{
$token = DAVSyncPlugin::SYNCTOKEN_PREFIX.$this->refreshSyncToken(null)->id;
return [
'id' => $this->backendUri(),
'uri' => $this->backendUri(),
'principaluri' => PrincipalBackend::getPrincipalUser($this->user),
'{DAV:}sync-token' => $token,
'{'.SabreServer::NS_SABREDAV.'}sync-token' => $token,
'{'.CalDAVPlugin::NS_CALENDARSERVER.'}getctag' => $token,
];
}
/**
* Get the new exported version of the object.
*
* @param mixed $obj
* @return string
*/
abstract protected function refreshObject($obj): string;
}

View File

@@ -0,0 +1,356 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CalDAV;
use Sabre\DAV;
use App\Traits\WithUser;
use Sabre\CalDAV\Backend\SyncSupport;
use Sabre\CalDAV\Backend\AbstractBackend;
class CalDAVBackend extends AbstractBackend implements SyncSupport
{
use WithUser;
/**
* Set the Calendar backends.
*
* @return array
*/
private function getBackends(): array
{
return [
app(CalDAVBirthdays::class)->init($this->user),
app(CalDAVTasks::class)->init($this->user),
];
}
/**
* Get the backend for this id.
*
* @return AbstractCalDAVBackend|null
*/
private function getBackend($id)
{
return collect($this->getBackends())->first(function ($backend) use ($id) {
return $backend->backendUri() === $id;
});
}
/**
* Returns a list of calendars for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which is the basename of the uri with which the calendar is
* accessed.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* Many clients also require:
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* For this property, you can just return an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet.
*
* If you return {http://sabredav.org/ns}read-only and set the value to 1,
* ACL will automatically be put in read-only mode.
*
* @param string $principalUri
* @return array
*/
public function getCalendarsForUser($principalUri)
{
return array_map(function ($backend) {
return $backend->getDescription();
}, $this->getBackends());
}
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken in the specified calendar.
*
* This function should return an array, such as the following:
*
* [
* 'syncToken' => 'The current synctoken',
* 'added' => [
* 'new.txt',
* ],
* 'modified' => [
* 'modified.txt',
* ],
* 'deleted' => [
* 'foo.php.bak',
* 'old.txt'
* ]
* );
*
* The returned syncToken property should reflect the *current* syncToken
* of the calendar, as reported in the {http://sabredav.org/ns}sync-token
* property This is * needed here too, to ensure the operation is atomic.
*
* If the $syncToken argument is specified as null, this is an initial
* sync, and all members should be reported.
*
* The modified property is an array of nodenames that have changed since
* the last token.
*
* The deleted property is an array with nodenames, that have been deleted
* from collection.
*
* The $syncLevel argument is basically the 'depth' of the report. If it's
* 1, you only have to report changes that happened only directly in
* immediate descendants. If it's 2, it should also include changes from
* the nodes below the child collections. (grandchildren)
*
* The $limit argument allows a client to specify how many results should
* be returned at most. If the limit is not specified, it should be treated
* as infinite.
*
* If the limit (infinite or not) is higher than you're willing to return,
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
*
* If the syncToken is expired (due to data cleanup) or unknown, you must
* return null.
*
* The limit is 'suggestive'. You are free to ignore it.
*
* @param string $calendarId
* @param string $syncToken
* @param int $syncLevel
* @param int $limit
* @return array
*/
public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null)
{
$backend = $this->getBackend($calendarId);
if ($backend) {
return $backend->getChanges($calendarId, $syncToken);
}
return [];
}
/**
* Returns all calendar objects within a calendar.
*
* Every item contains an array with the following keys:
* * calendardata - The iCalendar-compatible calendar data
* * uri - a unique key which will be used to construct the uri. This can
* be any arbitrary string, but making sure it ends with '.ics' is a
* good idea. This is only the basename, or filename, not the full
* path.
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
* '"abcdef"')
* * size - The size of the calendar objects, in bytes.
* * component - optional, a string containing the type of object, such
* as 'vevent' or 'vtodo'. If specified, this will be used to populate
* the Content-Type header.
*
* Note that the etag is optional, but it's highly encouraged to return for
* speed reasons.
*
* The calendardata is also optional. If it's not returned
* 'getCalendarObject' will be called later, which *is* expected to return
* calendardata.
*
* If neither etag or size are specified, the calendardata will be
* used/fetched to determine these numbers. If both are specified the
* amount of times this is needed is reduced by a great degree.
*
* @param mixed $calendarId
* @return array
*/
public function getCalendarObjects($calendarId)
{
$backend = $this->getBackend($calendarId);
if ($backend) {
$objs = $backend->getObjects($calendarId);
return $objs
->map(function ($date) use ($backend) {
return $backend->prepareData($date);
})
->filter(function ($event) {
return $event !== null;
})
->toArray();
}
return [];
}
/**
* Returns information from a single calendar object, based on it's object
* uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* The returned array must have the same keys as getCalendarObjects. The
* 'calendardata' object is required here though, while it's not required
* for getCalendarObjects.
*
* This method must return null if the object did not exist.
*
* @param mixed $calendarId
* @param string $objectUri
* @return array|null
*/
public function getCalendarObject($calendarId, $objectUri)
{
$backend = $this->getBackend($calendarId);
if ($backend) {
$obj = $backend->getObject($calendarId, $objectUri);
if ($obj) {
return $backend->prepareData($obj);
}
}
return [];
}
/**
* Creates a new calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible to return an etag from this function, which will be used
* in the response to this PUT request. Note that the ETag must be
* surrounded by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function createCalendarObject($calendarId, $objectUri, $calendarData)
{
return $this->updateCalendarObject($calendarId, $objectUri, $calendarData);
}
/**
* Updates an existing calendarobject, based on it's uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param mixed $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function updateCalendarObject($calendarId, $objectUri, $calendarData): ?string
{
$backend = $this->getBackend($calendarId);
return $backend ?
$backend->updateOrCreateCalendarObject($calendarId, $objectUri, $calendarData)
: null;
}
/**
* Deletes an existing calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* @param mixed $calendarId
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($calendarId, $objectUri)
{
$backend = $this->getBackend($calendarId);
if ($backend) {
$backend->deleteCalendarObject($objectUri);
}
}
/**
* Creates a new calendar for a principal.
*
* If the creation was a success, an id must be returned that can be used to
* reference this calendar in other methods, such as updateCalendar.
*
* The id can be any type, including ints, strings, objects or array.
*
* @param string $principalUri
* @param string $calendarUri
* @param array $properties
* @return void
*/
public function createCalendar($principalUri, $calendarUri, array $properties): void
{
}
/**
* Delete a calendar and all its objects.
*
* @param mixed $calendarId
* @return void
*/
public function deleteCalendar($calendarId)
{
}
/**
* Creates a new subscription for a principal.
*
* If the creation was a success, an id must be returned that can be used to reference
* this subscription in other methods, such as updateSubscription.
*
* @param string $principalUri
* @param string $uri
* @param array $properties
* @return mixed
*/
public function createSubscription($principalUri, $uri, array $properties)
{
return false;
}
/**
* Updates a subscription.
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
* you're going to process with the handle() method.
*
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documentation for more info and examples.
*
* @param mixed $subscriptionId
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
public function updateSubscription($subscriptionId, DAV\PropPatch $propPatch)
{
}
/**
* Deletes a subscription.
*
* @param mixed $subscriptionId
* @return void
*/
public function deleteSubscription($subscriptionId)
{
}
}

View File

@@ -0,0 +1,168 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CalDAV;
use Illuminate\Support\Facades\Log;
use App\Models\Instance\SpecialDate;
use Sabre\DAV\Server as SabreServer;
use Sabre\CalDAV\Plugin as CalDAVPlugin;
use App\Services\VCalendar\ExportVCalendar;
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
class CalDAVBirthdays extends AbstractCalDAVBackend
{
/**
* Returns the uri for this backend.
*
* @return string
*/
public function backendUri()
{
return 'birthdays';
}
public function getDescription()
{
return parent::getDescription()
+ [
'{DAV:}displayname' => trans('app.dav_birthdays'),
'{'.SabreServer::NS_SABREDAV.'}read-only' => true,
'{'.CalDAVPlugin::NS_CALDAV.'}calendar-description' => trans('app.dav_birthdays_description', ['name' => $this->user->name]),
'{'.CalDAVPlugin::NS_CALDAV.'}calendar-timezone' => $this->user->timezone,
'{'.CalDAVPlugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VEVENT']),
'{'.CalDAVPlugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp(ScheduleCalendarTransp::TRANSPARENT),
];
}
/**
* Extension for Calendar objects.
*
* @return string
*/
public function getExtension()
{
return '.ics';
}
/**
* Datas for this date.
*
* @param mixed $obj
* @return array
*/
public function prepareData($obj)
{
$calendardata = null;
if ($obj instanceof SpecialDate) {
try {
$calendardata = $this->refreshObject($obj);
return [
'id' => $obj->id,
'uri' => $this->encodeUri($obj),
'calendardata' => $calendardata,
'etag' => '"'.sha1($calendardata).'"',
'lastmodified' => $obj->updated_at->timestamp,
];
} catch (\Exception $e) {
Log::error(__CLASS__.' '.__FUNCTION__.': '.$e->getMessage(), [
'calendardata' => $calendardata,
$e,
]);
}
}
return [];
}
/**
* Get the new exported version of the object.
*
* @param mixed $obj date
* @return string
*/
protected function refreshObject($obj): string
{
$vcal = app(ExportVCalendar::class)
->execute([
'account_id' => $this->user->account_id,
'special_date_id' => $obj->id,
]);
return $vcal->serialize();
}
private function hasBirthday($contact)
{
if (! $contact || ! $contact->birthdate) {
return false;
}
$birthdayState = $contact->getBirthdayState();
if ($birthdayState != 'almost' && $birthdayState != 'exact') {
return false;
}
return true;
}
/**
* Returns the date for the specific uuid.
*
* @param string|null $collectionId
* @param string $uuid
* @return mixed
*/
public function getObjectUuid($collectionId, $uuid)
{
return SpecialDate::where([
'account_id' => $this->user->account_id,
'uuid' => $uuid,
])->first();
}
/**
* Returns the collection of contact's birthdays.
*
* @return \Illuminate\Support\Collection
*/
public function getObjects($collectionId)
{
// We only return the birthday of default addressBook
$contacts = $this->user->account->contacts()
->real()
->active()
->get();
return $contacts->filter(function ($contact) {
return $this->hasBirthday($contact);
})
->map(function ($contact) {
return $contact->birthdate;
});
}
/**
* Returns the collection of deleted birthdays.
*
* @param string|null $collectionId
* @return \Illuminate\Support\Collection
*/
public function getDeletedObjects($collectionId)
{
return collect();
}
/**
* @return string|null
*/
public function updateOrCreateCalendarObject($calendarId, $objectUri, $calendarData): ?string
{
return null;
}
public function deleteCalendarObject($objectUri)
{
// Not implemented
}
}

View File

@@ -0,0 +1,219 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CalDAV;
use Illuminate\Support\Arr;
use App\Models\Contact\Task;
use App\Services\Task\DestroyTask;
use Illuminate\Support\Facades\Log;
use App\Services\VCalendar\ExportTask;
use App\Services\VCalendar\ImportTask;
use Sabre\CalDAV\Plugin as CalDAVPlugin;
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
class CalDAVTasks extends AbstractCalDAVBackend
{
/**
* Returns the uri for this backend.
*
* @return string
*/
public function backendUri()
{
return 'tasks';
}
public function getDescription()
{
return parent::getDescription()
+ [
'{DAV:}displayname' => trans('app.dav_tasks'),
'{'.CalDAVPlugin::NS_CALDAV.'}calendar-description' => trans('app.dav_tasks_description', ['name' => $this->user->name]),
'{'.CalDAVPlugin::NS_CALDAV.'}calendar-timezone' => $this->user->timezone,
'{'.CalDAVPlugin::NS_CALDAV.'}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO']),
'{'.CalDAVPlugin::NS_CALDAV.'}schedule-calendar-transp' => new ScheduleCalendarTransp(ScheduleCalendarTransp::TRANSPARENT),
];
}
/**
* Returns the collection of all tasks.
*
* @param mixed|null $collectionId
* @return \Illuminate\Support\Collection
*/
public function getObjects($collectionId)
{
return $this->user->account
->tasks()
->get();
}
/**
* Returns the collection of deleted tasks.
*
* @param string|null $collectionId
* @return \Illuminate\Support\Collection
*/
public function getDeletedObjects($collectionId)
{
return collect();
}
/**
* Returns the contact for the specific uuid.
*
* @param mixed|null $collectionId
* @param string $uuid
* @return mixed
*/
public function getObjectUuid($collectionId, $uuid)
{
return Task::where([
'account_id' => $this->user->account_id,
'uuid' => $uuid,
])->first();
}
/**
* Extension for Calendar objects.
*
* @return string
*/
public function getExtension()
{
return '.ics';
}
/**
* Datas for this task.
*
* @param mixed $obj
* @return array
*/
public function prepareData($obj)
{
$calendardata = null;
if ($obj instanceof Task) {
try {
$calendardata = $this->refreshObject($obj);
return [
'id' => $obj->id,
'uri' => $this->encodeUri($obj),
'calendardata' => $calendardata,
'etag' => '"'.sha1($calendardata).'"',
'lastmodified' => $obj->updated_at->timestamp,
];
} catch (\Exception $e) {
Log::error(__CLASS__.' '.__FUNCTION__.': '.$e->getMessage(), [
'calendardata' => $calendardata,
$e,
]);
}
}
return [];
}
/**
* Get the new exported version of the object.
*
* @param mixed $obj task
* @return string
*/
protected function refreshObject($obj): string
{
$vcal = app(ExportTask::class)
->execute([
'account_id' => $this->user->account_id,
'task_id' => $obj->id,
]);
return $vcal->serialize();
}
/**
* Updates an existing calendarobject, based on it's uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function updateOrCreateCalendarObject($calendarId, $objectUri, $calendarData): ?string
{
$task_id = null;
if ($objectUri) {
$task = $this->getObject($this->backendUri(), $objectUri);
if ($task) {
$task_id = $task->id;
}
}
try {
$result = app(ImportTask::class)
->execute([
'account_id' => $this->user->account_id,
'task_id' => $task_id,
'entry' => $calendarData,
]);
if (! Arr::has($result, 'error')) {
$task = Task::where('account_id', $this->user->account_id)
->find($result['task_id']);
$calendar = $this->prepareData($task);
return $calendar['etag'];
}
} catch (\Exception $e) {
Log::error(__CLASS__.' '.__FUNCTION__.': '.$e->getMessage(), [
'calendarId' => $calendarId,
'objectUri' => $objectUri,
'calendarData' => $calendarData,
$e,
]);
}
return null;
}
/**
* Deletes an existing calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($objectUri)
{
$task = $this->getObject($this->backendUri(), $objectUri);
if ($task) {
try {
app(DestroyTask::class)
->execute([
'account_id' => $this->user->account_id,
'task_id' => $task->id,
]);
} catch (\Exception $e) {
Log::error(__CLASS__.' '.__FUNCTION__.': '.$e->getMessage(), [
'objectUri' => $objectUri,
$e,
]);
}
}
}
}

View File

@@ -0,0 +1,118 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CalDAV;
interface ICalDAVBackend
{
/**
* Returns a list of properties for a principal.
*
* Every project is an array with the following keys:
* * id, a unique id that will be used by other functions to modify the
* calendar. This can be the same as the uri or a database key.
* * uri, which is the basename of the uri with which the calendar is
* accessed.
* * principaluri. The owner of the calendar. Almost always the same as
* principalUri passed to this method.
*
* Furthermore it can contain webdav properties in clark notation. A very
* common one is '{DAV:}displayname'.
*
* Many clients also require:
* {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* For this property, you can just return an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet.
*
* If you return {http://sabredav.org/ns}read-only and set the value to 1,
* ACL will automatically be put in read-only mode.
*
************************
* == From Subscription :
* Furthermore, all the subscription info must be returned too:
*
* 1. {DAV:}displayname
* 2. {http://apple.com/ns/ical/}refreshrate
* 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
* should not be stripped).
* 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
* should not be stripped).
* 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
* attachments should not be stripped).
* 6. {http://calendarserver.org/ns/}source (Must be a
* Sabre\DAV\Property\Href).
* 7. {http://apple.com/ns/ical/}calendar-color
* 8. {http://apple.com/ns/ical/}calendar-order
* 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
* (should just be an instance of
* Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
* default components).
*
* @return array
*/
public function getDescription();
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken in the specified calendar.
*
* @param string|null $calendarId
* @param string $syncToken
* @return array
*/
public function getChanges($calendarId, $syncToken);
/**
* Returns calendar object.
*
* It returns an array with the following keys:
* * calendardata - The iCalendar-compatible calendar data
* * uri - a unique key which will be used to construct the uri. This can
* be any arbitrary string, but making sure it ends with '.ics' is a
* good idea. This is only the basename, or filename, not the full
* path.
* * lastmodified - a timestamp of the last modification time
* * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
* '"abcdef"')
* * size - The size of the calendar objects, in bytes.
* * component - optional, a string containing the type of object, such
* as 'vevent' or 'vtodo'. If specified, this will be used to populate
* the Content-Type header.
*
* Note that the etag is optional, but it's highly encouraged to return for
* speed reasons.
*
* @param mixed $obj
* @return array
*/
public function prepareData($obj);
/**
* Updates an existing calendarobject, based on it's uri.
*
* The object uri is only the basename, or filename and not a full path.
*
* It is possible return an etag from this function, which will be used in
* the response to this PUT request. Note that the ETag must be surrounded
* by double-quotes.
*
* However, you should only really return this ETag if you don't mangle the
* calendar-data. If the result of a subsequent GET to this object is not
* the exact same as this request body, you should omit the ETag.
*
* @param string|null $calendarId
* @param string $objectUri
* @param string $calendarData
* @return string|null
*/
public function updateOrCreateCalendarObject($calendarId, $objectUri, $calendarData): ?string;
/**
* Deletes an existing calendar object.
*
* The object uri is only the basename, or filename and not a full path.
*
* @param string $objectUri
* @return void
*/
public function deleteCalendarObject($objectUri);
}

View File

@@ -0,0 +1,100 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CardDAV;
use Sabre\CardDAV\AddressBook as BaseAddressBook;
class AddressBook extends BaseAddressBook
{
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL()
{
return [
[
'privilege' => '{DAV:}read',
'principal' => '{DAV:}owner',
'protected' => true,
],
[
'privilege' => '{DAV:}write-content',
'principal' => '{DAV:}owner',
'protected' => true,
],
[
'privilege' => '{DAV:}bind',
'principal' => '{DAV:}owner',
'protected' => true,
],
[
'privilege' => '{DAV:}unbind',
'principal' => '{DAV:}owner',
'protected' => true,
],
[
'privilege' => '{DAV:}write-properties',
'principal' => '{DAV:}owner',
'protected' => true,
],
];
}
/**
* This method returns the ACL's for card nodes in this address book.
* The result of this method automatically gets passed to the
* card nodes in this address book.
*
* @return array
*/
public function getChildACL()
{
return $this->getACL();
}
/**
* Returns the last modification date.
*
* @return int|null
*/
public function getLastModified(): ?int
{
$carddavBackend = $this->carddavBackend;
if ($carddavBackend instanceof CardDAVBackend) {
$date = $carddavBackend->getLastModified(null);
if (! is_null($date)) {
return (int) $date->timestamp;
}
}
return null;
}
/**
* This method returns the current sync-token for this collection.
* This can be any string.
*
* If null is returned from this function, the plugin assumes there's no
* sync information available.
*
* @return string|null
*/
public function getSyncToken(): ?string
{
$carddavBackend = $this->carddavBackend;
if ($carddavBackend instanceof CardDAVBackend) {
return (string) $carddavBackend->refreshSyncToken(null)->id;
}
return null;
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CardDAV;
use Sabre\CardDAV\AddressBookHome as BaseAddressBookHome;
class AddressBookHome extends BaseAddressBookHome
{
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL()
{
return [
[
'privilege' => '{DAV:}read',
'principal' => '{DAV:}owner',
'protected' => true,
],
];
}
/**
* Returns a list of addressbooks.
*
* @return array
*/
public function getChildren()
{
$addressBooks = $this->carddavBackend->getAddressBooksForUser($this->principalUri);
return collect($addressBooks)->map(function (array $addressBook): AddressBook {
return new AddressBook($this->carddavBackend, $addressBook);
})->toArray();
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CardDAV;
use Sabre\DAVACL\IACL;
use Sabre\DAVACL\ACLTrait;
use Sabre\CardDAV\AddressBookRoot as BaseAddressBookRoot;
class AddressBookRoot extends BaseAddressBookRoot implements IACL
{
use ACLTrait;
/**
* Returns a list of ACE's for this node.
*
* Each ACE has the following properties:
* * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
* currently the only supported privileges
* * 'principal', a url to the principal who owns the node
* * 'protected' (optional), indicating that this ACE is not allowed to
* be updated.
*
* @return array
*/
public function getACL()
{
return [
[
'privilege' => '{DAV:}read',
'principal' => '{DAV:}authenticated',
'protected' => true,
],
];
}
/**
* This method returns a node for a principal.
*
* The passed array contains principal information, and is guaranteed to
* at least contain a uri item. Other properties may or may not be
* supplied by the authentication backend.
*
* @param array $principal
* @return \Sabre\DAV\INode
* @psalm-suppress ParamNameMismatch
*/
public function getChildForPrincipal(array $principal)
{
return new AddressBookHome($this->carddavBackend, $principal['uri']);
}
}

View File

@@ -0,0 +1,486 @@
<?php
namespace App\Http\Controllers\DAV\Backend\CardDAV;
use Sabre\DAV;
use App\Jobs\Dav\UpdateVCard;
use App\Models\Contact\Contact;
use App\Services\VCard\GetEtag;
use App\Models\Account\AddressBook;
use App\Services\VCard\ExportVCard;
use Illuminate\Support\Facades\Bus;
use Illuminate\Support\Facades\Log;
use Sabre\DAV\Server as SabreServer;
use Sabre\CardDAV\Backend\SyncSupport;
use Sabre\CalDAV\Plugin as CalDAVPlugin;
use Sabre\CardDAV\Backend\AbstractBackend;
use Sabre\CardDAV\Plugin as CardDAVPlugin;
use Sabre\DAV\Sync\Plugin as DAVSyncPlugin;
use App\Services\Contact\Contact\SetMeContact;
use App\Services\Contact\Contact\DestroyContact;
use App\Http\Controllers\DAV\Backend\IDAVBackend;
use App\Http\Controllers\DAV\Backend\SyncDAVBackend;
use App\Http\Controllers\DAV\DAVACL\PrincipalBackend;
use App\Services\DavClient\Utils\Model\ContactUpdateDto;
class CardDAVBackend extends AbstractBackend implements SyncSupport, IDAVBackend
{
use SyncDAVBackend;
/**
* Returns the uri for this backend.
*
* @return string
*/
public function backendUri()
{
return 'contacts';
}
/**
* Returns the list of addressbooks for a specific user.
*
* Every addressbook should have the following properties:
* id - an arbitrary unique id
* uri - the 'basename' part of the url
* principaluri - Same as the passed parameter
*
* Any additional clark-notation property may be passed besides this. Some
* common ones are :
* {DAV:}displayname
* {urn:ietf:params:xml:ns:carddav}addressbook-description
* {http://calendarserver.org/ns/}getctag
*
* @param string $principalUri
* @return array
*/
public function getAddressBooksForUser($principalUri)
{
$result = [];
$result[] = $this->getDefaultAddressBook();
$addressBooks = AddressBook::where('account_id', $this->user->account_id)
->get();
foreach ($addressBooks as $addressBook) {
$result[] = $this->getAddressBookDetails($addressBook);
}
return $result;
}
private function getDefaultAddressBook()
{
$des = $this->getAddressBookDetails(null);
$me = auth()->user()->me;
if ($me) {
$des += [
'{'.CalDAVPlugin::NS_CALENDARSERVER.'}me-card' => '/'.config('laravelsabre.path').'/addressbooks/'.$this->user->email.'/contacts/'.$this->encodeUri($me),
];
}
return $des;
}
private function getAddressBookDetails($addressBook)
{
$id = $addressBook ? $addressBook->name : $this->backendUri();
$token = $this->getCurrentSyncToken($addressBook);
$des = [
'id' => $id,
'uri' => $id,
'principaluri' => PrincipalBackend::getPrincipalUser($this->user),
'{DAV:}displayname' => trans('app.dav_contacts'),
'{'.CardDAVPlugin::NS_CARDDAV.'}addressbook-description' => $addressBook ? $addressBook->description : trans('app.dav_contacts_description', ['name' => $this->user->name]),
];
if ($token) {
$des += [
'{DAV:}sync-token' => $token->id,
'{'.SabreServer::NS_SABREDAV.'}sync-token' => $token->id,
'{'.CalDAVPlugin::NS_CALENDARSERVER.'}getctag' => DAVSyncPlugin::SYNCTOKEN_PREFIX.$token->id,
];
}
return $des;
}
/**
* Extension for Calendar objects.
*
* @return string
*/
public function getExtension()
{
return '.vcf';
}
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken in the specified address book.
*
* This function should return an array, such as the following:
*
* [
* 'syncToken' => 'The current synctoken',
* 'added' => [
* 'new.txt',
* ],
* 'modified' => [
* 'modified.txt',
* ],
* 'deleted' => [
* 'foo.php.bak',
* 'old.txt'
* ]
* ];
*
* The returned syncToken property should reflect the *current* syncToken
* of the calendar, as reported in the {http://sabredav.org/ns}sync-token
* property. This is needed here too, to ensure the operation is atomic.
*
* If the $syncToken argument is specified as null, this is an initial
* sync, and all members should be reported.
*
* The modified property is an array of nodenames that have changed since
* the last token.
*
* The deleted property is an array with nodenames, that have been deleted
* from collection.
*
* The $syncLevel argument is basically the 'depth' of the report. If it's
* 1, you only have to report changes that happened only directly in
* immediate descendants. If it's 2, it should also include changes from
* the nodes below the child collections. (grandchildren)
*
* The $limit argument allows a client to specify how many results should
* be returned at most. If the limit is not specified, it should be treated
* as infinite.
*
* If the limit (infinite or not) is higher than you're willing to return,
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
*
* If the syncToken is expired (due to data cleanup) or unknown, you must
* return null.
*
* The limit is 'suggestive'. You are free to ignore it.
*
* @param string $addressBookId
* @param string $syncToken
* @param int $syncLevel
* @param int $limit
* @return array|null
*/
public function getChangesForAddressBook($addressBookId, $syncToken, $syncLevel, $limit = null): ?array
{
return $this->getChanges($addressBookId, $syncToken);
}
/**
* Prepare datas for this contact.
*
* @param Contact $contact
* @return array
*/
public function prepareCard($contact): array
{
$carddata = $contact->vcard;
try {
if (empty($carddata)) {
$carddata = $this->refreshObject($contact);
}
$etag = app(GetEtag::class)->execute([
'account_id' => $this->user->account_id,
'contact_id' => $contact->id,
]);
return [
'contact_id' => $contact->id,
'uri' => $this->encodeUri($contact),
'carddata' => $carddata,
'etag' => $etag,
'distant_etag' => $contact->distant_etag,
'lastmodified' => $contact->updated_at->timestamp,
];
} catch (\Exception $e) {
Log::error(__CLASS__.' '.__FUNCTION__.': '.$e->getMessage(), [
'carddata' => $carddata,
'contact_id' => $contact->id,
$e,
]);
throw $e;
}
}
/**
* Get the new exported version of the object.
*
* @param mixed $obj contact
* @return string
*/
protected function refreshObject($obj): string
{
$vcard = app(ExportVCard::class)
->execute([
'account_id' => $this->user->account_id,
'contact_id' => $obj->id,
]);
return $vcard->serialize();
}
/**
* Returns the contact for the specific uuid.
*
* @param mixed|null $collectionId
* @param string $uuid
* @return Contact
*/
public function getObjectUuid($collectionId, $uuid)
{
$addressBook = null;
if ($collectionId && $collectionId != $this->backendUri()) {
$addressBook = AddressBook::where([
'account_id' => $this->user->account_id,
'name' => $collectionId,
])->first();
}
return Contact::where([
'account_id' => $this->user->account_id,
'uuid' => $uuid,
'address_book_id' => $addressBook ? $addressBook->id : null,
])->first();
}
/**
* Returns the collection of all active contacts.
*
* @param string|null $collectionId
* @return \Illuminate\Support\Collection<array-key, Contact>
*/
public function getObjects($collectionId)
{
return $this->user->account->contacts($collectionId)
->real()
->active()
->get();
}
/**
* Returns the collection of deleted contacts.
*
* @param string|null $collectionId
* @return \Illuminate\Support\Collection<array-key, Contact>
*/
public function getDeletedObjects($collectionId)
{
return $this->user->account->contacts($collectionId)
->onlyTrashed()
->get();
}
/**
* Returns all cards for a specific addressbook id.
*
* This method should return the following properties for each card:
* * carddata - raw vcard data
* * uri - Some unique url
* * lastmodified - A unix timestamp
*
* It's recommended to also return the following properties:
* * etag - A unique etag. This must change every time the card changes.
* * size - The size of the card in bytes.
*
* If these last two properties are provided, less time will be spent
* calculating them. If they are specified, you can also ommit carddata.
* This may speed up certain requests, especially with large cards.
*
* @param mixed $addressbookId
* @return array
*/
public function getCards($addressbookId)
{
$contacts = $this->getObjects($addressbookId);
return $contacts->map(function ($contact) {
return $this->prepareCard($contact);
})->toArray();
}
/**
* Returns a specific card.
*
* The same set of properties must be returned as with getCards. The only
* exception is that 'carddata' is absolutely required.
*
* If the card does not exist, you must return false.
*
* @param mixed $addressBookId
* @param string $cardUri
* @return array|bool
*/
public function getCard($addressBookId, $cardUri)
{
$contact = $this->getObject($addressBookId, $cardUri);
if ($contact) {
return $this->prepareCard($contact);
}
return false;
}
/**
* Creates a new card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressBooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag is for the
* newly created resource, and must be enclosed with double quotes (that
* is, the string itself must contain the double quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string $cardData
* @return string|null
*/
public function createCard($addressBookId, $cardUri, $cardData)
{
return $this->updateCard($addressBookId, $cardUri, $cardData);
}
/**
* Updates a card.
*
* The addressbook id will be passed as the first argument. This is the
* same id as it is returned from the getAddressBooksForUser method.
*
* The cardUri is a base uri, and doesn't include the full path. The
* cardData argument is the vcard body, and is passed as a string.
*
* It is possible to return an ETag from this method. This ETag should
* match that of the updated resource, and must be enclosed with double
* quotes (that is: the string itself must contain the actual quotes).
*
* You should only return the ETag if you store the carddata as-is. If a
* subsequent GET request on the same card does not have the same body,
* byte-by-byte and you did return an ETag here, clients tend to get
* confused.
*
* If you don't return an ETag, you can just return null.
*
* @param mixed $addressBookId
* @param string $cardUri
* @param string|resource $cardData
* @return string|null
*/
public function updateCard($addressBookId, $cardUri, $cardData): ?string
{
$job = new UpdateVCard($this->user, $addressBookId, new ContactUpdateDto($cardUri, null, $cardData));
Bus::batch([$job])
->allowFailures()
->dispatch();
return null;
}
/**
* Deletes a card.
*
* @param mixed $addressBookId
* @param string $cardUri
* @return bool
*/
public function deleteCard($addressBookId, $cardUri)
{
$contact = $this->getObject($addressBookId, $cardUri);
if ($contact) {
DestroyContact::dispatch([
'account_id' => $contact->account_id,
'contact_id' => $contact->id,
]);
return true;
}
return false;
}
/**
* Updates properties for an address book.
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
* you're going to process with the handle() method.
*
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documentation for more info and examples.
*
* @param string $addressBookId
* @param \Sabre\DAV\PropPatch $propPatch
* @return bool|null
*/
public function updateAddressBook($addressBookId, DAV\PropPatch $propPatch): ?bool
{
$propPatch->handle('{'.CalDAVPlugin::NS_CALENDARSERVER.'}me-card', function ($props) use ($addressBookId) {
$contact = $this->getObject($addressBookId, $props->getHref());
$data = [
'contact_id' => $contact->id,
'account_id' => $this->user->account_id,
'user_id' => $this->user->id,
];
app(SetMeContact::class)->execute($data);
return true;
});
return null;
}
/**
* Creates a new address book.
*
* This method should return the id of the new address book. The id can be
* in any format, including ints, strings, arrays or objects.
*
* @param string $principalUri
* @param string $url Just the 'basename' of the url.
* @param array $properties
* @return int|bool
*/
public function createAddressBook($principalUri, $url, array $properties)
{
return false;
}
/**
* Deletes an entire addressbook and all its contents.
*
* @param mixed $addressBookId
* @return bool|null
*/
public function deleteAddressBook($addressBookId)
{
return false;
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace App\Http\Controllers\DAV\Backend;
interface IDAVBackend
{
/**
* Returns the uri for this backend.
*
* @return string
*/
public function backendUri();
/**
* Returns the object for the specific uuid.
*
* @param string|null $collectionId
* @param string $uuid
* @return mixed
*/
public function getObjectUuid($collectionId, $uuid);
/**
* Returns the collection of objects.
*
* @param string|null $collectionId
* @return \Illuminate\Support\Collection
*/
public function getObjects($collectionId);
/**
* Returns the extension for this backend.
*
* @return string
*/
public function getExtension();
}

View File

@@ -0,0 +1,288 @@
<?php
namespace App\Http\Controllers\DAV\Backend;
use App\Traits\WithUser;
use Illuminate\Support\Str;
use App\Models\User\SyncToken;
trait SyncDAVBackend
{
use WithUser;
/**
* This method returns a sync-token for this collection.
*
* If null is returned from this function, the plugin assumes there's no
* sync information available.
*
* @param string|null $collectionId
* @return SyncToken|null
*/
public function getCurrentSyncToken($collectionId): ?SyncToken
{
$tokens = SyncToken::where([
'account_id' => $this->user->account_id,
'user_id' => $this->user->id,
'name' => $collectionId ?? $this->backendUri(),
])
->orderBy('created_at')
->get();
return $tokens->count() > 0 ? $tokens->last() : null;
}
/**
* Create or refresh the token if a change happened.
*
* @param string|null $collectionId
* @return SyncToken
*/
public function refreshSyncToken($collectionId): SyncToken
{
$token = $this->getCurrentSyncToken($collectionId);
if (! $token || $token->timestamp < $this->getLastModified($collectionId)) {
$token = $this->createSyncTokenNow($collectionId);
}
return $token;
}
/**
* Get SyncToken by token id.
*
* @param string|null $collectionId
* @param string $syncToken
* @return SyncToken|null
*/
protected function getSyncToken($collectionId, $syncToken)
{
/** @var SyncToken|null */
return SyncToken::where([
'account_id' => $this->user->account_id,
'user_id' => $this->user->id,
'name' => $collectionId ?? $this->backendUri(),
])
->find($syncToken);
}
/**
* Create a token with now timestamp.
*
* @param string|null $collectionId
* @return SyncToken
*/
private function createSyncTokenNow($collectionId)
{
return SyncToken::create([
'account_id' => $this->user->account_id,
'user_id' => $this->user->id,
'name' => $collectionId ?? $this->backendUri(),
'timestamp' => now(),
]);
}
/**
* Returns the last modification date.
*
* @param string|null $collectionId
* @return \Carbon\Carbon|null
*/
public function getLastModified($collectionId)
{
return $this->getObjects($collectionId)
->map(function ($object) {
return $object->updated_at;
})
->max();
}
/**
* The getChanges method returns all the changes that have happened, since
* the specified syncToken.
*
* This function should return an array, such as the following:
*
* [
* 'syncToken' => 'The current synctoken',
* 'added' => [
* 'new.txt',
* ],
* 'modified' => [
* 'modified.txt',
* ],
* 'deleted' => [
* 'foo.php.bak',
* 'old.txt'
* ]
* );
*
* The returned syncToken property should reflect the *current* syncToken
* , as reported in the {http://sabredav.org/ns}sync-token
* property This is * needed here too, to ensure the operation is atomic.
*
* If the $syncToken argument is specified as null, this is an initial
* sync, and all members should be reported.
*
* The modified property is an array of nodenames that have changed since
* the last token.
*
* The deleted property is an array with nodenames, that have been deleted
* from collection.
*
* The $syncLevel argument is basically the 'depth' of the report. If it's
* 1, you only have to report changes that happened only directly in
* immediate descendants. If it's 2, it should also include changes from
* the nodes below the child collections. (grandchildren)
*
* The $limit argument allows a client to specify how many results should
* be returned at most. If the limit is not specified, it should be treated
* as infinite.
*
* If the limit (infinite or not) is higher than you're willing to return,
* you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
*
* If the syncToken is expired (due to data cleanup) or unknown, you must
* return null.
*
* The limit is 'suggestive'. You are free to ignore it.
*
* @param string $calendarId
* @param string $syncToken
* @return array|null
*/
public function getChanges($calendarId, $syncToken): ?array
{
$token = null;
$timestamp = null;
if (! empty($syncToken)) {
$token = $this->getSyncToken($calendarId, $syncToken);
if (is_null($token)) {
// syncToken is not recognized
return null;
}
$timestamp = $token->timestamp;
}
$objs = $this->getObjects($calendarId);
$modified = $objs->filter(function ($obj) use ($timestamp) {
return ! is_null($timestamp) &&
$obj->updated_at > $timestamp &&
$obj->created_at < $timestamp;
});
$added = $objs->filter(function ($obj) use ($timestamp) {
return is_null($timestamp) ||
$obj->created_at >= $timestamp;
});
$deleted = $this->getDeletedObjects($calendarId)
->filter(function ($obj) use ($timestamp) {
$d = $obj->deleted_at;
return is_null($timestamp) ||
$obj->deleted_at >= $timestamp;
});
return [
'syncToken' => $this->refreshSyncToken($calendarId)->id,
'added' => $added->map(function ($obj) {
return $this->encodeUri($obj);
})->values()->toArray(),
'modified' => $modified->map(function ($obj) {
$this->refreshObject($obj);
return $this->encodeUri($obj);
})->values()->toArray(),
'deleted' => $deleted->map(function ($obj) {
return $this->encodeUri($obj);
})->values()->toArray(),
];
}
protected function encodeUri($obj): string
{
if (empty($obj->uuid)) {
// refresh model from database
$obj->refresh();
if (empty($obj->uuid)) {
// in case uuid is still not set, do it
$obj->forceFill([
'uuid' => Str::uuid(),
])->save();
}
}
return urlencode($obj->uuid.$this->getExtension());
}
private function decodeUri($uri): string
{
return pathinfo(urldecode($uri), PATHINFO_FILENAME);
}
/**
* Returns the contact uuid for the specific uri.
*
* @param string $uri
* @return string
*/
public function getUuid($uri): string
{
return $this->decodeUri($uri);
}
/**
* Returns the contact for the specific uri.
*
* @param string|null $collectionId
* @param string $uri
* @return mixed
*/
public function getObject($collectionId, $uri)
{
try {
return $this->getObjectUuid($collectionId, $this->getUuid($uri));
} catch (\Exception $e) {
// Object not found
}
}
/**
* Returns the object for the specific uuid.
*
* @param string|null $collectionId
* @param string $uuid
* @return mixed
*/
abstract public function getObjectUuid($collectionId, $uuid);
/**
* Returns the collection of objects.
*
* @param string|null $collectionId
* @return \Illuminate\Support\Collection
*/
abstract public function getObjects($collectionId);
/**
* Returns the collection of objects.
*
* @param string|null $collectionId
* @return \Illuminate\Support\Collection
*/
abstract public function getDeletedObjects($collectionId);
abstract public function getExtension();
/**
* Get the new exported version of the object.
*
* @param mixed $obj
* @return string
*/
abstract protected function refreshObject($obj): string;
}

View File

@@ -0,0 +1,203 @@
<?php
namespace App\Http\Controllers\DAV\DAVACL;
use Sabre\DAV;
use App\Traits\WithUser;
use Illuminate\Support\Str;
use Sabre\DAV\Server as SabreServer;
use Sabre\DAVACL\PrincipalBackend\AbstractBackend;
class PrincipalBackend extends AbstractBackend
{
use WithUser;
/**
* This is the prefix that will be used to generate principal urls.
*
* @var string
*/
public const PRINCIPAL_PREFIX = 'principals/';
/**
* Get the principal for user.
*
* @return string
*/
public static function getPrincipalUser($user): string
{
return static::PRINCIPAL_PREFIX.$user->email;
}
protected function getPrincipals()
{
return [
[
'uri' => static::getPrincipalUser($this->user),
'{DAV:}displayname' => $this->user->name,
'{'.SabreServer::NS_SABREDAV.'}email-address' => $this->user->email,
],
];
}
/**
* Returns a list of principals based on a prefix.
*
* This prefix will often contain something like 'principals'. You are only
* expected to return principals that are in this base path.
*
* You are expected to return at least a 'uri' for every user, you can
* return any additional properties if you wish so. Common properties are:
* {DAV:}displayname
* {http://sabredav.org/ns}email-address - This is a custom SabreDAV
* field that's actually injected in a number of other properties. If
* you have an email address, use this property.
*
* @param string $prefixPath
* @return array
*/
public function getPrincipalsByPrefix($prefixPath)
{
$prefixPath = Str::finish($prefixPath, '/');
return array_filter($this->getPrincipals(), function ($principal) use ($prefixPath) {
return ! $prefixPath || strpos($principal['uri'], $prefixPath) == 0;
});
}
/**
* Returns a specific principal, specified by its path.
* The returned structure should be the exact same as from
* getPrincipalsByPrefix.
*
* @param string $path
* @return array
*/
public function getPrincipalByPath($path)
{
foreach ($this->getPrincipalsByPrefix(static::PRINCIPAL_PREFIX) as $principal) {
if ($principal['uri'] === $path) {
return $principal;
}
}
return [];
}
/**
* Updates one ore more webdav properties on a principal.
*
* The list of mutations is stored in a Sabre\DAV\PropPatch object.
* To do the actual updates, you must tell this object which properties
* you're going to process with the handle() method.
*
* Calling the handle method is like telling the PropPatch object "I
* promise I can handle updating this property".
*
* Read the PropPatch documentation for more info and examples.
*
* @param string $path
* @param \Sabre\DAV\PropPatch $propPatch
* @return void
*/
public function updatePrincipal($path, DAV\PropPatch $propPatch)
{
}
/**
* This method is used to search for principals matching a set of
* properties.
*
* This search is specifically used by RFC3744's principal-property-search
* REPORT.
*
* The actual search should be a unicode-non-case-sensitive search. The
* keys in searchProperties are the WebDAV property names, while the values
* are the property values to search on.
*
* By default, if multiple properties are submitted to this method, the
* various properties should be combined with 'AND'. If $test is set to
* 'anyof', it should be combined using 'OR'.
*
* This method should simply return an array with full principal uri's.
*
* If somebody attempted to search on a property the backend does not
* support, you should simply return 0 results.
*
* You can also just return 0 results if you choose to not support
* searching at all, but keep in mind that this may stop certain features
* from working.
*
* @param string $prefixPath
* @param array $searchProperties
* @param string $test
* @return array
*/
public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof')
{
$result = [];
$principals = $this->getPrincipalsByPrefix($prefixPath);
if (! $principals) {
return $result;
}
foreach ($principals as $principal) {
$ok = false;
foreach ($searchProperties as $key => $value) {
if ($principal[$key] == $value) {
$ok = true;
} elseif ($test == 'allof') {
$ok = false;
break;
}
}
if ($ok) {
$result[] = $principal['uri'];
}
}
return $result;
}
/**
* Returns the list of members for a group-principal.
*
* @param string $principal
* @return array
*/
public function getGroupMemberSet($principal)
{
$principal = $this->getPrincipalByPath($principal);
if (! $principal) {
return [];
}
return [
$principal['uri'],
];
}
/**
* Returns the list of groups a principal is a member of.
*
* @param string $principal
* @return array
*/
public function getGroupMembership($principal)
{
return $this->getGroupMemberSet($principal);
}
/**
* Updates the list of group members for a group principal.
*
* The principals should be passed as a list of uri's.
*
* @param string $principal
* @param array $members
* @return void
*/
public function setGroupMemberSet($principal, array $members)
{
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace App\Http\Controllers\DAV;
use Sabre\DAV\Server;
use Sabre\DAV\ServerPlugin;
use Sabre\HTTP\RequestInterface;
use Sabre\HTTP\ResponseInterface;
/**
* Redirect all GET methods to settings page.
*/
class DAVRedirect extends ServerPlugin
{
public function initialize(Server $server)
{
$server->on('method:GET', [$this, 'httpGet'], 500);
}
/**
* This method intercepts GET requests to collections and returns the html.
*
* @param RequestInterface $request
* @param ResponseInterface $response
* @return bool
*/
public function httpGet(RequestInterface $request, ResponseInterface $response)
{
$response->setStatus(302);
$response->setHeader('Location', route('settings.dav'));
return false;
}
}

View File

@@ -0,0 +1,184 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\DateHelper;
use App\Models\Contact\Debt;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use function Safe\json_encode;
use App\Helpers\InstanceHelper;
use Illuminate\Support\Collection;
use App\Http\Resources\Debt\Debt as DebtResource;
class DashboardController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
{
$account = auth()->user()->account()
->withCount(
'contacts', 'reminders', 'notes', 'activities', 'gifts', 'tasks'
)->with('debts.contact')
->first();
$numberOfContacts = $account->contacts()
->real()
->active()
->count();
if ($numberOfContacts === 0) {
return view('dashboard.blank');
}
// Fetch last updated contacts
$lastUpdatedContactsCollection = collect([]);
$lastUpdatedContacts = $account->contacts()
->real()
->active()
->alive()
->latest('last_consulted_at')
->limit(10)
->get();
foreach ($lastUpdatedContacts as $contact) {
$data = [
'id' => $contact->hashID(),
'has_avatar' => $contact->has_avatar,
'avatar_url' => $contact->getAvatarURL(),
'initials' => $contact->getInitials(),
'default_avatar_color' => $contact->default_avatar_color,
'complete_name' => $contact->name,
];
$lastUpdatedContactsCollection->push(json_encode($data));
}
$debts = $account->debts()->inProgress();
$debt_due = $debts->due()->get()
->reduce(function ($totalDueDebt, Debt $debt) {
return $totalDueDebt + $debt->amount;
}, 0);
$debt_owed = $debts->owed()->get()
->reduce(function ($totalOwedDebt, Debt $debt) {
return $totalOwedDebt + $debt->amount;
}, 0);
// get last 3 changelog entries
$changelogs = InstanceHelper::getChangelogEntries(3);
// Load the reminderOutboxes for the upcoming three months
$reminderOutboxes = [
0 => AccountHelper::getUpcomingRemindersForMonth(auth()->user()->account, 0),
1 => AccountHelper::getUpcomingRemindersForMonth(auth()->user()->account, 1),
2 => AccountHelper::getUpcomingRemindersForMonth(auth()->user()->account, 2),
];
$data = [
'lastUpdatedContacts' => $lastUpdatedContactsCollection,
'number_of_contacts' => $numberOfContacts,
'number_of_reminders' => $account->reminders_count,
'number_of_notes' => $account->notes_count,
'number_of_activities' => $account->activities_count,
'number_of_gifts' => $account->gifts_count,
'number_of_tasks' => $account->tasks_count,
'debt_due' => $debt_due,
'debt_owed' => $debt_owed,
'debts' => $debts,
'user' => auth()->user(),
'changelogs' => $changelogs,
'reminderOutboxes' => $reminderOutboxes,
];
return view('dashboard.index', $data);
}
/**
* Get calls for the dashboard.
*
* @return Collection
*/
public function calls()
{
$callsCollection = collect([]);
$calls = auth()->user()->account->calls()
->get()
->reject(function ($call) {
return $call->contact === null;
})
->take(15);
foreach ($calls as $call) {
$data = [
'id' => $call->id,
'called_at' => DateHelper::getShortDate($call->called_at),
'name' => $call->contact->getIncompleteName(),
'contact_id' => $call->contact->hashID(),
];
$callsCollection->push($data);
}
return $callsCollection;
}
/**
* Get notes for the dashboard.
*
* @return Collection
*/
public function notes()
{
$notesCollection = collect([]);
$notes = auth()->user()->account->notes()->favorited()->get();
foreach ($notes as $note) {
$data = [
'id' => $note->id,
'body' => $note->body,
'created_at' => DateHelper::getShortDate($note->created_at),
'name' => $note->contact->getIncompleteName(),
'contact' => [
'id' => $note->contact->hashID(),
'has_avatar' => $note->contact->has_avatar,
'avatar_url' => $note->contact->getAvatarURL(),
'initials' => $note->contact->getInitials(),
'default_avatar_color' => $note->contact->default_avatar_color,
'complete_name' => $note->contact->name,
],
];
$notesCollection->push($data);
}
return $notesCollection;
}
/**
* Get debts for the dashboard.
*
* @return Collection
*/
public function debts()
{
$debtsCollection = collect([]);
$debts = auth()->user()->account->debts()->get();
foreach ($debts as $debt) {
$debtsCollection->push(new DebtResource($debt));
}
return $debtsCollection;
}
/**
* Save the current active tab to the User table.
*/
public function setTab(Request $request)
{
auth()->user()->dashboard_active_tab = $request->input('tab');
auth()->user()->save();
}
}

View File

@@ -0,0 +1,48 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Instance\Emotion\Emotion;
use App\Models\Instance\Emotion\PrimaryEmotion;
use App\Models\Instance\Emotion\SecondaryEmotion;
use App\Http\Resources\Emotion\Emotion as EmotionResource;
class EmotionController extends Controller
{
/**
* Get the list of primary emotions.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function primaries()
{
return EmotionResource::collection(PrimaryEmotion::get());
}
/**
* Get the list of secondary emotions.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function secondaries(Request $request, $primaryEmotionId)
{
$secondaries = SecondaryEmotion::where('emotion_primary_id', $primaryEmotionId)
->get();
return EmotionResource::collection($secondaries);
}
/**
* Get the list of emotions.
*
* @return \Illuminate\Http\Resources\Json\AnonymousResourceCollection
*/
public function emotions(Request $request, $primaryEmotionId, $secondaryEmotionId)
{
$emotions = Emotion::where('emotion_secondary_id', $secondaryEmotionId)
->get();
return EmotionResource::collection($emotions);
}
}

View File

@@ -0,0 +1,273 @@
<?php
namespace App\Http\Controllers;
use App\Helpers\DateHelper;
use App\Models\Journal\Day;
use Illuminate\Http\Request;
use App\Models\Journal\Entry;
use App\Helpers\JournalHelper;
use App\Models\Journal\JournalEntry;
use Illuminate\Support\Facades\Validator;
use App\Http\Requests\Journal\DaysRequest;
class JournalController extends Controller
{
/**
* Display a listing of the resource.
*
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
{
return view('journal.index');
}
/**
* Get all the journal entries.
*
* @return array
*/
public function list(Request $request)
{
$startDate = $request->input('start_date');
$endDate = $request->input('end_date');
$sortBy = $request->input('sort_by', 'created_at');
$sortOrder = $request->input('sort_order', 'desc');
$perPage = $request->input('per_page', 30);
$entries = collect([]);
$journalEntriesQuery = auth()->user()->account->journalEntries();
if ($startDate && $endDate) {
$journalEntriesQuery->whereDate('date', '>=', $startDate)
->whereDate('date', '<=', $endDate);
}
$journalEntries = $journalEntriesQuery->orderBy($sortBy, $sortOrder)
->paginate($perPage);
// this is needed to determine if we need to display the calendar
// (month + year) next to the journal entry
$previousEntryMonth = 0;
$previousEntryYear = 0;
$showCalendar = true;
foreach ($journalEntries->items() as $journalEntry) {
if ($previousEntryMonth == $journalEntry->date->month && $previousEntryYear == $journalEntry->date->year) {
$showCalendar = false;
}
$data = [
'id' => $journalEntry->id,
'date' => $journalEntry->date,
'journalable_id' => $journalEntry->journalable_id,
'journalable_type' => $journalEntry->journalable_type,
'object' => $journalEntry->getObjectData(),
'show_calendar' => $showCalendar,
];
$entries->push($data);
$previousEntryMonth = $journalEntry->date->month;
$previousEntryYear = $journalEntry->date->year;
$showCalendar = true;
}
// I need the pagination items when I send back the array.
// There is probably a simpler way to achieve this.
return [
'total' => $journalEntries->total(),
'per_page' => $journalEntries->perPage(),
'current_page' => $journalEntries->currentPage(),
'next_page_url' => $journalEntries->nextPageUrl(),
'prev_page_url' => $journalEntries->previousPageUrl(),
'data' => $entries,
];
}
/**
* Gets the details of a single Journal Entry.
*
* @param JournalEntry $journalEntry
* @return array
*/
public function get(JournalEntry $journalEntry)
{
return $journalEntry->getObjectData();
}
/**
* Store the day entry.
*/
public function storeDay(DaysRequest $request)
{
$day = auth()->user()->account->days()->create([
'date' => now(DateHelper::getTimezone()),
'rate' => $request->input('rate'),
'comment' => $request->input('comment'),
]);
// Log a journal entry
$journalEntry = JournalEntry::add($day);
return [
'id' => $journalEntry->id,
'date' => $journalEntry->date,
'journalable_id' => $journalEntry->journalable_id,
'journalable_type' => $journalEntry->journalable_type,
'object' => $journalEntry->getObjectData(),
'show_calendar' => true,
];
}
/**
* Delete the Day entry.
*
* @return void
*/
public function trashDay(Day $day): void
{
$day->deleteJournalEntry();
$day->delete();
}
/**
* Indicates whether the user has already rated the current day.
*
* @return string
*/
public function hasRated()
{
if (JournalHelper::hasAlreadyRatedToday(auth()->user())) {
return 'true';
}
return 'notYet';
}
/**
* Display the Create journal entry screen.
*
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function create()
{
return view('journal.add');
}
/**
* Saves the journal entry.
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function save(Request $request)
{
$validator = Validator::make($request->all(), [
'entry' => 'required|string',
'date' => 'required|date',
]);
if ($validator->fails()) {
return back()
->withInput()
->withErrors($validator);
}
$entry = new Entry;
$entry->account_id = $request->user()->account_id;
$entry->post = $request->input('entry');
if ($request->input('title') != '') {
$entry->title = $request->input('title');
}
$entry->save();
$entry->date = $request->input('date');
// Log a journal entry
JournalEntry::add($entry);
return redirect()->route('journal.index');
}
/**
* Display the Edit journal entry screen.
*
* @param Entry $entry
* @return \Illuminate\View\View
*/
public function edit(Entry $entry)
{
return view('journal.edit')
->withEntry($entry);
}
/**
* Method updateDay
*
* @param Request $request
* @param Day $day
*
*/
public function updateDay(Request $request, Day $day)
{
$validatedData = $request->validate([
'comment' => 'required|string',
]);
$day->update($validatedData);
return response()->json(['message' => 'Day updated successfully']);
}
/**
* Update a journal entry.
*
* @param Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function update(Request $request, Entry $entry)
{
$validator = Validator::make($request->all(), [
'entry' => 'required|string',
'date' => 'required|date',
]);
if ($validator->fails()) {
return back()
->withInput()
->withErrors($validator);
}
$entry->post = $request->input('entry');
if ($request->input('title') != '') {
$entry->title = $request->input('title');
}
$entry->save();
// Update journal entry
$journalEntry = $entry->journalEntry;
if ($journalEntry) {
$entry->date = $request->input('date');
$journalEntry->edit($entry);
}
return redirect()->route('journal.index');
}
/**
* Delete the reminder.
*/
public function deleteEntry(Request $request, Entry $entry)
{
$entry->deleteJournalEntry();
$entry->delete();
return ['true'];
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\Contact\Contact;
use App\Traits\JsonRespondController;
use App\Services\Contact\Contact\SetMeContact;
use App\Services\Contact\Contact\DeleteMeContact;
class MeController extends Controller
{
use JsonRespondController;
/**
* Set a contact as 'me'.
*
* @param Request $request
* @return string
*/
public function store(Request $request)
{
$this->validate($request, [
'contact_id' => 'required|integer|exists:contacts,id',
]);
app(SetMeContact::class)->execute([
'contact_id' => $request->input('contact_id'),
'account_id' => $request->user()->account_id,
'user_id' => $request->user()->id,
]);
return $this->respond(['true']);
}
/**
* Removes contact as 'me' association.
*
* @param Request $request
* @return string
*/
public function destroy(Request $request)
{
app(DeleteMeContact::class)->execute([
'account_id' => $request->user()->account_id,
'user_id' => $request->user()->id,
]);
return $this->respond(['true']);
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Helpers\AccountHelper;
use App\Helpers\AuditLogHelper;
use App\Http\Controllers\Controller;
class AuditLogController extends Controller
{
/**
* Display the page listing all the audit logs.
*/
public function index()
{
$logs = auth()->user()->account->auditLogs()
->with('author')
->orderBy('created_at', 'desc')
->paginate(15);
$accountHasLimitations = AccountHelper::hasLimitations(auth()->user()->account);
return view('settings.auditlog.index')
->withLogsCollection(AuditLogHelper::getCollectionOfAudits($logs))
->withAccountHasLimitations($accountHasLimitations)
->withLogsPagination($logs);
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Jobs\ExportAccount;
use Illuminate\Http\Request;
use App\Helpers\AccountHelper;
use App\Helpers\StorageHelper;
use App\Models\Account\ExportJob;
use App\Http\Controllers\Controller;
class ExportController extends Controller
{
/**
* Display the export view.
*
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function index()
{
$exports = ExportJob::where([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
])
->orderByDesc('created_at')
->get();
return view('settings.export')
->withAccountHasLimitations(AccountHelper::hasLimitations(auth()->user()->account))
->withExports($exports);
}
/**
* Exports the data of the account in SQL format.
*
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response|null
*/
public function storeSql()
{
$job = $this->newExport(ExportJob::SQL);
ExportAccount::dispatch($job);
return redirect()->route('settings.export.index')
->withStatus(trans('settings.export_submitted'));
}
/**
* Exports the data of the account in SQL format.
*
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response|null
*/
public function storeJson()
{
$job = $this->newExport(ExportJob::JSON);
ExportAccount::dispatch($job);
return redirect()->route('settings.export.index')
->withStatus(trans('settings.export_submitted'));
}
/**
* Create a new ExportJob.
*
* @param string $type
* @return ExportJob
*/
private function newExport(string $type): ExportJob
{
$exports = ExportJob::where([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
])
->orderBy('created_at')
->get();
if ($exports->count() >= config('monica.export_size')) {
$job = $exports->first();
try {
if ($job->filename !== null) {
StorageHelper::disk($job->location)
->delete($job->filename);
}
} finally {
$job->delete();
}
}
return ExportJob::create([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
'type' => $type,
]);
}
/**
* Download the generated file.
*
* @param Request $request
* @param string $uuid
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response|null
*/
public function download(Request $request, string $uuid)
{
$job = ExportJob::where([
'account_id' => auth()->user()->account_id,
'user_id' => auth()->user()->id,
'uuid' => $uuid,
])->firstOrFail();
if ($job->status !== ExportJob::EXPORT_DONE) {
return redirect()->route('settings.export.index')
->withErrors(trans('settings.export_not_done'));
}
$disk = StorageHelper::disk($job->location);
return $disk->response($job->filename,
"monica.{$job->type}",
[
'Content-Type' => "application/{$job->type}; charset=utf-8",
'Content-Disposition' => "attachment; filename=monica.{$job->type}",
]
);
}
}

View File

@@ -0,0 +1,178 @@
<?php
namespace App\Http\Controllers\Settings;
use Illuminate\Http\Request;
use App\Helpers\GenderHelper;
use App\Models\Contact\Gender;
use Illuminate\Validation\Rule;
use App\Helpers\CollectionHelper;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use Illuminate\Support\Facades\Validator;
use App\Http\Requests\Settings\GendersRequest;
use Illuminate\Database\Eloquent\ModelNotFoundException;
class GendersController extends Controller
{
use JsonRespondController;
/**
* Get all the gender types.
*/
public function index()
{
$gendersData = collect([]);
$genders = auth()->user()->account->genders;
foreach ($genders as $gender) {
$gendersData->push($this->formatData($gender));
}
return CollectionHelper::sortByCollator($gendersData, 'name');
}
/**
* Get all the gender sex types.
*/
public function types()
{
$gendersData = collect([]);
$types = [
Gender::MALE,
Gender::FEMALE,
Gender::OTHER,
Gender::UNKNOWN,
Gender::NONE,
];
foreach ($types as $type) {
$gendersData->push([
'id' => $type,
'name' => trans('settings.personalization_genders_'.strtolower($type)),
]);
}
return CollectionHelper::sortByCollator($gendersData, 'name');
}
/**
* Store the gender.
*/
public function store(Request $request)
{
Validator::make($request->all(), [
'name' => 'required|max:255',
'type' => ['required', Rule::in(Gender::LIST)],
])->validate();
$gender = auth()->user()->account->genders()->create(
$request->only([
'name',
'type',
])
+ [
'account_id' => auth()->user()->account_id,
]
);
if ($request->input('isDefault')) {
$this->updateDefault($gender);
}
return $this->formatData($gender);
}
/**
* Update the given gender.
*/
public function update(GendersRequest $request, Gender $gender)
{
$gender->update(
$request->only([
'name',
'type',
])
);
if ($request->input('isDefault')) {
$this->updateDefault($gender);
$gender->refresh();
} elseif ($gender->isDefault()) {
// Case of this gender was the default one previously
$account = auth()->user()->account;
$account->default_gender_id = null;
$account->save();
$gender->refresh();
}
return $this->formatData($gender);
}
/**
* Destroy a gender type.
*/
public function destroyAndReplaceGender(Gender $gender, $genderId)
{
$account = auth()->user()->account;
try {
$genderToReplaceWith = Gender::where('account_id', $account->id)
->findOrFail($genderId);
} catch (ModelNotFoundException $e) {
return response()->json([
'message' => trans('settings.personalization_genders_modal_error'),
], 403);
}
// We get the new gender to associate the contacts with.
GenderHelper::replace($account, $gender, $genderToReplaceWith);
if ($gender->isDefault()) {
$account->default_gender_id = $genderToReplaceWith->id;
$account->save();
}
$gender->delete();
return $this->respondObjectDeleted($gender->id);
}
/**
* Destroy a gender type.
*/
public function destroy(Gender $gender)
{
$gender->delete();
return $this->respondObjectDeleted($gender->id);
}
/**
* Update the given gender to the default gender.
*/
public function updateDefault(Gender $gender)
{
$account = auth()->user()->account;
$account->default_gender_id = $gender->id;
$account->save();
return $this->formatData($gender);
}
/**
* Format data for output.
*
* @param Gender $gender
* @return array
*/
private function formatData($gender)
{
return [
'id' => $gender->id,
'name' => $gender->name,
'type' => $gender->type,
'isDefault' => $gender->isDefault(),
'numberOfContacts' => $gender->contacts->count(),
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Models\User\Module;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
class ModulesController extends Controller
{
use JsonRespondController;
/**
* Get all the reminder rules.
*/
public function index()
{
$modules = auth()->user()->account->modules;
return $modules->map(function ($module) {
return $this->format($module);
});
}
public function toggle(Request $request, Module $module)
{
$module->active = ! $module->active;
$module->save();
return $this->respond([
'data' => $this->format($module),
]);
}
private function format(Module $module)
{
return [
'id' => $module->id,
'key' => $module->key,
'name' => trans($module->translation_key),
'active' => $module->active,
];
}
}

View File

@@ -0,0 +1,157 @@
<?php
namespace App\Http\Controllers\Settings;
use App\Models\User\User;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Traits\JsonRespondController;
use Illuminate\Foundation\Auth\RedirectsUsers;
use PragmaRX\Google2FALaravel\Facade as Google2FA;
use PragmaRX\Google2FALaravel\Support\Authenticator;
class MultiFAController extends Controller
{
use RedirectsUsers, JsonRespondController;
/**
* @var string
*/
protected $redirectTo = '/settings/security';
/**
* Session var name to store secret code.
*
* @var string
*/
private $SESSION_TFA_SECRET = '2FA_secret';
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function enableTwoFactor(Request $request)
{
//generate new secret
$secret = $this->generateSecret();
$user = $request->user();
//generate image for QR barcode
$imageDataUri = Google2FA::getQRCodeInline(
$request->getHttpHost(),
$user->email,
$secret,
200
);
$request->session()->put($this->SESSION_TFA_SECRET, $secret);
return response()->json(['image' => $imageDataUri, 'secret' => $secret]);
}
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function validateTwoFactor(Request $request)
{
//get user
$user = $request->user();
if (! is_null($user->google2fa_secret)) {
return response()->json(['error' => trans('settings.2fa_enable_error_already_set')]);
}
$this->validate($request, [
'one_time_password' => 'required',
]);
//retrieve secret
$secret = $request->session()->pull($this->SESSION_TFA_SECRET);
$authenticator = app(Authenticator::class)->boot($request);
if ($authenticator->verifyGoogle2FA($secret, $request['one_time_password'])) {
//encrypt and then save secret
$user->google2fa_secret = $secret;
$user->save();
$authenticator->login();
return response()->json(['success' => true]);
}
$authenticator->logout();
return response()->json(['success' => false]);
}
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory
*/
public function disableTwoFactor(Request $request)
{
return view('settings.security.2fa-disable');
}
/**
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function deactivateTwoFactor(Request $request)
{
$this->validate($request, [
'one_time_password' => 'required',
]);
$user = $request->user();
if ($this->validateTwoFactorLogin($request, $user, $request['one_time_password'])) {
//make secret column blank
$user->google2fa_secret = null;
$user->save();
return response()->json(['success' => true]);
}
return response()->json(['success' => false]);
}
/**
* Validate 2nd factor for user with 2FA code or recovery code.
*
* @param Request $request
* @param User $user
* @param string $oneTimePassword
* @return bool
*/
private function validateTwoFactorLogin(Request $request, User $user, string $oneTimePassword): bool
{
//retrieve secret
$secret = $user->google2fa_secret;
$authenticator = app(Authenticator::class)->boot($request);
// try provided token as a 2FA code, or as a recovery code
if ($authenticator->verifyGoogle2FA($secret, $oneTimePassword)
|| $user->recoveryChallenge($oneTimePassword)) {
$authenticator->logout();
return true;
}
return false;
}
/**
* Generate a secret key in Base32 format.
*
* @return string
*/
private function generateSecret()
{
return Google2FA::generateSecretKey(32);
}
}

Some files were not shown because too many files have changed in this diff Show More