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:
parent
db612b7767
commit
e25c5b1005
1
.gitignore
vendored
1
.gitignore
vendored
@ -5,6 +5,7 @@ env.*.php
|
||||
env
|
||||
.env.php
|
||||
.env
|
||||
.env.bak
|
||||
.env.example
|
||||
vendor/
|
||||
composer.phar
|
||||
|
100
app/Factories/LinkFactory.php
Normal file
100
app/Factories/LinkFactory.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
@ -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
12
app/Helpers/ApiHelper.php
Normal 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;
|
||||
}
|
||||
}
|
@ -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),
|
||||
|
@ -1,5 +1,6 @@
|
||||
<?php
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use App\Helpers\LinkHelper;
|
||||
use App\Helpers\CryptoHelper;
|
||||
|
31
app/Http/Controllers/Api/ApiController.php
Normal file
31
app/Http/Controllers/Api/ApiController.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
38
app/Http/Controllers/Api/ApiLinkController.php
Normal file
38
app/Http/Controllers/Api/ApiLinkController.php
Normal 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.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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']);
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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
|
||||
|
3
util/restore_stock_env.sh
Normal file
3
util/restore_stock_env.sh
Normal file
@ -0,0 +1,3 @@
|
||||
mv .env .env.bak
|
||||
wget https://raw.githubusercontent.com/cydrobolt/polr/2.0-dev/.env
|
||||
echo "Done!"
|
Loading…
Reference in New Issue
Block a user