1
0
mirror of https://github.com/cydrobolt/polr.git synced 2024-11-09 11:42:28 +01:00

Abstract URL shortening, create API endpoints & logic

This commit is contained in:
Chaoyi Zha 2016-01-29 14:58:49 -05:00
parent db612b7767
commit e25c5b1005
15 changed files with 221 additions and 75 deletions

1
.gitignore vendored
View File

@ -5,6 +5,7 @@ env.*.php
env
.env.php
.env
.env.bak
.env.example
vendor/
composer.phar

View File

@ -0,0 +1,100 @@
<?php
namespace App\Factories;
use App\Models\Link;
use App\Helpers\CryptoHelper;
use App\Helpers\LinkHelper;
class LinkFactory {
private static function formatLink($link_ending, $secret_ending=false) {
/**
* Given a link ending and a boolean indicating whether a secret ending is needed,
* return a link formatted with app protocol, app address, and link ending.
* @param string $link_ending
* @param boolean $secret_ending
* @return string
*/
$short_url = env('APP_PROTOCOL') . env('APP_ADDRESS') . '/' . $link_ending;
if ($secret_ending) {
$short_url .= '/' . $secret_ending;
}
return $short_url;
}
public static function createLink($long_url, $is_secret=false, $custom_ending=null, $link_ip='127.0.0.1', $creator=false) {
/**
* Given parameters needed to create a link, generate appropriate ending and
* return formatted link.
*
* @param string $custom_ending
* @param boolean (optional) $is_secret
* @param string (optional) $custom_ending
* @param string $link_ip
* @param string $creator
* @return string $formatted_link
*/
$is_already_short = LinkHelper::checkIfAlreadyShortened($long_url);
if ($is_already_short) {
throw new \Exception('Sorry, but your link already
looks like a shortened URL.');
}
if (!$is_secret && !$custom_ending && $existing_link = LinkHelper::longLinkExists($long_url)) {
// if link is not specified as secret, is non-custom, and
// already exists in Polr, lookup the value and return
return self::formatLink($existing_link);
}
if ($custom_ending) {
// has custom ending
$ending_conforms = LinkHelper::validateEnding($custom_ending);
if (!$ending_conforms) {
throw new \Exception('Sorry, but custom endings
can only contain alphanumeric characters');
}
$ending_in_use = LinkHelper::linkExists($custom_ending);
if ($ending_in_use) {
throw new \Exception('Sorry, but this URL ending is already in use.');
}
$link_ending = $custom_ending;
}
else {
// no custom ending
$link_ending = LinkHelper::findSuitableEnding();
}
$link = new Link;
$link->short_url = $link_ending;
$link->long_url = $long_url;
$link->ip = $link_ip;
$link->is_custom = $custom_ending != null;
if ($creator) {
// if user is logged in, save user as creator
$link->creator = $creator;
}
if ($is_secret) {
$rand_bytes_num = intval(env('POLR_SECRET_BYTES'));
$secret_key = CryptoHelper::generateRandomHex($rand_bytes_num);
$link->secret_key = $secret_key;
}
else {
$secret_key = false;
}
$link->save();
$formatted_link = self::formatLink($link_ending, $secret_key);
return $formatted_link;
}
}

View File

@ -4,7 +4,8 @@ namespace App\Factories;
use Hash;
use App\Models\User;
use App\Helpers\CryptoHelper;
class UserFactory {
class UserFactory {
public static function createUser($username, $email, $password, $active=0, $ip='127.0.0.1') {
$hashed_password = Hash::make($password);

12
app/Helpers/ApiHelper.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Helpers\ApiHelper;
class ApiHelper {
public static function checkUserApiQuota($username) {
// pass
return true;
}
}

View File

@ -36,14 +36,15 @@ class LinkHelper {
static public function linkExists($link_ending) {
/**
* Provided a link ending (string),
* check whether the ending is in use.
* @return boolean
* return the link object, or false.
* @return Link model instance
*/
$link = Link::where('short_url', $link_ending)
->first();
if ($link == null) {
return false;
return $link;
}
else {
return true;
@ -73,6 +74,14 @@ class LinkHelper {
return $is_alphanum;
}
static public function processPostClick($link) {
/**
* Given a Link model instance, process post click operations.
* @param Link model instance $link
* @return boolean
*/
}
static public function findSuitableEnding() {
/**
* Provided an in-use link ending (string),

View File

@ -1,5 +1,6 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Helpers\LinkHelper;
use App\Helpers\CryptoHelper;

View File

@ -0,0 +1,31 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Helpers\ApiHelper;
class ApiController extends ApiController {
protected static function getApiUserInfo(Request $request) {
$api_key = $request->input('api_key');
$user = User::where('active', 1)
->where('api_key', $api_key)
->where('api_active', true)
->first();
$api_limited_reached = ApiHelper::checkUserApiQuota($user->username);
}
protected static function encodeResponse($result, $action, $response_type='json') {
$response = {
"action" => $action,
"result" => $result
}
if ($response_type == 'json') {
return json_encode($response);
}
else if ($response_type == 'plain_text') {
return $result;
}
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace App\Http\Controllers\Api;
use App\Factories\LinkFactory;
use App\Helpers\LinkHelper;
class ApiLinkController extends ApiController {
public static function shortenLink(Request $request) {
$response_type = $request->input('response_type');
$ard = self::getApiUserInfo($request);
/* */
$long_url = $request->input('url');
$is_secret = $request->input('is_secret');
$custom_ending = $request->input('custom_ending');
$formatted_link = LinkFactory::createLink();
return self::encodeResponse($formatted_link, 'shorten', $response_type);
}
public static function lookupLink(Request $request) {
$response_type = $request->input('response_type');
$ard = self::getApiUserInfo($request);
/* */
$url_ending = $request->input('url_ending');
$link_or_false = LinkHelper::linkExists($url_ending);
if ($link_or_false) {
return $link_or_false;
}
else {
abort(404, "Link not found.");
}
}
}

View File

@ -4,7 +4,7 @@ use Illuminate\Http\Request;
use Illuminate\Http\Redirect;
use App\Models\Link;
use App\Factories\LinkFactory;
use App\Helpers\CryptoHelper;
use App\Helpers\LinkHelper;
@ -19,78 +19,25 @@ class LinkController extends Controller {
return redirect(route('index'))->with('error', $message);
}
private function formatAndRender($link_ending, $secret_ending=False) {
$short_url = env('APP_PROTOCOL') . env('APP_ADDRESS') . '/' . $link_ending;
if ($secret_ending) {
$short_url .= '/' . $secret_ending;
}
return view('shorten_result', ['short_url' => $short_url]);
}
public function performShorten(Request $request) {
$this->request = $request;
$long_url = $request->input('link-url');
$custom_ending = $request->input('custom-ending');
$is_secret = ($request->input('options') == "s" ? true : false);
$creator = session('username');
$is_already_short = LinkHelper::checkIfAlreadyShortened($long_url);
if ($is_already_short) {
return $this->renderError('Sorry, but your link already\
looks like a shortened URL.');
$link_ip = $request->ip();
try {
$short_url = LinkFactory::createLink($long_url, $is_secret, $custom_ending, $link_ip, $creator);
}
catch (\Exception $e) {
return self::renderError($e->getMessage());
}
if (!$is_secret && $existing_link = LinkHelper::longLinkExists($long_url)) {
// if link is not specified as secret, is non-custom, and
// already exists in Polr, lookup the value and return
return $this->formatAndRender($existing_link);
}
if ($custom_ending) {
// has custom ending
$ending_conforms = LinkHelper::validateEnding($custom_ending);
if (!$ending_conforms) {
return $this->renderError('Sorry, but custom endings\
can only contain alphanumeric characters');
}
$ending_in_use = LinkHelper::linkExists($custom_ending);
if ($ending_in_use) {
return $this->renderError('Sorry, but this URL ending is already in use.');
}
$link_ending = $custom_ending;
}
else {
// no custom ending
$link_ending = LinkHelper::findSuitableEnding();
}
$link = new Link;
$link->short_url = $link_ending;
$link->long_url = $long_url;
$link->ip = $request->ip();
$link->is_custom = $custom_ending != null;
if ($creator) {
// if user is logged in, save user as creator
$link->creator = $creator;
}
if ($is_secret) {
$rand_bytes_num = intval(env('POLR_SECRET_BYTES'));
$secret_key = CryptoHelper::generateRandomHex($rand_bytes_num);
$link->secret_key = $secret_key;
}
else {
$secret_key = false;
}
$link->save();
return $this->formatAndRender($link_ending, $secret_key);
return view('shorten_result', ['short_url' => $short_url]);
}
public function performRedirect(Request $request, $short_url, $secret_key=false) {
@ -109,8 +56,6 @@ class LinkController extends Controller {
]);
}
if ($link_secret_key) {
if (!$secret_key) {
// if we do not receieve a secret key
@ -137,6 +82,8 @@ class LinkController extends Controller {
$link->save();
LinkHelper::processPostClick($link);
return redirect()->to($long_url);
}
}

View File

@ -42,5 +42,7 @@ $app->post('/admin/action/change_password', ['as' => 'change_password', 'uses' =
$app->post('/api/v2/link_avail_check', ['as' => 'api_link_check', 'uses' => 'AjaxController@checkLinkAvailability']);
$app->post('/api/v2/admin/toggle_api_active', ['as' => 'api_toggle_api_active', 'uses' => 'AjaxController@toggleAPIActive']);
$app->post('/api/v2/admin/generate_new_api_key', ['as' => 'api_generate_new_api_key', 'uses' => 'AjaxController@generateNewAPIKey']);
$app->post('/api/v2/admin/delete_user', ['as' => 'api_generate_new_api_key', 'uses' => 'AjaxController@deleteUser']);
$app->post('/api/v2/action/shorten', ['as' => 'api_shorten_url', 'uses' => 'Api\ApiLinkController@shortenLink']);
$app->post('/api/v2/action/lookup', ['as' => 'api_lookup_url', 'uses' => 'Api\ApiLinkController@lookupLink']);

View File

@ -28,6 +28,7 @@ class CreateLinkTable extends Migration
$table->boolean('is_disabled')->default(0);
$table->boolean('is_custom')->default(0);
$table->boolean('is_api')->default(0);
$table->timestamps();
});

View File

@ -57,16 +57,16 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
<script src='/js/base.js'></script>
<script>
@if (Session::has('info'))
toastr["info"]("{{session('infoo')}}", "Info")
toastr["info"](`{{session('infoo')}}`, "Info")
@endif
@if (Session::has('error'))
toastr["error"]("{{session('error')}}", "Error")
toastr["error"](`{{session('error')}}`, "Error")
@endif
@if (Session::has('warning'))
toastr["warning"]("{{session('warning')}}", "Warning")
toastr["warning"](`{{session('warning')}}`, "Warning")
@endif
@if (Session::has('success'))
toastr["success"]("{{session('success')}}", "Success")
toastr["success"](`{{session('success')}}`, "Success")
@endif
</script>

View File

@ -30,7 +30,7 @@
</td>
<td>
<a class='delete-user btn btn-sm btn-danger'
<a class='delete-user btn btn-sm btn-danger @if (session('username') == $user->username)btn-disabled @endif'
data-user-id='{{$user->id}}'>
Delete

View File

@ -0,0 +1,3 @@
mv .env .env.bak
wget https://raw.githubusercontent.com/cydrobolt/polr/2.0-dev/.env
echo "Done!"