mirror of
https://github.com/solidtime-io/solidtime.git
synced 2026-08-08 08:12:17 +01:00
Compare commits
1 Commits
v0.5.0
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45a60a926f |
1
.env.ci
1
.env.ci
@@ -5,6 +5,7 @@ APP_DEBUG=true
|
||||
APP_URL=http://localhost
|
||||
APP_FORCE_HTTPS=false
|
||||
APP_ENABLE_REGISTRATION=true
|
||||
SESSION_SECURE_COOKIE=false
|
||||
|
||||
# Logging
|
||||
LOG_CHANNEL=stack
|
||||
|
||||
@@ -5,6 +5,7 @@ VITE_APP_NAME=solidtime
|
||||
APP_ENV=production
|
||||
APP_DEBUG=false
|
||||
APP_FORCE_HTTPS=true
|
||||
SESSION_SECURE_COOKIE=true
|
||||
OCTANE_SERVER=frankenphp
|
||||
PAGINATION_PER_PAGE_DEFAULT=500
|
||||
|
||||
|
||||
@@ -42,7 +42,6 @@ class UserResource extends Resource
|
||||
{
|
||||
/** @var User|null $record */
|
||||
$record = $form->getRecord();
|
||||
|
||||
return $form
|
||||
->columns(1)
|
||||
->schema([
|
||||
|
||||
@@ -64,7 +64,6 @@ class HealthCheckController extends Controller
|
||||
$response['app_env'] = app()->environment();
|
||||
$response['app_timezone'] = config('app.timezone');
|
||||
$response['app_force_https'] = config('app.force_https');
|
||||
$response['session_secure'] = config('session.secure');
|
||||
$response['trusted_proxies'] = config('trustedproxy.proxies');
|
||||
$headers = $request->headers->all();
|
||||
if (isset($headers['cookie'])) {
|
||||
|
||||
@@ -18,7 +18,7 @@ class Kernel extends HttpKernel
|
||||
* @var array<int, class-string|string>
|
||||
*/
|
||||
protected $middleware = [
|
||||
\App\Http\Middleware\ForceHttps::class,
|
||||
// \App\Http\Middleware\TrustHosts::class,
|
||||
\App\Http\Middleware\TrustProxies::class,
|
||||
\Illuminate\Http\Middleware\HandleCors::class,
|
||||
\App\Http\Middleware\PreventRequestsDuringMaintenance::class,
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class ForceHttps
|
||||
{
|
||||
/**
|
||||
* Handle an incoming request.
|
||||
*
|
||||
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
|
||||
*/
|
||||
public function handle(Request $request, Closure $next, string ...$guards): Response
|
||||
{
|
||||
if (config('app.force_https', false)) {
|
||||
URL::forceScheme('https');
|
||||
$request->server->set('HTTPS', 'on');
|
||||
$request->headers->set('X-Forwarded-Proto', 'https');
|
||||
}
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
}
|
||||
22
app/Http/Middleware/TrustHosts.php
Normal file
22
app/Http/Middleware/TrustHosts.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Middleware;
|
||||
|
||||
use Illuminate\Http\Middleware\TrustHosts as Middleware;
|
||||
|
||||
class TrustHosts extends Middleware
|
||||
{
|
||||
/**
|
||||
* Get the host patterns that should be trusted.
|
||||
*
|
||||
* @return array<int, string|null>
|
||||
*/
|
||||
public function hosts(): array
|
||||
{
|
||||
return [
|
||||
$this->allSubdomainsOfApplicationUrl(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Support\Facades\URL;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
@@ -89,6 +90,12 @@ class AppServiceProvider extends ServiceProvider
|
||||
);
|
||||
});
|
||||
|
||||
if (config('app.force_https', false)) {
|
||||
URL::forceScheme('https');
|
||||
request()->server->set('HTTPS', 'on');
|
||||
request()->headers->set('X-Forwarded-Proto', 'https');
|
||||
}
|
||||
|
||||
$this->app->scoped(PermissionStore::class, function (Application $app): PermissionStore {
|
||||
return new PermissionStore;
|
||||
});
|
||||
|
||||
@@ -168,7 +168,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'secure' => env('SESSION_SECURE_COOKIE', env('APP_FORCE_HTTPS')),
|
||||
'secure' => env('SESSION_SECURE_COOKIE'),
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
|
||||
@@ -29,8 +29,6 @@
|
||||
</source>
|
||||
<php>
|
||||
<env name="APP_ENV" value="testing"/>
|
||||
<env name="APP_FORCE_HTTPS" value="false"/>
|
||||
<env name="TRUSTED_PROXIES" value="0.0.0.0/0,2000:0:0:0:0:0:0:0/3"/>
|
||||
<env name="BCRYPT_ROUNDS" value="4"/>
|
||||
<env name="CACHE_DRIVER" value="array"/>
|
||||
<env name="DB_CONNECTION" value="pgsql_test"/>
|
||||
|
||||
@@ -129,6 +129,7 @@ function onSelectChange(event: Event) {
|
||||
:project="timeEntry.project_id"
|
||||
:enable-estimated-time
|
||||
:currency="currency"
|
||||
class="border border-border-primary"
|
||||
:task="
|
||||
timeEntry.task_id
|
||||
"
|
||||
|
||||
@@ -119,6 +119,7 @@ function onSelectChange(event: Event) {
|
||||
:show-badge-border="false"
|
||||
:project="timeEntry.project_id"
|
||||
:currency="currency"
|
||||
class="border border-border-primary"
|
||||
:enable-estimated-time
|
||||
:task="
|
||||
timeEntry.task_id
|
||||
|
||||
@@ -103,14 +103,10 @@ function setBillableDefaultForProject() {
|
||||
}
|
||||
}
|
||||
|
||||
const blockRefocus = ref(false);
|
||||
|
||||
function onToggleButtonPress(newState: boolean) {
|
||||
if (newState) {
|
||||
emit('startTimer');
|
||||
if (!blockRefocus.value){
|
||||
currentTimeEntryDescriptionInput.value?.focus();
|
||||
}
|
||||
currentTimeEntryDescriptionInput.value?.focus();
|
||||
} else {
|
||||
emit('stopTimer');
|
||||
}
|
||||
@@ -133,27 +129,11 @@ function updateTimeEntryDescription() {
|
||||
|
||||
const {timeEntries} = storeToRefs(useTimeEntriesStore());
|
||||
const filteredRecentlyTrackedTimeEntries = computed(() => {
|
||||
// do not include running time entries
|
||||
const finishedTimeEntries = timeEntries.value.filter((item) => item.end !== null);
|
||||
|
||||
// filter out duplicates based on description, task, project, tags and billable
|
||||
const nonDuplicateTimeEntries = finishedTimeEntries.filter((item, index, self) => {
|
||||
return index === self.findIndex((t) => (
|
||||
t.description === item.description &&
|
||||
t.task_id === item.task_id &&
|
||||
t.project_id === item.project_id &&
|
||||
t.tags.length === item.tags.length &&
|
||||
t.tags.every((tag) => item.tags.includes(tag)) &&
|
||||
t.billable === item.billable
|
||||
));
|
||||
});
|
||||
|
||||
// filter time entries based on current description
|
||||
return nonDuplicateTimeEntries.filter((item) => {
|
||||
return timeEntries.value.filter((item) => {
|
||||
return item.description
|
||||
?.toLowerCase()
|
||||
?.includes(tempDescription.value?.toLowerCase()?.trim() || '');
|
||||
}).slice(0, 5);
|
||||
}).slice(0, 5);;
|
||||
});
|
||||
|
||||
const showDropdown = ref(false);
|
||||
@@ -163,14 +143,6 @@ watch(focused, (focused) => {
|
||||
nextTick(() => {
|
||||
// make sure the click event on the dropdown does not get interrupted
|
||||
showDropdown.value = focused
|
||||
|
||||
// make sure that the input does not get refocused after the dropdown is closed
|
||||
if(!focused){
|
||||
blockRefocus.value = true;
|
||||
setTimeout(() => {
|
||||
blockRefocus.value = false;
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -30,14 +30,13 @@ const task = computed(() => {
|
||||
tabindex="-1"
|
||||
:data-select-id="timeEntry.id"
|
||||
:class="twMerge('px-2 py-1.5 flex justify-between items-center space-x-2 w-full rounded', props.highlighted && 'bg-card-background-active')">
|
||||
<span v-if="timeEntry.description !== ''" class="text-sm font-medium">
|
||||
<span class="text-sm font-medium">
|
||||
{{
|
||||
timeEntry.description
|
||||
timeEntry.description !== ''
|
||||
? timeEntry.description
|
||||
: 'No Description'
|
||||
}}
|
||||
</span>
|
||||
<span v-else class="text-sm text-text-tertiary font-medium">
|
||||
No Description
|
||||
</span>
|
||||
<ProjectBadge
|
||||
ref="projectDropdownTrigger"
|
||||
:color="project?.color"
|
||||
|
||||
@@ -82,7 +82,6 @@ class HealthCheckEndpointTest extends EndpointTestAbstract
|
||||
'secure',
|
||||
'timestamp',
|
||||
'timezone',
|
||||
'session_secure',
|
||||
'trusted_proxies',
|
||||
'url',
|
||||
]);
|
||||
|
||||
@@ -5,15 +5,11 @@ declare(strict_types=1);
|
||||
namespace Tests\Unit\Endpoint\Web;
|
||||
|
||||
use App\Http\Controllers\Web\HomeController;
|
||||
use App\Http\Middleware\Authenticate;
|
||||
use App\Http\Middleware\RedirectIfAuthenticated;
|
||||
use App\Models\User;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(HomeController::class)]
|
||||
#[CoversClass(Authenticate::class)]
|
||||
#[CoversClass(RedirectIfAuthenticated::class)]
|
||||
#[UsesClass(HomeController::class)]
|
||||
class HomeEndpointTest extends EndpointTestAbstract
|
||||
{
|
||||
@@ -40,17 +36,4 @@ class HomeEndpointTest extends EndpointTestAbstract
|
||||
// Assert
|
||||
$response->assertRedirect('/login');
|
||||
}
|
||||
|
||||
public function test_login_redirects_to_dashboard_if_user_is_logged_in(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->withPersonalOrganization()->create();
|
||||
$this->actingAs($user);
|
||||
|
||||
// Act
|
||||
$response = $this->get('/login');
|
||||
|
||||
// Assert
|
||||
$response->assertRedirect('/dashboard');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,20 +47,6 @@ class EnsureEmailIsVerifiedMiddlewareTest extends MiddlewareTestAbstract
|
||||
$response->assertRedirect(route('verification.notice'));
|
||||
}
|
||||
|
||||
public function test_users_with_unverified_email_get_error_if_the_request_is_json(): void
|
||||
{
|
||||
// Arrange
|
||||
$user = User::factory()->unverified()->create();
|
||||
$route = $this->createTestRoute();
|
||||
$this->actingAs($user);
|
||||
|
||||
// Act
|
||||
$response = $this->getJson($route);
|
||||
|
||||
// Assert
|
||||
$response->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_users_with_verified_email_can_access_route(): void
|
||||
{
|
||||
// Arrange
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit\Middleware;
|
||||
|
||||
use App\Http\Middleware\ForceHttps;
|
||||
use Illuminate\Support\Facades\Config;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use PHPUnit\Framework\Attributes\UsesClass;
|
||||
|
||||
#[CoversClass(ForceHttps::class)]
|
||||
#[UsesClass(ForceHttps::class)]
|
||||
class ForceHttpsMiddlewareTest extends MiddlewareTestAbstract
|
||||
{
|
||||
private function createTestRoute(): string
|
||||
{
|
||||
return Route::get('/test-route', function () {
|
||||
return [
|
||||
'is_secure' => request()->secure(),
|
||||
];
|
||||
})->middleware(ForceHttps::class)->uri;
|
||||
}
|
||||
|
||||
public function test_if_config_app_force_https_is_true_then_the_request_will_be_modified_to_make_the_app_think_it_was_a_https_request(): void
|
||||
{
|
||||
// Arrange
|
||||
Config::set('app.force_https', true);
|
||||
$route = $this->createTestRoute();
|
||||
|
||||
// Act
|
||||
$response = $this->get($route);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertJson(['is_secure' => true]);
|
||||
}
|
||||
|
||||
public function test_if_config_app_force_https_is_true_then_the_request_will_be_modified_to_make_the_app_think_it_was_a_https_request_even_if_a_load_balancer_says_it_was_a_http_request(): void
|
||||
{
|
||||
// Arrange
|
||||
Config::set('app.force_https', true);
|
||||
$route = $this->createTestRoute();
|
||||
|
||||
// Act
|
||||
$response = $this->get($route, ['X-Forwarded-Proto' => 'http']);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertJson(['is_secure' => true]);
|
||||
}
|
||||
|
||||
public function test_if_config_app_force_https_is_false_then_the_request_will_not_be_modified_to_make_the_app_think_it_was_a_https_request(): void
|
||||
{
|
||||
// Arrange
|
||||
Config::set('app.force_https', false);
|
||||
$route = $this->createTestRoute();
|
||||
|
||||
// Act
|
||||
$response = $this->get($route);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertJson(['is_secure' => false]);
|
||||
}
|
||||
|
||||
public function test_if_config_app_force_https_is_false_then_the_request_will_not_be_modified_but_the_request_can_still_be_https(): void
|
||||
{
|
||||
// Arrange
|
||||
Config::set('app.force_https', false);
|
||||
$route = $this->createTestRoute();
|
||||
|
||||
// Act
|
||||
$response = $this->get($route, ['X-Forwarded-Proto' => 'https']);
|
||||
|
||||
// Assert
|
||||
$response->assertSuccessful();
|
||||
$response->assertJson(['is_secure' => true]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user