diff --git a/v5/Http/Controllers/USSDController.php b/v5/Http/Controllers/USSDController.php new file mode 100644 index 0000000000..40a073a604 --- /dev/null +++ b/v5/Http/Controllers/USSDController.php @@ -0,0 +1,153 @@ + WARNING: This is a crutch <--- + * + * We are adding this specific controller for posts coming from USSD sources, + * so that incoming posts can have contact information attached, just like SMS. + * + * Eventually we should have a more evolved data source framework that allows + * bringing in structured posts with source metadata. + */ +class USSDController extends PostController +{ + public function show(int $id) + { + throw new Exception("Invalid controller method"); + } + + public function index() + { + throw new Exception("Invalid controller method"); + } + + /** + * Overriding the POST method so as to handle contact information + */ + public function store(Request $request) + { + /* TODO: USSD-specific authorization? + * + * Presently the assumption is that these come in anonymously, + * because that's how ussd-engine operates. + * But what would happen if they came in as an authenticated user? + * What would be considered the source of the post? The user, or + * the detailed shource information. + * + * As we are thinking of more loosely coupled datasources, this + * should be taken into account. + */ + + /* This method works on an additional property to the post body: + * + * { + * ..., + * source_info: { + * received: "YYYY-MM-DDTHH:MM:SSZ", + * data_source: "ussd", + * type: "phone", + * contact: "xxxxxxxxx" + * } + * ... + * } + * + * Validate this property from the request. + */ + $source_info = $request->input('source_info'); + $val_rules = [ + 'source_info' => 'required', + // TODO: couldn't get this validation just right yet + // 'source_info.received' => 'required|string|date_format:' . \DateTime::ISO8601, + 'source_info.data_source' => 'required|string|in:ussd', + 'source_info.type' => 'required|string|in:phone', + 'source_info.contact' => 'required|string|min:6' + ]; + $v = ValidatorRunner::runValidation( + ['source_info' => $source_info], + $val_rules, + [] // TODO: custom messages? + ); + if ($v->fails()) { + return self::make422($v->getErrors()); + } + + /* Call up the parent controller to get the post created */ + $post = parent::store($request); + if (!($post instanceof PostResource)) { + /* An error has happened creating the post, shortcircuit to that */ + return $post; + } + + DB::beginTransaction(); + try { + /* Lookup / create contact if not present */ + /* assert type is phone */ + $contact = Contact::firstOrCreate([ + 'data_source' => $source_info['data_source'], + 'type' => $source_info['type'], + 'contact' => $source_info['contact'] + ], [ + 'can_notify' => false + ]); + + /* Create message record */ + $message = new Message; + $message->contact()->associate($contact)->save(); + $message->post_id = $post->id; // this is not yet an eloquent-managed relationship + /* USSD doesn't technically come in with a message, we shall craft + * a stand-in. */ + $message->title = "(Fulfilled USSD survey)"; + /* TODO: anything more useful that could go here? */ + $message->message = "(Fulfilled USSD survey)"; + $message->datetime = \DateTime::createFromFormat(\DateTime::ISO8601, $source_info['received']); + $message->data_source = $source_info['data_source']; + /* TODO: anything useful from ussd-engine that could go here? */ + $message->data_source_message_id = "random-" . UUID::uuid4()->toString(); + $message->type = 'ussd'; + $message->status = 'received'; + $message->direction = 'incoming'; + $message->notification_post_id = null; + $message->save(); + + DB::commit(); + + /* The post resource should now be re-rendered, because of the + * information added to it since creation in the parent. + * This is not ideal performance-wise, but we'll take the hit + * for now. + */ + return new PostResource($post->resource); + } catch (\Exception $e) { + DB::rollback(); + return self::make500($e->getMessage()); + } + } + + public function patch(int $id, Request $request) + { + throw new Exception("Invalid controller method"); + } + + public function update(int $id, Request $request) + { + throw new Exception("Invalid controller method"); + } + + public function delete(int $id, Request $request) + { + throw new Exception("Invalid controller method"); + } +} diff --git a/v5/Http/Middleware/V5GlobalScopes.php b/v5/Http/Middleware/V5GlobalScopes.php index b4b764b28c..95f75feaf0 100644 --- a/v5/Http/Middleware/V5GlobalScopes.php +++ b/v5/Http/Middleware/V5GlobalScopes.php @@ -25,7 +25,11 @@ public function handle($request, Closure $next) * @TODO more tests maybe???????? * @TODO remove the need for isSavingPost */ - $isSavingPost = $request->path() === 'api/v5/posts' && $request->isMethod('post'); + $isSavingPost = $request->isMethod('post') && \ + in_array($request->path(), [ + 'api/v5/posts', + 'api/v5/posts/_ussd' + ]); if (!$isSavingPost) { Category::addGlobalScope(new CategoryAllowed); diff --git a/v5/Models/Contact.php b/v5/Models/Contact.php index aab731f5f7..d724f947ed 100644 --- a/v5/Models/Contact.php +++ b/v5/Models/Contact.php @@ -8,12 +8,22 @@ class Contact extends BaseModel 'messages' ]; + const CREATED_AT = 'created'; + const UPDATED_AT = 'updated'; + /** * Add eloquent style timestamps * * @var boolean */ - public $timestamps = false; + public $timestamps = true; + + /** + * The storage format of the model's date columns. + * + * @var string + */ + protected $dateFormat = 'U'; /** * Specify the table to load with Survey diff --git a/v5/Models/Message.php b/v5/Models/Message.php index db0243b6ae..53893ac23c 100644 --- a/v5/Models/Message.php +++ b/v5/Models/Message.php @@ -7,13 +7,24 @@ class Message extends BaseModel public static $relationships = [ 'contact' ]; + # --> and relationship to Post? + + const CREATED_AT = 'created'; + const UPDATED_AT = null; /** * Add eloquent style timestamps * * @var boolean */ - public $timestamps = false; + public $timestamps = true; + + /** + * The storage format of the model's date columns. + * + * @var string + */ + protected $dateFormat = 'U'; /** * Specify the table to load with Survey diff --git a/v5/routes/api.php b/v5/routes/api.php index 4a1cd6168b..e1088feba4 100644 --- a/v5/routes/api.php +++ b/v5/routes/api.php @@ -82,5 +82,6 @@ ], function () use ($router) { // Public access $router->post('/', 'PostController@store'); + $router->post('/_ussd', 'USSDController@store'); }); });