mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Implement Design API (#3400)
* Working on CompanyUser route * CompanyUser update route * tests for updating a company user * Fixes for exchange currency rate * Move slack and google analytics fields into company table * implement Design API
This commit is contained in:
parent
3ae5c0467e
commit
f8ea4c0d0f
@ -195,9 +195,6 @@ class CompanySettings extends BaseSettings {
|
||||
public $client_online_payment_notification = true; //@todo implement in notifications
|
||||
public $client_manual_payment_notification = true; //@todo implement in notifications
|
||||
|
||||
public $system_notifications_slack = '';
|
||||
public $system_notifications_email = '';
|
||||
|
||||
/* Company Meta data that we can use to build sub companies*/
|
||||
|
||||
public $name = '';
|
||||
@ -225,8 +222,6 @@ class CompanySettings extends BaseSettings {
|
||||
public $pdf_variables = [];
|
||||
|
||||
public static $casts = [
|
||||
'system_notifications_slack' => 'string',
|
||||
'system_notifications_email' => 'string',
|
||||
'portal_design_id' => 'string',
|
||||
'late_fee_endless_percent' => 'float',
|
||||
'late_fee_endless_amount' => 'float',
|
||||
|
31
app/Factory/DesignFactory.php
Normal file
31
app/Factory/DesignFactory.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Factory;
|
||||
|
||||
use App\Models\Design;
|
||||
|
||||
class DesignFactory
|
||||
{
|
||||
public static function create(int $company_id, int $user_id) :Design
|
||||
{
|
||||
$design = new Design();
|
||||
$design->user_id = $user_id;
|
||||
$design->company_id = $company_id;
|
||||
$design->is_deleted = false;
|
||||
$design->is_active = true;
|
||||
$design->is_custom = true;
|
||||
$design->name = '';
|
||||
$design->design = '';
|
||||
|
||||
return $design;
|
||||
}
|
||||
}
|
144
app/Filters/DesignFilters.php
Normal file
144
app/Filters/DesignFilters.php
Normal file
@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Filters;
|
||||
|
||||
use App\Models\Design;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
|
||||
/**
|
||||
* DesignFilters
|
||||
*/
|
||||
class DesignFilters extends QueryFilters
|
||||
{
|
||||
|
||||
/**
|
||||
* Filter based on search text
|
||||
*
|
||||
* @param string query filter
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
* @deprecated
|
||||
*
|
||||
*/
|
||||
public function filter(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
return $this->builder->where(function ($query) use ($filter) {
|
||||
$query->where('designs.name', 'like', '%'.$filter.'%');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the list based on the status
|
||||
* archived, active, deleted
|
||||
*
|
||||
* @param string filter
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function status(string $filter = '') : Builder
|
||||
{
|
||||
if (strlen($filter) == 0) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
$table = 'designs';
|
||||
$filters = explode(',', $filter);
|
||||
|
||||
return $this->builder->where(function ($query) use ($filters, $table) {
|
||||
$query->whereNull($table . '.id');
|
||||
|
||||
if (in_array(parent::STATUS_ACTIVE, $filters)) {
|
||||
$query->orWhereNull($table . '.deleted_at');
|
||||
}
|
||||
|
||||
if (in_array(parent::STATUS_ARCHIVED, $filters)) {
|
||||
$query->orWhere(function ($query) use ($table) {
|
||||
$query->whereNotNull($table . '.deleted_at');
|
||||
|
||||
if (! in_array($table, ['users'])) {
|
||||
$query->where($table . '.is_deleted', '=', 0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (in_array(parent::STATUS_DELETED, $filters)) {
|
||||
$query->orWhere($table . '.is_deleted', '=', 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the list based on $sort
|
||||
*
|
||||
* @param string sort formatted as column|asc
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function sort(string $sort) : Builder
|
||||
{
|
||||
$sort_col = explode("|", $sort);
|
||||
return $this->builder->orderBy($sort_col[0], $sort_col[1]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base query
|
||||
*
|
||||
* @param int company_id
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
* @deprecated
|
||||
*/
|
||||
public function baseQuery(int $company_id, User $user) : Builder
|
||||
{
|
||||
$query = DB::table('designs')
|
||||
->join('companies', 'companies.id', '=', 'designs.company_id')
|
||||
->where('designs.company_id', '=', $company_id)
|
||||
//->whereRaw('(designs.name != "" or contacts.first_name != "" or contacts.last_name != "" or contacts.email != "")') // filter out buy now invoices
|
||||
->select(
|
||||
'designs.id',
|
||||
'designs.name',
|
||||
'designs.design',
|
||||
'designs.created_at',
|
||||
'designs.created_at as design_created_at',
|
||||
'designs.deleted_at',
|
||||
'designs.is_deleted',
|
||||
'designs.user_id',
|
||||
);
|
||||
|
||||
/**
|
||||
* If the user does not have permissions to view all invoices
|
||||
* limit the user to only the invoices they have created
|
||||
*/
|
||||
if (Gate::denies('view-list', Design::class)) {
|
||||
$query->where('designs.user_id', '=', $user->id);
|
||||
}
|
||||
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query by the users company ID
|
||||
*
|
||||
* @param $company_id The company Id
|
||||
* @return Illuminate\Database\Query\Builder
|
||||
*/
|
||||
public function entityFilter()
|
||||
{
|
||||
|
||||
//return $this->builder->whereCompanyId(auth()->user()->company()->id);
|
||||
return $this->builder->company();
|
||||
}
|
||||
}
|
490
app/Http/Controllers/DesignController.php
Normal file
490
app/Http/Controllers/DesignController.php
Normal file
@ -0,0 +1,490 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Factory\DesignFactory;
|
||||
use App\Filters\DesignFilters;
|
||||
use App\Http\Requests\Design\CreateDesignRequest;
|
||||
use App\Http\Requests\Design\DestroyDesignRequest;
|
||||
use App\Http\Requests\Design\EditDesignRequest;
|
||||
use App\Http\Requests\Design\ShowDesignRequest;
|
||||
use App\Http\Requests\Design\StoreDesignRequest;
|
||||
use App\Http\Requests\Design\UpdateDesignRequest;
|
||||
use App\Jobs\Entity\ActionEntity;
|
||||
use App\Models\Design;
|
||||
use App\Transformers\DesignTransformer;
|
||||
use App\Utils\Traits\BulkOptions;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
|
||||
/**
|
||||
* Class DesignController
|
||||
* @package App\Http\Controllers
|
||||
* @covers App\Http\Controllers\DesignController
|
||||
*/
|
||||
class DesignController extends BaseController
|
||||
{
|
||||
use MakesHash;
|
||||
|
||||
protected $entity_type = Design::class;
|
||||
|
||||
protected $entity_transformer = DesignTransformer::class;
|
||||
|
||||
|
||||
/**
|
||||
* DesignController constructor.
|
||||
* @param DesignRepository $designRepo
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* @OA\Get(
|
||||
* path="/api/v1/designs",
|
||||
* operationId="getDesigns",
|
||||
* tags={"designs"},
|
||||
* summary="Gets a list of designs",
|
||||
* description="Lists designs",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(ref="#/components/parameters/index"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A list of designs",
|
||||
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Design"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
*/
|
||||
public function index(DesignFilters $filters)
|
||||
{
|
||||
$designs = Design::filter($filters);
|
||||
|
||||
return $this->listResponse($designs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/designs/{id}",
|
||||
* operationId="showDesign",
|
||||
* tags={"designs"},
|
||||
* summary="Shows a design",
|
||||
* description="Displays a design by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Design Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the expense object",
|
||||
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Design"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
*/
|
||||
public function show(ShowDesignRequest $request, Design $design)
|
||||
{
|
||||
return $this->itemResponse($design);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for editing the specified resource.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/designs/{id}/edit",
|
||||
* operationId="editDesign",
|
||||
* tags={"designs"},
|
||||
* summary="Shows a design for editting",
|
||||
* description="Displays a design by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Design Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the design object",
|
||||
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Design"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
*/
|
||||
public function edit(EditDesignRequest $request, Design $design)
|
||||
{
|
||||
return $this->itemResponse($design);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the specified resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @param App\Models\Design $design
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Put(
|
||||
* path="/api/v1/designs/{id}",
|
||||
* operationId="updateDesign",
|
||||
* tags={"designs"},
|
||||
* summary="Updates a design",
|
||||
* description="Handles the updating of a design by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Design Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the design object",
|
||||
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Design"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
*/
|
||||
public function update(UpdateDesignRequest $request, Design $design)
|
||||
{
|
||||
if($request->entityIsDeleted($design))
|
||||
return $request->disallowUpdate();
|
||||
|
||||
$design->fill($request->all());
|
||||
$design->save();
|
||||
|
||||
return $this->itemResponse($design->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the form for creating a new resource.
|
||||
*
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Get(
|
||||
* path="/api/v1/designs/create",
|
||||
* operationId="getDesignsCreate",
|
||||
* tags={"designs"},
|
||||
* summary="Gets a new blank design object",
|
||||
* description="Returns a blank object with default values",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="A blank design object",
|
||||
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Design"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
*/
|
||||
public function create(CreateDesignRequest $request)
|
||||
{
|
||||
$design = DesignFactory::create(auth()->user()->company()->id, auth()->user()->id);
|
||||
|
||||
return $this->itemResponse($design);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store a newly created resource in storage.
|
||||
*
|
||||
* @param \Illuminate\Http\Request $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/designs",
|
||||
* operationId="storeDesign",
|
||||
* tags={"designs"},
|
||||
* summary="Adds a design",
|
||||
* description="Adds an design to a company",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns the saved design object",
|
||||
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Design"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
*/
|
||||
public function store(StoreDesignRequest $request)
|
||||
{
|
||||
$design = DesignFactory::create(auth()->user()->company()->id, auth()->user()->id);
|
||||
$design->fill($request->all());
|
||||
$design->save();
|
||||
|
||||
return $this->itemResponse($design->fresh());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the specified resource from storage.
|
||||
*
|
||||
* @param int $id
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
*
|
||||
* @OA\Delete(
|
||||
* path="/api/v1/designs/{id}",
|
||||
* operationId="deleteDesign",
|
||||
* tags={"designs"},
|
||||
* summary="Deletes a design",
|
||||
* description="Handles the deletion of a design by id",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/include"),
|
||||
* @OA\Parameter(
|
||||
* name="id",
|
||||
* in="path",
|
||||
* description="The Design Hashed ID",
|
||||
* example="D2J234DFA",
|
||||
* required=true,
|
||||
* @OA\Schema(
|
||||
* type="string",
|
||||
* format="string",
|
||||
* ),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="Returns a HTTP status",
|
||||
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
*
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*
|
||||
*/
|
||||
public function destroy(DestroyDesignRequest $request, Design $design)
|
||||
{
|
||||
//may not need these destroy routes as we are using actions to 'archive/delete'
|
||||
$design->is_deleted = true;
|
||||
$design->delete();
|
||||
$design->save();
|
||||
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform bulk actions on the list view
|
||||
*
|
||||
* @param BulkDesignRequest $request
|
||||
* @return \Illuminate\Http\Response
|
||||
*
|
||||
*
|
||||
* @OA\Post(
|
||||
* path="/api/v1/designs/bulk",
|
||||
* operationId="bulkDesigns",
|
||||
* tags={"designs"},
|
||||
* summary="Performs bulk actions on an array of designs",
|
||||
* description="",
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Secret"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Api-Token"),
|
||||
* @OA\Parameter(ref="#/components/parameters/X-Requested-With"),
|
||||
* @OA\Parameter(ref="#/components/parameters/index"),
|
||||
* @OA\RequestBody(
|
||||
* description="User credentials",
|
||||
* required=true,
|
||||
* @OA\MediaType(
|
||||
* mediaType="application/json",
|
||||
* @OA\Schema(
|
||||
* type="array",
|
||||
* @OA\Items(
|
||||
* type="integer",
|
||||
* description="Array of hashed IDs to be bulk 'actioned",
|
||||
* example="[0,1,2,3]",
|
||||
* ),
|
||||
* )
|
||||
* )
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=200,
|
||||
* description="The Design User response",
|
||||
* @OA\Header(header="X-API-Version", ref="#/components/headers/X-API-Version"),
|
||||
* @OA\Header(header="X-RateLimit-Remaining", ref="#/components/headers/X-RateLimit-Remaining"),
|
||||
* @OA\Header(header="X-RateLimit-Limit", ref="#/components/headers/X-RateLimit-Limit"),
|
||||
* @OA\JsonContent(ref="#/components/schemas/Design"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response=422,
|
||||
* description="Validation error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/ValidationError"),
|
||||
* ),
|
||||
* @OA\Response(
|
||||
* response="default",
|
||||
* description="Unexpected Error",
|
||||
* @OA\JsonContent(ref="#/components/schemas/Error"),
|
||||
* ),
|
||||
* )
|
||||
*/
|
||||
public function bulk()
|
||||
{
|
||||
$action = request()->input('action');
|
||||
|
||||
$ids = request()->input('ids');
|
||||
$designs = Design::withTrashed()->find($this->transformKeys($ids));
|
||||
|
||||
$designs->each(function ($design, $key) use ($action) {
|
||||
if (auth()->user()->can('edit', $design)) {
|
||||
//$this->design_repo->{$action}($design);@todo
|
||||
}
|
||||
});
|
||||
|
||||
return $this->listResponse(Design::withTrashed()->whereIn('id', $this->transformKeys($ids)));
|
||||
}
|
||||
|
||||
|
||||
}
|
16
app/Http/Controllers/OpenAPI/DesignSchema.php
Normal file
16
app/Http/Controllers/OpenAPI/DesignSchema.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* @OA\Schema(
|
||||
* schema="Design",
|
||||
* type="object",
|
||||
* @OA\Property(property="id", type="string", example="AS3df3A", description="The design hashed id"),
|
||||
* @OA\Property(property="name", type="string", example="Beauty", description="The design name"),
|
||||
* @OA\Property(property="design", type="string", example="<html></html>", description="The design HTML"),
|
||||
* @OA\Property(property="is_custom", type="boolean", example=true, description="Flag to determine if the design is a custom user design"),
|
||||
* @OA\Property(property="is_active", type="boolean", example=true, description="Flag to determine if the design is available for use"),
|
||||
* @OA\Property(property="is_deleted", type="boolean", example=true, description="Flag to determine if the design is deleted"),
|
||||
* @OA\Property(property="created_at", type="number", format="integer", example="134341234234", description="Timestamp"),
|
||||
* @OA\Property(property="updated_at", type="number", format="integer", example="134341234234", description="Timestamp"),
|
||||
* @OA\Property(property="deleted_at", type="number", format="integer", example="134341234234", description="Timestamp"),
|
||||
* )
|
||||
*/
|
29
app/Http/Requests/Design/CreateDesignRequest.php
Normal file
29
app/Http/Requests/Design/CreateDesignRequest.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Design;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\Design;
|
||||
|
||||
class CreateDesignRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
}
|
28
app/Http/Requests/Design/DestroyDesignRequest.php
Normal file
28
app/Http/Requests/Design/DestroyDesignRequest.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Design;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class DestroyDesignRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
}
|
39
app/Http/Requests/Design/EditDesignRequest.php
Normal file
39
app/Http/Requests/Design/EditDesignRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Design;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class EditDesignRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
39
app/Http/Requests/Design/ShowDesignRequest.php
Normal file
39
app/Http/Requests/Design/ShowDesignRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Design;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
|
||||
class ShowDesignRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validation rules that apply to the request.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//
|
||||
];
|
||||
}
|
||||
}
|
38
app/Http/Requests/Design/StoreDesignRequest.php
Normal file
38
app/Http/Requests/Design/StoreDesignRequest.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Design;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\Design;
|
||||
|
||||
class StoreDesignRequest extends Request
|
||||
{
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
//'name' => 'required',
|
||||
'name' => 'required|unique:designs,name,null,null,company_id,'.auth()->user()->companyId(),
|
||||
'design' => 'required',
|
||||
];
|
||||
}
|
||||
}
|
39
app/Http/Requests/Design/UpdateDesignRequest.php
Normal file
39
app/Http/Requests/Design/UpdateDesignRequest.php
Normal file
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com)
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2020. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://opensource.org/licenses/AAL
|
||||
*/
|
||||
|
||||
namespace App\Http\Requests\Design;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Models\Design;
|
||||
use App\Utils\Traits\ChecksEntityStatus;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class UpdateDesignRequest extends Request
|
||||
{
|
||||
use ChecksEntityStatus;
|
||||
/**
|
||||
* Determine if the user is authorized to make this request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
|
||||
public function authorize() : bool
|
||||
{
|
||||
return auth()->user()->isAdmin();
|
||||
}
|
||||
|
||||
public function rules()
|
||||
{
|
||||
return [
|
||||
// 'name' => 'unique:designs,name,'.$this->designs->name.',id,company_id,'.auth()->user()->companyId(),
|
||||
];
|
||||
}
|
||||
}
|
@ -75,9 +75,11 @@ class Company extends BaseModel
|
||||
'show_product_details',
|
||||
'first_day_of_week',
|
||||
'first_month_of_year',
|
||||
|
||||
'slack_webhook_url',
|
||||
'google_analytics_url',
|
||||
];
|
||||
|
||||
|
||||
protected $hidden = [
|
||||
'id',
|
||||
'db',
|
||||
|
@ -11,18 +11,29 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\Filterable;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class Design extends BaseModel
|
||||
{
|
||||
|
||||
use Filterable;
|
||||
use SoftDeletes;
|
||||
|
||||
protected $casts = [
|
||||
'design' => 'object',
|
||||
'deleted_at' => 'timestamp',
|
||||
'updated_at' => 'timestamp',
|
||||
'created_at' => 'timestamp',
|
||||
];
|
||||
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'design',
|
||||
'is_active',
|
||||
];
|
||||
|
||||
public function company()
|
||||
{
|
||||
return $this->belongsTo(Company::class);
|
||||
|
@ -300,13 +300,9 @@ class User extends Authenticatable implements MustVerifyEmail
|
||||
|
||||
public function routeNotificationForSlack($notification)
|
||||
{
|
||||
//todo need to return the company channel here for hosted users
|
||||
//else the env variable for selfhosted
|
||||
if(config('ninja.environment') == 'selfhosted')
|
||||
return config('ninja.notification.slack');
|
||||
|
||||
if($this->company())
|
||||
return $this->company()->settings->system_notifications_slack;
|
||||
return $this->company()->slack_webhook_url;
|
||||
|
||||
|
||||
}
|
||||
|
16
app/Policies/DesignPolicy.php
Normal file
16
app/Policies/DesignPolicy.php
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Policies;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Auth\Access\HandlesAuthorization;
|
||||
|
||||
class DesignPolicy extends EntityPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
public function create(User $user) : bool
|
||||
{
|
||||
return $user->isAdmin() || $user->hasPermission('create_all');
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Design;
|
||||
use App\Models\Expense;
|
||||
use App\Models\GroupSetting;
|
||||
use App\Models\Invoice;
|
||||
@ -32,6 +33,7 @@ use App\Policies\ClientPolicy;
|
||||
use App\Policies\CompanyGatewayPolicy;
|
||||
use App\Policies\CompanyPolicy;
|
||||
use App\Policies\CreditPolicy;
|
||||
use App\Policies\DesignPolicy;
|
||||
use App\Policies\ExpensePolicy;
|
||||
use App\Policies\GroupSettingPolicy;
|
||||
use App\Policies\InvoicePolicy;
|
||||
@ -58,6 +60,7 @@ class AuthServiceProvider extends ServiceProvider
|
||||
Activity::class => ActivityPolicy::class,
|
||||
Client::class => ClientPolicy::class,
|
||||
Company::class => CompanyPolicy::class,
|
||||
Design::class => DesignPolicy::class,
|
||||
Product::class => ProductPolicy::class,
|
||||
Invoice::class => InvoicePolicy::class,
|
||||
Credit::class => CreditPolicy::class,
|
||||
|
@ -51,6 +51,7 @@ class DesignTransformer extends EntityTransformer
|
||||
'updated_at' => (int)$design->updated_at,
|
||||
'archived_at' => (int)$design->deleted_at,
|
||||
'created_at' => (int)$design->created_at,
|
||||
'is_deleted' => (bool)$design->is_deleted,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ class CreateUsersTable extends Migration
|
||||
$table->string('decimal_separator');
|
||||
$table->string('code');
|
||||
$table->boolean('swap_currency_symbol')->default(false);
|
||||
$table->decimal('exchange_rate', 6, 6)->default(1);
|
||||
$table->decimal('exchange_rate', 13, 6)->default(1);
|
||||
|
||||
});
|
||||
|
||||
@ -175,6 +175,9 @@ class CreateUsersTable extends Migration
|
||||
$table->mediumText('custom_fields');
|
||||
$table->mediumText('settings');
|
||||
|
||||
$table->string('slack_webhook_url');
|
||||
$table->string('google_analytics_url');
|
||||
|
||||
$table->timestamps(6);
|
||||
//$table->softDeletes('deleted_at', 6);
|
||||
|
||||
@ -1375,6 +1378,7 @@ class CreateUsersTable extends Migration
|
||||
$table->boolean('is_custom')->default(true);
|
||||
$table->boolean('is_active')->default(true);
|
||||
$table->mediumText('design')->nullable();
|
||||
$table->boolean('is_deleted')->default(false);
|
||||
$table->timestamps(6);
|
||||
$table->softDeletes('deleted_at', 6);
|
||||
$table->foreign('company_id')->references('id')->on('companies')->onDelete('cascade')->onUpdate('cascade');
|
||||
|
@ -88,6 +88,8 @@ Route::group(['middleware' => ['api_db', 'token_auth', 'locale'], 'prefix' => 'a
|
||||
|
||||
Route::post('migrate', 'MigrationController@index')->name('migrate.start');
|
||||
|
||||
Route::resource('designs', 'DesignController');// name = (payments. index / create / show / update / destroy / edit
|
||||
|
||||
// Route::resource('users', 'UserController')->middleware('password_protected'); // name = (users. index / create / show / update / destroy / edit
|
||||
Route::get('users', 'UserController@index');
|
||||
Route::put('users/{user}', 'UserController@update')->middleware('password_protected');
|
||||
|
132
tests/Feature/DesignApiTest.php
Normal file
132
tests/Feature/DesignApiTest.php
Normal file
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\DataMapper\DefaultSettings;
|
||||
use App\Models\Account;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientContact;
|
||||
use App\Models\Company;
|
||||
use App\Models\Design;
|
||||
use App\Models\User;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Faker\Factory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Foundation\Testing\WithFaker;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Session;
|
||||
use Tests\MockAccountData;
|
||||
use Tests\TestCase;
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @covers App\Http\Controllers\DesignController
|
||||
*/
|
||||
class DesignApiTest extends TestCase
|
||||
{
|
||||
use MakesHash;
|
||||
use DatabaseTransactions;
|
||||
use MockAccountData;
|
||||
|
||||
public $id;
|
||||
|
||||
public function setUp() :void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->makeTestData();
|
||||
|
||||
Session::start();
|
||||
|
||||
$this->faker = \Faker\Factory::create();
|
||||
|
||||
Model::reguard();
|
||||
|
||||
}
|
||||
|
||||
|
||||
public function testDesignPost()
|
||||
{
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'design' => '<HTML></HTML'
|
||||
];
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token
|
||||
])->post('/api/v1/designs', $data);
|
||||
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->id = $arr['data']['id'];
|
||||
|
||||
$this->assertEquals($data['name'], $arr['data']['name']);
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token
|
||||
])->get('/api/v1/designs');
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token
|
||||
])->get('/api/v1/designs/'.$this->id);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals($this->id, $arr['data']['id']);
|
||||
|
||||
$data = [
|
||||
'name' => $this->faker->firstName,
|
||||
'design' => 'changed'
|
||||
];
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token
|
||||
])->put('/api/v1/designs/'.$this->id, $data);
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$this->assertEquals($data['name'], $arr['data']['name']);
|
||||
$this->assertEquals($data['design'], $arr['data']['design']);
|
||||
|
||||
|
||||
$response = $this->withHeaders([
|
||||
'X-API-SECRET' => config('ninja.api_secret'),
|
||||
'X-API-TOKEN' => $this->token
|
||||
])->delete('/api/v1/designs/'.$this->id, $data);
|
||||
|
||||
|
||||
$response->assertStatus(200);
|
||||
|
||||
$arr = $response->json();
|
||||
|
||||
$design = Design::whereId($this->decodePrimaryKey($this->id))->withTrashed()->first();
|
||||
|
||||
$this->assertTrue((bool)$design->is_deleted);
|
||||
$this->assertGreaterThan(0, $design->deleted_at);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user