digitalbiblesociety/dbp

View on GitHub
app/Http/Controllers/APIController.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php

namespace App\Http\Controllers;

use App\Models\Language\Language;
use App\Models\User\Key;

use Illuminate\Http\JsonResponse;
use SoapBox\Formatter\Formatter;
use League\Fractal\Serializer\DataArraySerializer;

use Spatie\Fractalistic\ArraySerializer;
use Spatie\ArrayToXml\ArrayToXml;

use Log;
use Symfony\Component\Yaml\Yaml;
use Yosymfony\Toml\TomlBuilder;
use Illuminate\Support\Str;


class APIController extends Controller
{
    // Top Level Swagger Docs

    /**
     * @OA\Info(
     *     description="A Bible API",
     *     version="3.0.0",
     *     title="Digital Bible Platform",
     *     termsOfService="http://dbp4.org/terms/",
     *     @OA\Contact(email="jon@dbs.org"),
     *     @OA\License(name="Apache 2.0",url="http://www.apache.org/licenses/LICENSE-2.0.html")
     * )
     *
     * @OA\Server(
     *     url="https://api.dbp4.org",
     *     description="Live Server",
     *     @OA\ServerVariable( serverVariable="schema", enum={"https"}, default="https")
     * )
     *
     * @OA\Server(
     *     url="https://api.dbp.test",
     *     description="Development server",
     *     @OA\ServerVariable( serverVariable="schema", enum={"https"}, default="https")
     * )
     *
     * @OA\Parameter(parameter="version_number",name="v",in="query",description="The Version Number",required=true,@OA\Schema(type="integer",enum={2,4},example=4))
     * @OA\Parameter(parameter="key",name="key",in="query",description="The Key granted to the api user upon sign up",required=true,@OA\Schema(type="string",example="ar45g3h4ae644"))
     * @OA\Parameter(parameter="pretty",name="pretty",in="query",description="Setting this param to true will add human readable whitespace to the return",@OA\Schema(type="boolean"))
     * @OA\Parameter(parameter="format",name="format",in="query",description="Setting this param to true will add format the return as a specific file type. The currently supported return types are `xml`, `csv`, `json`, and `yaml`",@OA\Schema(type="string",enum={"xml","csv","json","yaml"}))
     * @OA\Parameter(name="sort_by", in="query", description="The field to sort by", @OA\Schema(type="string"))
     * @OA\Parameter(name="sort_dir", in="query", description="The direction to sort by", @OA\Schema(type="string",enum={"asc","desc"}))
     * @OA\Parameter(name="l10n", in="query", description="When set to a valid three letter language iso, the returning results will be localized in the language matching that iso. (If an applicable translation exists).", @OA\Schema(ref="#/components/schemas/Language/properties/iso")),
     *
     */

    /**
     * Version 2 Tags
     *
     * @OA\Tag(name="Library Audio",    description="v2 These methods retrieve all the information needed to build and retrieve audio information for each chapter/book/or volume.")
     * @OA\Tag(name="Library Catalog",  description="v2 These methods retrieve all the information needed to build and retrieve audio information for each chapter/book/or volume.")
     * @OA\Tag(name="Library Text",     description="v2 These methods allow the caller to retrieve Bible text in a variety of configurations.")
     * @OA\Tag(name="Library Video",    description="v2 These calls address the information needed to build and retrieve video information for each volume.")
     * @OA\Tag(name="Country Language", description="v2 These calls provide all information pertaining to country languages.")
     * @OA\Tag(name="Study Programs",   description="v2 These calls provide all information pertaining to Bible study programs.")
     *
     */

    /**
     * Version 4 Tags
     *
     * @OA\Tag(name="Languages",       description="v4 Routes for obtaining Languages Data",
     *     @OA\ExternalDocumentation(
     *         description="For more info please refer to the Ethnologue Registration Authority",
     *         url="https://www.iso.org/iso-639-language-codes.html"
     *     )
     * )
     * @OA\Tag(name="Countries",       description="v4 Routes for obtaining Countries Data",
     *     @OA\ExternalDocumentation(
     *         description="For more info please refer to the Iso Registration Authority",
     *         url="https://www.iso.org/iso-3166-country-codes.html"
     *     )
     * )
     * @OA\Tag(name="Bibles",          description="v4 Routes for obtaining Bibles Data")
     * @OA\Tag(name="Users",           description="v4 Routes for obtaining Users Data")
     *
     */

    /**
     * The statusCode is a http status code. Every variation of this
     * must also be a http status code. There is a full list here
     * https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
     *
     * @var int $statusCode
     */
    protected $statusCode = 200;
    protected $api;
    protected $serializer;
    protected $preset_v;
    protected $v;
    protected $key;
    protected $user;

    public function __construct()
    {
        $url           = explode('.', url()->current());
        $subdomain     = array_shift($url);
        if (Str::contains($subdomain, 'api')) {
            $this->api = true;
            $this->v   = (int) checkParam('v', true, $this->preset_v);
            $this->key = checkParam('key', true);

            $cache_string = 'keys:'.$this->key;
            $keyExists = \Cache::remember($cache_string, now()->addDay(), function () {
                return Key::with('user')->where('key', $this->key)->first();
            });
            $this->user = $keyExists->user ?? null;

            if (!$this->user) {
                abort(401, 'You need to provide a valid API key. To request an api key please email access@dbp4.org');
            }

            // i18n
            $i18n = checkParam('i18n') ?? 'eng';

            $cache_string = 'selected_api_language:'.strtolower($i18n);
            $current_language = \Cache::remember($cache_string, now()->addDay(), function () use ($i18n) {
                $language = Language::where('iso', $i18n)->select(['iso','id'])->first();
                return [
                    'i18n_iso' => $language->iso,
                    'i18n_id'  => $language->id
                ];
            });
            $GLOBALS['i18n_iso'] = $current_language['i18n_iso'];
            $GLOBALS['i18n_id']  = $current_language['i18n_id'];

            $this->serializer = (($this->v === 1) || ($this->v === 2) || ($this->v === 3)) ? new ArraySerializer() : new DataArraySerializer();
        }
    }

    /**
     * Set Status Code
     *
     * @param mixed $statusCode
     *
     * @return mixed
     */
    public function setStatusCode($statusCode)
    {
        $this->statusCode = $statusCode;

        return $this;
    }

    /**
     *
     * Get The object and return it in the format requested via query params.
     *
     * @param       $object
     *
     * @param array $meta
     * @param null  $s3_transaction_id
     *
     * @return mixed
     */
    public function reply($object, array $meta = [], $s3_transaction_id = null)
    {
        if (isset($_GET['echo'])) {
            $object = [$_GET, $object];
        }
        $input  = checkParam('callback|jsonp');
        $format = checkParam('reply|format');

        if (is_a($object, JsonResponse::class)) {
            return $object;
        }
        
        // Status Code, Headers, Params, Body, Time
        try {
            apiLogs(request(), $this->statusCode, $s3_transaction_id, request()->ip());
        } catch (\Exception $e) {
            Log::error($e->getMessage());
        }

        return $this->replyFormatter($object, $meta, $format, $input);
    }

    /**
     * @param $message
     * @param $action
     *
     * @return mixed
     */
    public function replyWithError($message, $action = null)
    {
        if (!$this->api) {
            return view('layouts.errors.broken')->with(['message' => $message]);
        }

        try {
            apiLogs(request(), $this->statusCode);
        } catch (\Exception $e) {
            Log::error($e);
        }

        if ((int) $this->v === 2) {
            return [];
        }

        return response()->json(['error' => [
            'message'     => $message,
            'status code' => $this->statusCode,
            'action'      => $action ?? ''
        ]], $this->statusCode);
    }


    /**
     * @param       $object
     * @param array $meta
     * @param       $format
     * @param       $input
     *
     * @return JsonResponse|\Illuminate\Http\Response
     */
    private function replyFormatter($object, array $meta, $format, $input)
    {
        $object = json_decode(json_encode($object), true);

        switch ($format) {
            case 'jsonp':
                return response()->json($object, $this->statusCode)
                                 ->header('Content-Type', 'application/javascript; charset=utf-8')
                                 ->setCallback(request()->input('callback'));
            case 'xml':
                $formatter = ArrayToXml::convert($object, [
                    'rootElementName' => $meta['rootElementName'] ?? 'root',
                    '_attributes'     => $meta['rootAttributes'] ?? []
                ], true, 'utf-8');
                return response()->make($formatter, $this->statusCode)
                                 ->header('Content-Type', 'application/xml; charset=utf-8');
            case 'yaml':
                $formatter = Yaml::dump($object);
                return response()->make($formatter, $this->statusCode)
                                 ->header('Content-Type', 'text/yaml; charset=utf-8');
            case 'toml':
                $tomlBuilder = new TomlBuilder();
                $formatter   = $tomlBuilder->addValue('multiple', $object)->getTomlString();
                return response()->make($formatter, $this->statusCode)
                                 ->header('Content-Type', 'text/yaml; charset=utf-8');
            case 'csv':
                $formatter = Formatter::make($object, Formatter::ARR);
                return response()->make($formatter->toCsv(), $this->statusCode)
                                 ->header('Content-Type', 'text/csv; charset=utf-8');
            default:
                return response()->json($object, $this->statusCode, [], JSON_UNESCAPED_UNICODE)
                        ->header('Content-Type', 'application/json; charset=utf-8')->setCallback($input);
        }
    }
}