Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
85b9eff
Apply fixes from StyleCI
StyleCIBot Jun 10, 2025
97bbe4b
Merge pull request #1 from stardothosting/analysis-RPrkYY
stardothosting Jun 10, 2025
83781b9
Updated readme
stardothosting Jun 10, 2025
533faa7
Footer github link
stardothosting Jun 10, 2025
bfd32e1
Added privacy policy, updated footer links
stardothosting Jun 10, 2025
bfa596c
Speed improvements, exception handling for unwrangle improvements
stardothosting Jun 10, 2025
af95159
Verbiage change on homepage blade template
stardothosting Jun 10, 2025
1396001
Verbiage change on homepage blade template
stardothosting Jun 10, 2025
650b971
Verbiage change on homepage blade template
stardothosting Jun 10, 2025
ca49fec
Verbiage change on homepage blade template
stardothosting Jun 10, 2025
b69c0b5
Verbiage change on homepage blade template
stardothosting Jun 10, 2025
a67b3d9
Fixed ASIN validation issue
stardothosting Jun 10, 2025
e116598
Fixed Captcha validation
stardothosting Jun 10, 2025
138041e
Fixed OpenAI parsing error
stardothosting Jun 10, 2025
2220d9d
Fixed OpenAI parsing error
stardothosting Jun 10, 2025
49fe98b
Issue 5 : Amazon URL validation was requiring a backend fetch request…
stardothosting Jun 11, 2025
22b921e
Further changes and additional tests for issue 5
stardothosting Jun 11, 2025
a86ed01
feat: add backend URL expansion for Amazon short URLs
stardothosting Jun 11, 2025
78f859c
Created new tests , final adjustments
stardothosting Jun 12, 2025
31e6011
Fixed failing test
stardothosting Jun 12, 2025
db0dc46
Merge pull request #6 from stardothosting/issue-5
stardothosting Jun 12, 2025
af5c6b1
Updated readme
stardothosting Jun 12, 2025
725d072
removed cloudflare worker proxy
stardothosting Jun 13, 2025
d6015a9
Minor responsive fix
stardothosting Jun 17, 2025
90c653d
Merge pull request #10 from stardothosting/issue-9
stardothosting Jun 17, 2025
b6c7358
Improve review audit prompt with OpenAI
stardothosting Jun 21, 2025
a0bd00c
Improved system prompt with LLM-based prompt eng.
TaylorTurnerIT Jun 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

A Laravel application that analyzes Amazon product reviews to detect fake reviews using AI. The service fetches reviews via the Unwrangle API, analyzes them with OpenAI, and provides authenticity scores.

Visit [nullfake.com](https://nullfake.com) to try it out.
# Visit [nullfake.com](https://nullfake.com) to try it out.
# Read our [blog post about how nullfake works](https://shift8web.ca/from-fakespot-to-null-fake-navigating-the-evolving-landscape-of-fake-reviews/)

## How It Works

Expand Down Expand Up @@ -54,3 +55,8 @@ The model calculates:
## License

MIT

## Shift8

This was developed in Toronto Canada by [Shift8 Web](https://shift8web.ca)

2 changes: 1 addition & 1 deletion app/Exceptions/ReviewServiceException.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

class ReviewServiceException extends \Exception
{
}
}
3 changes: 2 additions & 1 deletion app/Http/Controllers/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@

class Controller extends BaseController
{
use AuthorizesRequests, ValidatesRequests;
use AuthorizesRequests;
use ValidatesRequests;
}
148 changes: 148 additions & 0 deletions app/Http/Controllers/UrlExpansionController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use App\Services\LoggingService;

class UrlExpansionController extends Controller
{
/**
* Expand a shortened URL to its final destination
*/
public function expandUrl(Request $request): JsonResponse
{
$request->validate([
'url' => 'required|url'
]);

$shortUrl = $request->input('url');

// Only expand Amazon short URLs for security
if (!$this->isAmazonShortUrl($shortUrl)) {
return response()->json([
'success' => false,
'error' => 'Only Amazon URLs are supported'
], 400);
}

try {
LoggingService::log('Expanding short URL', [
'short_url' => $shortUrl
]);

// Follow redirects manually to get final URL
$finalUrl = $this->followRedirects($shortUrl);

LoggingService::log('URL expansion successful', [
'short_url' => $shortUrl,
'final_url' => $finalUrl
]);

return response()->json([
'success' => true,
'original_url' => $shortUrl,
'expanded_url' => $finalUrl
]);

} catch (GuzzleException $e) {
LoggingService::log('URL expansion failed', [
'short_url' => $shortUrl,
'error' => $e->getMessage()
]);

return response()->json([
'success' => false,
'error' => 'Unable to expand URL: ' . $e->getMessage()
], 500);
}
}

/**
* Follow redirects manually to get final URL without validating the final destination
*/
private function followRedirects(string $url, int $maxRedirects = 5): string
{
$client = new Client([
'timeout' => 5,
'connect_timeout' => 2,
'allow_redirects' => false,
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
]
]);

$currentUrl = $url;
$redirectCount = 0;

while ($redirectCount < $maxRedirects) {
try {
$response = $client->get($currentUrl);
$statusCode = $response->getStatusCode();

LoggingService::log('Redirect step', [
'url' => $currentUrl,
'status' => $statusCode,
'redirect_count' => $redirectCount
]);

if (in_array($statusCode, [301, 302, 303, 307, 308])) {
$locationHeaders = $response->getHeader('Location');
if (empty($locationHeaders)) {
LoggingService::log('No location header found, stopping redirects');
break;
}

$newUrl = $locationHeaders[0];

// If we've reached an Amazon product URL, stop here - don't try to validate it
if (strpos($newUrl, 'amazon.com/dp/') !== false || strpos($newUrl, 'amazon.com/gp/product/') !== false) {
LoggingService::log('Reached Amazon product URL, stopping redirects', [
'final_url' => $newUrl
]);
return $newUrl;
}

$currentUrl = $newUrl;
$redirectCount++;
} else {
// Got a non-redirect response, stop here
break;
}
} catch (\Exception $e) {
LoggingService::log('Redirect failed', [
'url' => $currentUrl,
'error' => $e->getMessage()
]);
break;
}
}

return $currentUrl;
}

/**
* Check if URL is an Amazon short URL
*/
private function isAmazonShortUrl(string $url): bool
{
$amazonShortDomains = [
'a.co',
'amzn.to',
'amazon.com'
];

$host = parse_url($url, PHP_URL_HOST);

foreach ($amazonShortDomains as $domain) {
if ($host === $domain || str_ends_with($host, '.' . $domain)) {
return true;
}
}

return false;
}
}
20 changes: 10 additions & 10 deletions app/Http/Kernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,16 @@ class Kernel extends HttpKernel
* @var array<string, class-string|string>
*/
protected $middlewareAliases = [
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'auth' => \App\Http\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'auth.session' => \Illuminate\Session\Middleware\AuthenticateSession::class,
'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
];
}
2 changes: 1 addition & 1 deletion app/Http/Middleware/RedirectIfAuthenticated.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class RedirectIfAuthenticated
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next, string ...$guards): Response
{
Expand Down
Loading