diff --git a/laravel/app/Http/Controllers/ShortlinkController.php b/laravel/app/Http/Controllers/ShortlinkController.php new file mode 100644 index 0000000..cf2b632 --- /dev/null +++ b/laravel/app/Http/Controllers/ShortlinkController.php @@ -0,0 +1,43 @@ +user() == null) { + return response()->json(['error' => 'Unauthorized'], 401); + } + + try { + $request->validate([ + 'url' => 'required|url' + ]); + + //check if url returns 200 at its final redirect + + $shortlink = new Shortlink(); + $shortlink->create($request->url, auth()->id()); + return redirect("/l/{$shortlink->shortid}"); + } catch (\Exception $e) { + return back()->withErrors([ + 'error' => $e->getMessage() + ]); + } + + } + + public function goto(Request $request, $id) { + try { + $shortlink = (new Shortlink())->get($id); + // check if the link is expired or if it has reached the max clicks + // log the interaction + return redirect($shortlink->destination); + } catch (\Exception $e) { + return response()->json(['error' => $e->getMessage()], 404); + } + } +} diff --git a/laravel/app/Models/Shortlink.php b/laravel/app/Models/Shortlink.php new file mode 100644 index 0000000..fe5fe02 --- /dev/null +++ b/laravel/app/Models/Shortlink.php @@ -0,0 +1,62 @@ +shortid = $this->generateNewId(); + $this->destination = $url; + $this->user_id = $user_id; + $this->max_clicks = 0; + $this->expires_at = null; + $this->save(); + return $this; + } + + public function get(string $id): Shortlink { + $shortlink = Shortlink::where('shortid', $id)->first(); + if ($shortlink == null) { + throw new \Exception('This shortened link does not exist'); + } + return $shortlink; + } + + public function delete(): void { + Shortlink::where('shortid', $this->id)->delete(); + } + + public function modify(int $max_clicks, $expires_at): void { + $this->max_clicks = $max_clicks; + $this->expires_at = $expires_at; + $this->save(); + } + + function generateNewId(int $length = 6): string { + $characters = 'qwrtypsdfghjklzxcvbnmQWRTYPSDFGHJKLZXCVBNM256789_'; + // try n x 2 times to generate a new id, if it finds an id, return it, otherwise, try to look for an id of length + 1 + for ($i = 0; $i < $length * 2; $i++) { + $id = ''; + for ($i = 0; $i < $length; $i++) { + $id .= $characters[rand(0, strlen($characters) - 1)]; + } + if (Shortlink::where('shortid', $id)->count() == 0) { + return $id; + } + } + return $this->generateNewId($length + 1); + } +} diff --git a/laravel/database/migrations/2024_06_10_010339_create_shortlinks_table.php b/laravel/database/migrations/2024_06_10_010339_create_shortlinks_table.php new file mode 100644 index 0000000..a43a2ed --- /dev/null +++ b/laravel/database/migrations/2024_06_10_010339_create_shortlinks_table.php @@ -0,0 +1,40 @@ +string('shortid')->unique()->primary(); + $table->string('destination'); + $table->foreignId('user_id')->references('id')->on('users'); + $table->integer('max_clicks')->default(0); + $table->timestamp('expires_at')->nullable(); + $table->timestamps(); + }); + + Schema::create('link_interactions', function (Blueprint $table) { + $table->id(); + $table->string('link'); + $table->foreign('link')->references('shortid')->on('shortlinks'); + $table->string('ip'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('shortlinks'); + Schema::dropIfExists('link_interactions'); + } +}; diff --git a/laravel/public/css/default.css b/laravel/public/css/default.css index b960761..4df7210 100644 --- a/laravel/public/css/default.css +++ b/laravel/public/css/default.css @@ -120,4 +120,45 @@ header nav a { background: #ff000028; border-radius: 5px; padding: 10px; +} + +#shortener-container { + display: flex; + flex-direction: column; + align-items: center; + padding: 60px 50px; + background-color: var(--primary); + width: calc(100% - 100px); + gap: 40px; +} + +#shortener-container h1 { + font-size: 2.5em; + color: white; +} + +#shortener-container input { + color: black; + background-color: #fff; + border-radius: 5px; + font-size: 1.3em; + margin-bottom: 20px; + padding: 10px; + border: 0; + width: 600px; + box-shadow: 2px 2px 5px 0 #0004; +} + +#shortener-container button { + font-size: 1.3em; + padding: 10px 20px; + border: none; + border-radius: 5px; + color: white; + box-shadow: 2px 2px 5px 0 #0004; + background-color: var(--foreground); +} + +#shortener-container input::placeholder { + color: #aaa; } \ No newline at end of file diff --git a/laravel/resources/views/home.blade.php b/laravel/resources/views/home.blade.php index 9c42834..af4752e 100644 --- a/laravel/resources/views/home.blade.php +++ b/laravel/resources/views/home.blade.php @@ -5,6 +5,21 @@ @endsection @section('content') -

HOMEPAGE!

-

Placeholder text

+
+ @if (auth()->check()) +

Shorten your URL

+
+ @csrf + + +
+ @if ($errors->any()) +

{{ $errors->first() }}

+ @endif + @else +

Welcome to slink URL Shortener

+

Sign Up or Log In to shorten your URLs

+ @endif +
+ @endsection \ No newline at end of file diff --git a/laravel/routes/web.php b/laravel/routes/web.php index 2995aec..10f8713 100644 --- a/laravel/routes/web.php +++ b/laravel/routes/web.php @@ -3,6 +3,7 @@ use Illuminate\Support\Facades\Route; use App\Http\Controllers\RegisterController; use App\Http\Controllers\LoginController; +use App\Http\Controllers\ShortlinkController; Route::get('/', function () { return view('welcome'); @@ -43,4 +44,10 @@ Route::get('/home', function () { return view('home'); } return view('home'); -}); \ No newline at end of file +}); + +Route::post('/shorten', [ShortlinkController::class, 'create']); + +// Route::get('/l/{id}', ); + +Route::get('/{id}', [ShortlinkController::class, 'goto']); \ No newline at end of file