Expand API for attachments.

This commit is contained in:
James Cole 2018-06-24 06:51:22 +02:00
parent 32b6ded008
commit ad6a9a7df7
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
28 changed files with 1290 additions and 59 deletions

View File

@ -30,6 +30,7 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Transformers\AccountTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
@ -57,12 +58,14 @@ class AccountController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var User $user */
$user = auth()->user();
// @var AccountRepositoryInterface repository
$this->repository = app(AccountRepositoryInterface::class);
$this->repository->setUser(auth()->user());
$this->repository->setUser($user);
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->currencyRepository->setUser(auth()->user());
$this->currencyRepository->setUser($user);
return $next($request);
}
@ -93,7 +96,7 @@ class AccountController extends Controller
public function index(Request $request): JsonResponse
{
// create some objects:
$manager = new Manager();
$manager = new Manager;
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
// read type from URI
@ -129,7 +132,7 @@ class AccountController extends Controller
*/
public function show(Request $request, Account $account): JsonResponse
{
$manager = new Manager();
$manager = new Manager;
// add include parameter:
$include = $request->get('include') ?? '';
@ -156,7 +159,7 @@ class AccountController extends Controller
$data['currency_id'] = null === $currency ? 0 : $currency->id;
}
$account = $this->repository->store($data);
$manager = new Manager();
$manager = new Manager;
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
@ -184,7 +187,7 @@ class AccountController extends Controller
// set correct type:
$data['type'] = config('firefly.shortNamesByFullName.' . $account->accountType->type);
$this->repository->update($account, $data);
$manager = new Manager();
$manager = new Manager;
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));

View File

@ -0,0 +1,227 @@
<?php
/**
* AttachmentController.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers;
use FireflyIII\Api\V1\Requests\AttachmentRequest;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
use FireflyIII\Models\Attachment;
use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface;
use FireflyIII\Transformers\AttachmentTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response as LaravelResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use League\Fractal\Manager;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
use League\Fractal\Serializer\JsonApiSerializer;
/**
* Class AttachmentController
*/
class AttachmentController extends Controller
{
/** @var AttachmentRepositoryInterface */
private $repository;
/**
* AccountController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
/** @var User $user */
$user = auth()->user();
$this->repository = app(AttachmentRepositoryInterface::class);
$this->repository->setUser($user);
return $next($request);
}
);
}
/**
* Remove the specified resource from storage.
*
* @param int $id
*
* @return \Illuminate\Http\Response
*/
public function destroy($id)
{
//
}
/**
* @param Attachment $attachment
*
* @return LaravelResponse
* @throws FireflyException
*/
public function download(Attachment $attachment): LaravelResponse
{
if ($attachment->uploaded === false) {
throw new FireflyException('No file has been uploaded for this attachment (yet).');
}
if ($this->repository->exists($attachment)) {
$content = $this->repository->getContent($attachment);
$quoted = sprintf('"%s"', addcslashes(basename($attachment->filename), '"\\'));
/** @var LaravelResponse $response */
$response = response($content, 200);
$response
->header('Content-Description', 'File Transfer')
->header('Content-Type', 'application/octet-stream')
->header('Content-Disposition', 'attachment; filename=' . $quoted)
->header('Content-Transfer-Encoding', 'binary')
->header('Connection', 'Keep-Alive')
->header('Expires', '0')
->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
->header('Pragma', 'public')
->header('Content-Length', \strlen($content));
return $response;
}
throw new FireflyException('Could not find the indicated attachment. The file is no longer there.');
}
/**
* Display a listing of the resource.
*
* @param Request $request
*
* @return JsonResponse
*/
public function index(Request $request): JsonResponse
{
// create some objects:
$manager = new Manager;
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
// types to get, page size:
$pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data;
// get list of accounts. Count it and split it.
$collection = $this->repository->get();
$count = $collection->count();
$attachments = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize);
// make paginator:
$paginator = new LengthAwarePaginator($attachments, $count, $pageSize, $this->parameters->get('page'));
$paginator->setPath(route('api.v1.attachments.index') . $this->buildParams());
// present to user.
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new FractalCollection($attachments, new AttachmentTransformer($this->parameters), 'attachments');
$resource->setPaginator(new IlluminatePaginatorAdapter($paginator));
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* Display the specified resource.
*
* @param Request $request
* @param Attachment $attachment
*
* @return JsonResponse
*/
public function show(Request $request, Attachment $attachment): JsonResponse
{
$manager = new Manager;
// add include parameter:
$include = $request->get('include') ?? '';
$manager->parseIncludes($include);
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* Store a newly created resource in storage.
*
* @param \Illuminate\Http\Request $request
*
* @return \Illuminate\Http\Response
* @throws FireflyException
*/
public function store(AttachmentRequest $request): JsonResponse
{
$data = $request->getAll();
$attachment = $this->repository->store($data);
$manager = new Manager;
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* Update the specified resource in storage.
*
* @param AttachmentRequest $request
* @param Attachment $attachment
*
* @return JsonResponse
*/
public function update(AttachmentRequest $request, Attachment $attachment): JsonResponse
{
$data = $request->getAll();
$this->repository->update($attachment, $data);
$manager = new Manager;
$baseUrl = $request->getSchemeAndHttpHost() . '/api/v1';
$manager->setSerializer(new JsonApiSerializer($baseUrl));
$resource = new Item($attachment, new AttachmentTransformer($this->parameters), 'attachments');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json');
}
/**
* @param Request $request
* @param Attachment $attachment
*
* @return LaravelResponse
*/
public function upload(Request $request, Attachment $attachment): LaravelResponse
{
/** @var AttachmentHelperInterface $helper */
$helper = app(AttachmentHelperInterface::class);
$body = $request->getContent();
$helper->saveAttachmentFromApi($attachment, $body);
return response('', 200);
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* AttachmentRequest.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Api\V1\Requests;
use FireflyIII\Models\Bill;
use FireflyIII\Models\ImportJob;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Rules\IsBase64;
use FireflyIII\Rules\IsValidAttachmentModel;
/**
* Class AttachmentRequest
*/
class AttachmentRequest extends Request
{
/**
* @return bool
*/
public function authorize(): bool
{
// Only allow authenticated users
return auth()->check();
}
/**
* @return array
*/
public function getAll(): array
{
return [
'filename' => $this->string('filename'),
'title' => $this->string('title'),
'notes' => $this->string('notes'),
'model' => $this->string('model'),
'model_id' => $this->integer('model_id'),
];
}
/**
* @return array
*/
public function rules(): array
{
$models = implode(
',', [
Bill::class,
ImportJob::class,
TransactionJournal::class,
]
);
$model = $this->string('model');
$rules = [
'filename' => 'required|between:1,255',
'title' => 'between:1,255',
'notes' => 'between:1,65000',
'model' => sprintf('required|in:%s', $models),
'model_id' => ['required', 'numeric', new IsValidAttachmentModel($model)],
];
switch ($this->method()) {
default:
break;
case 'PUT':
case 'PATCH':
unset($rules['model'], $rules['model_id']);
$rules['filename'] = 'between:1,255';
break;
}
return $rules;
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* AttachmentFactory.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Factory;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Note;
use FireflyIII\User;
/**
* Class AttachmentFactory
*/
class AttachmentFactory
{
/** @var User */
private $user;
/**
* @param array $data
*
* @return Attachment|null
*/
public function create(array $data): ?Attachment
{
// create attachment:
$attachment = Attachment::create(
[
'user_id' => $this->user->id,
'attachable_id' => $data['model_id'],
'attachable_type' => $data['model'],
'md5' => '',
'filename' => $data['filename'],
'title' => '' === $data['title'] ? null : $data['title'],
'description' => null,
'mime' => '',
'size' => 0,
'uploaded' => 0,
]
);
$notes = (string)($data['notes'] ?? '');
if ('' !== $notes) {
$note = new Note;
$note->noteable()->associate($attachment);
$note->text = $notes;
$note->save();
}
return $attachment;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
}

View File

@ -122,6 +122,46 @@ class AttachmentHelper implements AttachmentHelperInterface
return $this->messages;
}
/**
* Uploads a file as a string.
*
* @param Attachment $attachment
* @param string $content
*
* @return bool
*/
public function saveAttachmentFromApi(Attachment $attachment, string $content): bool
{
$resource = tmpfile();
if (false === $resource) {
Log::error('Cannot create temp-file for file upload.');
return false;
}
$path = stream_get_meta_data($resource)['uri'];
fwrite($resource, $content);
$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $path);
$allowedMime = config('firefly.allowedMimes');
if (!\in_array($mime, $allowedMime, true)) {
Log::error(sprintf('Mime type %s is not allowed for API file upload.', $mime));
return false;
}
// is allowed? Save the file!
$encrypted = Crypt::encrypt($content);
$this->uploadDisk->put($attachment->fileName(), $encrypted);
// update attachment.
$attachment->md5 = md5_file($path);
$attachment->mime = $mime;
$attachment->size = \strlen($content);
$attachment->uploaded = 1;
$attachment->save();
return true;
}
/**
* @param Model $model
* @param array|null $files
@ -232,7 +272,7 @@ class AttachmentHelper implements AttachmentHelperInterface
Log::debug(sprintf('Name is %s, and mime is %s', $name, $mime));
Log::debug('Valid mimes are', $this->allowedMimes);
if (!\in_array($mime, $this->allowedMimes)) {
if (!\in_array($mime, $this->allowedMimes, true)) {
$msg = (string)trans('validation.file_invalid_mime', ['name' => $name, 'mime' => $mime]);
$this->errors->add('attachments', $msg);
Log::error($msg);

View File

@ -61,6 +61,16 @@ interface AttachmentHelperInterface
*/
public function getMessages(): MessageBag;
/**
* Uploads a file as a string.
*
* @param Attachment $attachment
* @param string $content
*
* @return bool
*/
public function saveAttachmentFromApi(Attachment $attachment, string $content): bool;
/**
* @param Model $model
* @param null|array $files

View File

@ -44,6 +44,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @property AccountType $accountType
* @property bool $active
* @property string $virtual_balance
* @property User $user
*/
class Account extends Model
{

View File

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use Crypt;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
@ -32,6 +33,20 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class Attachment.
*
* @property int $id
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $attachable_type
* @property string $md5
* @property string $filename
* @property string $title
* @property string $description
* @property string $notes
* @property string $mime
* @property int $size
* @property User $user
* @property bool $uploaded
*/
class Attachment extends Model
{
@ -100,9 +115,9 @@ class Attachment extends Model
* @return null|string
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function getDescriptionAttribute($value)
public function getDescriptionAttribute($value): ?string
{
if (null === $value || 0 === \strlen($value)) {
if (null === $value || '' === $value) {
return null;
}
@ -116,9 +131,9 @@ class Attachment extends Model
* @return null|string
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function getFilenameAttribute($value)
public function getFilenameAttribute($value): ?string
{
if (null === $value || 0 === \strlen($value)) {
if (null === $value || '' === $value) {
return null;
}
@ -132,9 +147,9 @@ class Attachment extends Model
* @return null|string
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function getMimeAttribute($value)
public function getMimeAttribute($value): ?string
{
if (null === $value || 0 === \strlen($value)) {
if (null === $value || '' === $value) {
return null;
}
@ -148,9 +163,9 @@ class Attachment extends Model
* @return null|string
* @throws \Illuminate\Contracts\Encryption\DecryptException
*/
public function getTitleAttribute($value)
public function getTitleAttribute($value): ?string
{
if (null === $value || 0 === \strlen($value)) {
if (null === $value || '' === $value) {
return null;
}
@ -169,13 +184,14 @@ class Attachment extends Model
/**
* @codeCoverageIgnore
*
* @param string $value
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
* @param string|null $value
*/
public function setDescriptionAttribute(string $value)
public function setDescriptionAttribute(string $value = null): void
{
$this->attributes['description'] = Crypt::encrypt($value);
if (null !== $value) {
$this->attributes['description'] = Crypt::encrypt($value);
}
}
/**
@ -185,7 +201,7 @@ class Attachment extends Model
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function setFilenameAttribute(string $value)
public function setFilenameAttribute(string $value): void
{
$this->attributes['filename'] = Crypt::encrypt($value);
}
@ -197,7 +213,7 @@ class Attachment extends Model
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function setMimeAttribute(string $value)
public function setMimeAttribute(string $value): void
{
$this->attributes['mime'] = Crypt::encrypt($value);
}
@ -209,9 +225,11 @@ class Attachment extends Model
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function setTitleAttribute(string $value)
public function setTitleAttribute(string $value = null): void
{
$this->attributes['title'] = Crypt::encrypt($value);
if (null !== $value) {
$this->attributes['title'] = Crypt::encrypt($value);
}
}
/**

View File

@ -22,23 +22,34 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use Crypt;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class Bill.
*
* @property bool $active
* @property int $transaction_currency_id
* @property string $amount_min
* @property string $amount_max
* @property int $id
* @property string $name
* @property bool $active
* @property int $transaction_currency_id
* @property string $amount_min
* @property string $amount_max
* @property int $id
* @property string $name
* @property Collection $notes
* @property TransactionCurrency $transactionCurrency
* @property Carbon $created_at
* @property Carbon $updated_at
* @property Carbon $date
* @property string $repeat_freq
* @property int $skip
* @property bool $automatch
* @property User $user
*/
class Bill extends Model
{

View File

@ -22,12 +22,17 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
* Class Note.
*
* @property int $id
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $text
* @property string $title
*/
class Note extends Model
{

View File

@ -38,6 +38,7 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @property string $targetamount
* @property int $id
* @property string $name
* @property Account $account
*
*/
class PiggyBank extends Model

View File

@ -22,19 +22,30 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class Rule.
*
* @property bool $stop_processing
* @property int $id
* @property \Illuminate\Support\Collection $ruleTriggers
* @property bool $active
* @property bool $strict
* @property bool $stop_processing
* @property int $id
* @property Collection $ruleTriggers
* @property Collection $ruleActions
* @property bool $active
* @property bool $strict
* @property User $user
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $title
* @property string $text
* @property int $order
* @property RuleGroup $ruleGroup
*/
class Rule extends Model
{
@ -78,9 +89,9 @@ class Rule extends Model
/**
* @codeCoverageIgnore
* @return \Illuminate\Database\Eloquent\Relations\HasMany
* @return HasMany
*/
public function ruleActions()
public function ruleActions(): HasMany
{
return $this->hasMany(RuleAction::class);
}

View File

@ -22,10 +22,20 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
* Class RuleAction.
*
* @property string $action_value
* @property string $action_type
* @property int $id
* @property Carbon $created_at
* @property Carbon $updated_at
* @property int $order
* @property bool $active
* @property bool $stop_processing
*/
class RuleAction extends Model
{

View File

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
@ -30,7 +31,14 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Class RuleGroup.
*
* @property bool $active
* @property bool $active
* @property User $user
* @property Carbon $created_at
* @property Carbon $updated_at
* @property string $title
* @property string $text
* @property int $id
* @property int $order
*/
class RuleGroup extends Model
{

View File

@ -22,6 +22,7 @@ declare(strict_types=1);
namespace FireflyIII\Models;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
/**
@ -29,6 +30,12 @@ use Illuminate\Database\Eloquent\Model;
*
* @property string $trigger_value
* @property string $trigger_type
* @property int $id
* @property Carbon $created_at
* @property Carbon $updated_at
* @property int $order
* @property bool $active
* @property bool $stop_processing
*/
class RuleTrigger extends Model
{

View File

@ -25,6 +25,8 @@ namespace FireflyIII\Repositories\Attachment;
use Carbon\Carbon;
use Crypt;
use Exception;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Factory\AttachmentFactory;
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Note;
@ -179,11 +181,30 @@ class AttachmentRepository implements AttachmentRepositoryInterface
/**
* @param User $user
*/
public function setUser(User $user)
public function setUser(User $user): void
{
$this->user = $user;
}
/**
* @param array $data
*
* @return Attachment
* @throws FireflyException
*/
public function store(array $data): Attachment
{
/** @var AttachmentFactory $factory */
$factory = app(AttachmentFactory::class);
$factory->setUser($this->user);
$result = $factory->create($data);
if (null === $result) {
throw new FireflyException('Could not store attachment.');
}
return $result;
}
/**
* @param Attachment $attachment
* @param array $data
@ -193,8 +214,13 @@ class AttachmentRepository implements AttachmentRepositoryInterface
public function update(Attachment $attachment, array $data): Attachment
{
$attachment->title = $data['title'];
// update filename, if present and different:
if (isset($data['filename']) && '' !== $data['filename'] && $data['filename'] !== $attachment->filename) {
$attachment->filename = $data['filename'];
}
$attachment->save();
$this->updateNote($attachment, $data['notes']);
$this->updateNote($attachment, $data['notes'] ?? '');
return $attachment;
}
@ -207,7 +233,7 @@ class AttachmentRepository implements AttachmentRepositoryInterface
*/
public function updateNote(Attachment $attachment, string $note): bool
{
if (0 === \strlen($note)) {
if ('' === $note) {
$dbNote = $attachment->notes()->first();
if (null !== $dbNote) {
$dbNote->delete();

View File

@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Attachment;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Attachment;
use FireflyIII\User;
use Illuminate\Support\Collection;
@ -95,6 +96,14 @@ interface AttachmentRepositoryInterface
*/
public function setUser(User $user);
/**
* @param array $data
*
* @return Attachment
* @throws FireflyException
*/
public function store(array $data): Attachment;
/**
* @param Attachment $attachment
* @param array $attachmentData

View File

@ -0,0 +1,88 @@
<?php
/**
* IsValidAttachmentModel.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Rules;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use Illuminate\Contracts\Validation\Rule;
/**
* Class IsValidAttachmentModel
*/
class IsValidAttachmentModel implements Rule
{
/** @var string */
private $model;
/**
* IsValidAttachmentModel constructor.
*
* @param string $model
*/
public function __construct(string $model)
{
$this->model = $model;
}
/**
* Get the validation error message.
*
* @return string
*/
public function message(): string
{
return trans('validation.model_id_invalid');
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
*
* @return bool
* @throws FireflyException
*/
public function passes($attribute, $value): bool
{
if (!auth()->check()) {
return false;
}
$user = auth()->user();
switch ($this->model) {
default:
throw new FireflyException(sprintf('Model "%s" cannot be validated.', $this->model));
case TransactionJournal::class:
/** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class);
$repository->setUser($user);
$result = $repository->findNull((int)$value);
return null !== $result;
break;
}
}
}

View File

@ -153,7 +153,7 @@ class AccountTransformer extends TransformerAbstract
}
$currencyId = (int)$this->repository->getMetaValue($account, 'currency_id');
$currencyCode = null;
$currencySymbol = 'x';
$currencySymbol = null;
$decimalPlaces = 2;
if ($currencyId > 0) {
$currency = TransactionCurrency::find($currencyId);

View File

@ -28,6 +28,7 @@ use FireflyIII\Models\Attachment;
use League\Fractal\Resource\Item;
use League\Fractal\TransformerAbstract;
use Symfony\Component\HttpFoundation\ParameterBag;
use League\Fractal\Resource\Collection as FractalCollection;
/**
* Class AttachmentTransformer
@ -39,13 +40,13 @@ class AttachmentTransformer extends TransformerAbstract
*
* @var array
*/
protected $availableIncludes = ['user'];
protected $availableIncludes = ['user','notes'];
/**
* List of resources to automatically include
*
* @var array
*/
protected $defaultIncludes = ['user'];
protected $defaultIncludes = ['user','notes'];
/** @var ParameterBag */
protected $parameters;
@ -76,6 +77,20 @@ class AttachmentTransformer extends TransformerAbstract
return $this->item($attachment->user, new UserTransformer($this->parameters), 'user');
}
/**
* Attach the notes.
*
* @codeCoverageIgnore
*
* @param Attachment $attachment
*
* @return FractalCollection
*/
public function includeNotes(Attachment $attachment): FractalCollection
{
return $this->collection($attachment->notes, new NoteTransformer($this->parameters), 'notes');
}
/**
* Transform attachment.
*
@ -92,11 +107,11 @@ class AttachmentTransformer extends TransformerAbstract
'attachable_type' => $attachment->attachable_type,
'md5' => $attachment->md5,
'filename' => $attachment->filename,
'download_uri' => route('api.v1.attachments.download', [$attachment->id]),
'upload_uri' => route('api.v1.attachments.upload', [$attachment->id]),
'title' => $attachment->title,
'description' => $attachment->description,
'notes' => $attachment->notes,
'mime' => $attachment->mime,
'size' => $attachment->size,
'size' => (int)$attachment->size,
'links' => [
[
'rel' => 'self',

View File

@ -26,7 +26,6 @@ namespace FireflyIII\Transformers;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Note;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Support\Collection;
use League\Fractal\Resource\Collection as FractalCollection;
@ -45,13 +44,13 @@ class BillTransformer extends TransformerAbstract
*
* @var array
*/
protected $availableIncludes = ['attachments', 'transactions', 'user'];
protected $availableIncludes = ['attachments', 'transactions', 'user', 'notes', 'rules'];
/**
* List of resources to automatically include
*
* @var array
*/
protected $defaultIncludes = [];
protected $defaultIncludes = ['notes', 'rules'];
/** @var ParameterBag */
protected $parameters;
@ -83,6 +82,40 @@ class BillTransformer extends TransformerAbstract
return $this->collection($attachments, new AttachmentTransformer($this->parameters), 'attachments');
}
/**
* Attach the notes.
*
* @codeCoverageIgnore
*
* @param Bill $bill
*
* @return FractalCollection
*/
public function includeNotes(Bill $bill): FractalCollection
{
return $this->collection($bill->notes, new NoteTransformer($this->parameters), 'notes');
}
/**
* Attach the rules.
*
* @codeCoverageIgnore
*
* @param Bill $bill
*
* @return FractalCollection
*/
public function includeRules(Bill $bill): FractalCollection
{
/** @var BillRepositoryInterface $repository */
$repository = app(BillRepositoryInterface::class);
$repository->setUser($bill->user);
// add info about rules:
$rules = $repository->getRulesForBill($bill);
return $this->collection($rules, new RuleTransformer($this->parameters), 'rules');
}
/**
* Include any transactions.
*
@ -141,19 +174,17 @@ class BillTransformer extends TransformerAbstract
'name' => $bill->name,
'currency_id' => $bill->transaction_currency_id,
'currency_code' => $bill->transactionCurrency->code,
'match' => explode(',', $bill->match),
'amount_min' => round($bill->amount_min, 2),
'amount_max' => round($bill->amount_max, 2),
'date' => $bill->date->format('Y-m-d'),
'repeat_freq' => $bill->repeat_freq,
'skip' => (int)$bill->skip,
'automatch' => (int)$bill->automatch === 1,
'active' => (int)$bill->active === 1,
'automatch' => $bill->automatch,
'active' => $bill->active,
'attachments_count' => $bill->attachments()->count(),
'pay_dates' => $payDates,
'paid_dates' => $paidData['paid_dates'],
'next_expected_match' => $paidData['next_expected_match'],
'notes' => null,
'links' => [
[
'rel' => 'self',
@ -161,11 +192,6 @@ class BillTransformer extends TransformerAbstract
],
],
];
/** @var Note $note */
$note = $bill->notes()->first();
if (null !== $note) {
$data['notes'] = $note->text;
}
return $data;

View File

@ -0,0 +1,92 @@
<?php
/**
* NoteTransformer.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Transformers;
use FireflyIII\Models\Note;
use League\CommonMark\CommonMarkConverter;
use League\Fractal\TransformerAbstract;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
* Class NoteTransformer
*/
class NoteTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* @var array
*/
protected $availableIncludes = [];
/**
* List of resources to automatically include
*
* @var array
*/
protected $defaultIncludes = [];
/** @var ParameterBag */
protected $parameters;
/**
* CurrencyTransformer constructor.
*
* @codeCoverageIgnore
*
* @param ParameterBag $parameters
*/
public function __construct(ParameterBag $parameters)
{
$this->parameters = $parameters;
}
/**
* Transform the note.
*
* @param Note $note
*
* @return array
*/
public function transform(Note $note): array
{
$converter = new CommonMarkConverter;
$data = [
'id' => (int)$note->id,
'updated_at' => $note->updated_at->toAtomString(),
'created_at' => $note->created_at->toAtomString(),
'title' => $note->title,
'text' => $note->text,
'markdown' => $converter->convertToHtml($note->text),
'links' => [
[
'rel' => 'self',
'uri' => '/notes/' . $note->id,
],
],
];
return $data;
}
}

View File

@ -0,0 +1,93 @@
<?php
/**
* RuleActionTransformer.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Transformers;
use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleTrigger;
use League\Fractal\TransformerAbstract;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
* Class RuleActionTransformer
*/
class RuleActionTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* @var array
*/
protected $availableIncludes = [];
/**
* List of resources to automatically include
*
* @var array
*/
protected $defaultIncludes = [];
/** @var ParameterBag */
protected $parameters;
/**
* CurrencyTransformer constructor.
*
* @codeCoverageIgnore
*
* @param ParameterBag $parameters
*/
public function __construct(ParameterBag $parameters)
{
$this->parameters = $parameters;
}
/**
* Transform the rule action.
*
* @param RuleAction $ruleAction
*
* @return array
*/
public function transform(RuleAction $ruleAction): array
{
$data = [
'id' => (int)$ruleAction->id,
'updated_at' => $ruleAction->updated_at->toAtomString(),
'created_at' => $ruleAction->created_at->toAtomString(),
'action_type' => $ruleAction->action_type,
'action_value' => $ruleAction->action_value,
'order' => $ruleAction->order,
'active' => $ruleAction->active,
'stop_processing' => $ruleAction->stop_processing,
'links' => [
[
'rel' => 'self',
'uri' => '/rule_triggers/' . $ruleAction->id,
],
],
];
return $data;
}
}

View File

@ -0,0 +1,110 @@
<?php
/**
* RuleGroupTransformer.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Transformers;
use FireflyIII\Models\RuleGroup;
use League\Fractal\Resource\Item;
use League\Fractal\TransformerAbstract;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
* Class RuleGroupTransformer
*/
class RuleGroupTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* @var array
*/
protected $availableIncludes = ['user'];
/**
* List of resources to automatically include
*
* @var array
*/
protected $defaultIncludes = ['user'];
/** @var ParameterBag */
protected $parameters;
/**
* CurrencyTransformer constructor.
*
* @codeCoverageIgnore
*
* @param ParameterBag $parameters
*/
public function __construct(ParameterBag $parameters)
{
$this->parameters = $parameters;
}
/**
* Include the user.
*
* @param RuleGroup $ruleGroup
*
* @codeCoverageIgnore
* @return Item
*/
public function includeUser(RuleGroup $ruleGroup): Item
{
return $this->item($ruleGroup->user, new UserTransformer($this->parameters), 'users');
}
/**
* Transform the rule group
*
* @param RuleGroup $ruleGroup
*
* @return array
*/
public function transform(RuleGroup $ruleGroup): array
{
$data = [
'id' => (int)$ruleGroup->id,
'updated_at' => $ruleGroup->updated_at->toAtomString(),
'created_at' => $ruleGroup->created_at->toAtomString(),
'title' => $ruleGroup->title,
'text' => $ruleGroup->text,
'order' => $ruleGroup->order,
'active' => $ruleGroup->active,
'links' => [
[
'rel' => 'self',
'uri' => '/rule_groups/' . $ruleGroup->id,
],
],
];
return $data;
}
}

View File

@ -0,0 +1,140 @@
<?php
/**
* RuleTransformer.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Transformers;
use FireflyIII\Models\Rule;
use League\Fractal\Resource\Item;
use League\Fractal\TransformerAbstract;
use Symfony\Component\HttpFoundation\ParameterBag;
use League\Fractal\Resource\Collection as FractalCollection;
/**
* Class RuleTransformer
*/
class RuleTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* @var array
*/
protected $availableIncludes = ['rule_group', 'rule_triggers', 'rule_actions', 'user'];
/**
* List of resources to automatically include
*
* @var array
*/
protected $defaultIncludes = ['rule_group', 'rule_triggers', 'rule_actions'];
/** @var ParameterBag */
protected $parameters;
/**
* CurrencyTransformer constructor.
*
* @codeCoverageIgnore
*
* @param ParameterBag $parameters
*/
public function __construct(ParameterBag $parameters)
{
$this->parameters = $parameters;
}
/**
* @param Rule $rule
*
* @return FractalCollection
*/
public function includeRuleTriggers(Rule $rule): FractalCollection {
return $this->collection($rule->ruleTriggers, new RuleTriggerTransformer($this->parameters), 'rule_triggers');
}
/**
* @param Rule $rule
*
* @return FractalCollection
*/
public function includeRuleActions(Rule $rule): FractalCollection {
return $this->collection($rule->ruleActions, new RuleActionTransformer($this->parameters), 'rule_actions');
}
/**
* Include the user.
*
* @param Rule $rule
*
* @codeCoverageIgnore
* @return Item
*/
public function includeUser(Rule $rule): Item
{
return $this->item($rule->user, new UserTransformer($this->parameters), 'users');
}
/**
* Include the rule group.
*
* @param Rule $rule
*
* @codeCoverageIgnore
* @return Item
*/
public function includeRuleGroup(Rule $rule): Item
{
return $this->item($rule->ruleGroup, new RuleGroupTransformer($this->parameters), 'rule_groups');
}
/**
* Transform the rule.
*
* @param Rule $rule
*
* @return array
*/
public function transform(Rule $rule): array
{
$data = [
'id' => (int)$rule->id,
'updated_at' => $rule->updated_at->toAtomString(),
'created_at' => $rule->created_at->toAtomString(),
'title' => $rule->title,
'text' => $rule->text,
'order' => (int)$rule->order,
'active' => $rule->active,
'stop_processing' => $rule->stop_processing,
'strict' => $rule->strict,
'links' => [
[
'rel' => 'self',
'uri' => '/rules/' . $rule->id,
],
],
];
return $data;
}
}

View File

@ -0,0 +1,92 @@
<?php
/**
* RuleTriggerTransformer.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Transformers;
use FireflyIII\Models\RuleTrigger;
use League\Fractal\TransformerAbstract;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
* Class RuleTriggerTransformer
*/
class RuleTriggerTransformer extends TransformerAbstract
{
/**
* List of resources possible to include
*
* @var array
*/
protected $availableIncludes = [];
/**
* List of resources to automatically include
*
* @var array
*/
protected $defaultIncludes = [];
/** @var ParameterBag */
protected $parameters;
/**
* CurrencyTransformer constructor.
*
* @codeCoverageIgnore
*
* @param ParameterBag $parameters
*/
public function __construct(ParameterBag $parameters)
{
$this->parameters = $parameters;
}
/**
* Transform the rule trigger.
*
* @param RuleTrigger $ruleTrigger
*
* @return array
*/
public function transform(RuleTrigger $ruleTrigger): array
{
$data = [
'id' => (int)$ruleTrigger->id,
'updated_at' => $ruleTrigger->updated_at->toAtomString(),
'created_at' => $ruleTrigger->created_at->toAtomString(),
'trigger_type' => $ruleTrigger->trigger_type,
'trigger_value' => $ruleTrigger->trigger_value,
'order' => $ruleTrigger->order,
'active' => $ruleTrigger->active,
'stop_processing' => $ruleTrigger->stop_processing,
'links' => [
[
'rel' => 'self',
'uri' => '/rule_triggers/' . $ruleTrigger->id,
],
],
];
return $data;
}
}

View File

@ -44,6 +44,8 @@ return [
'belongs_to_user' => 'The value of :attribute is unknown',
'accepted' => 'The :attribute must be accepted.',
'bic' => 'This is not a valid BIC.',
'base64' => 'This is not valid base64 encoded data.',
'model_id_invalid' => 'The given ID seems invalid for this model.',
'more' => ':attribute must be larger than zero.',
'active_url' => 'The :attribute is not a valid URL.',
'after' => 'The :attribute must be a date after :date.',

View File

@ -43,6 +43,21 @@ Route::group(
}
);
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'attachments', 'as' => 'api.v1.attachments.'],
function () {
// Accounts API routes:
Route::get('', ['uses' => 'AttachmentController@index', 'as' => 'index']);
Route::post('', ['uses' => 'AttachmentController@store', 'as' => 'store']);
Route::get('{attachment}', ['uses' => 'AttachmentController@show', 'as' => 'show']);
Route::get('{attachment}/download', ['uses' => 'AttachmentController@download', 'as' => 'download']);
Route::post('{attachment}/upload', ['uses' => 'AttachmentController@upload', 'as' => 'upload']);
Route::put('{attachment}', ['uses' => 'AttachmentController@update', 'as' => 'update']);
Route::delete('{attachment}', ['uses' => 'AttachmentController@delete', 'as' => 'delete']);
}
);
Route::group(
['middleware' => ['auth:api', 'bindings'], 'namespace' => 'FireflyIII\Api\V1\Controllers', 'prefix' => 'bills', 'as' => 'api.v1.bills.'], function () {