Fixes after merge

This commit is contained in:
Constantin Graf
2024-03-12 18:33:47 +01:00
parent ea98572f98
commit 37cf43fe2d
12 changed files with 205 additions and 15 deletions

17
.env.ci
View File

@@ -1,19 +1,22 @@
APP_NAME=Laravel APP_NAME=solidtime
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_URL=http://localhost APP_URL=http://localhost
APP_FORCE_HTTPS=false
SESSION_SECURE_COOKIE=false
LOG_CHANNEL=stack LOG_CHANNEL=stack
LOG_DEPRECATIONS_CHANNEL=null LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
DB_CONNECTION=pgsql DB_CONNECTION=pgsql_test
DB_HOST=127.0.0.1
DB_PORT=5432 DB_TEST_HOST=127.0.0.1
DB_DATABASE=laravel DB_TEST_PORT=5432
DB_USERNAME=root DB_TEST_DATABASE=laravel
DB_PASSWORD=root DB_TEST_USERNAME=root
DB_TEST_PASSWORD=root
BROADCAST_DRIVER=log BROADCAST_DRIVER=log
CACHE_DRIVER=file CACHE_DRIVER=file

View File

@@ -4,6 +4,7 @@ APP_KEY=base64:UNQNf1SXeASNkWux01Rj8EnHYx8FO0kAxWNDwktclkk=
APP_DEBUG=true APP_DEBUG=true
APP_URL=https://solidtime.test APP_URL=https://solidtime.test
APP_FORCE_HTTPS=true APP_FORCE_HTTPS=true
SESSION_SECURE_COOKIE=true
SUPER_ADMINS=admin@example.com SUPER_ADMINS=admin@example.com
@@ -12,12 +13,19 @@ LOG_DEPRECATIONS_CHANNEL=null
LOG_LEVEL=debug LOG_LEVEL=debug
DB_CONNECTION=pgsql DB_CONNECTION=pgsql
DB_HOST=pgsql DB_HOST=pgsql
DB_PORT=5432 DB_PORT=5432
DB_DATABASE=laravel DB_DATABASE=laravel
DB_USERNAME=root DB_USERNAME=root
DB_PASSWORD=root DB_PASSWORD=root
DB_TEST_HOST=pgsql_test
DB_TEST_PORT=5432
DB_TEST_DATABASE=laravel
DB_TEST_USERNAME=root
DB_TEST_PASSWORD=root
BROADCAST_DRIVER=log BROADCAST_DRIVER=log
CACHE_DRIVER=file CACHE_DRIVER=file
FILESYSTEM_DISK=local FILESYSTEM_DISK=local

View File

@@ -5,7 +5,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
services: services:
pgsql: pgsql_test:
image: postgres:15 image: postgres:15
env: env:
PGPASSWORD: 'root' PGPASSWORD: 'root'

View File

@@ -11,7 +11,7 @@ jobs:
services: services:
mailpit: mailpit:
image: 'axllent/mailpit:latest' image: 'axllent/mailpit:latest'
pgsql: pgsql_test:
image: postgres:15 image: postgres:15
env: env:
PGPASSWORD: 'root' PGPASSWORD: 'root'

View File

@@ -48,7 +48,8 @@ class TimeEntryIndexRequest extends FormRequest
], ],
// Filter only time entries that are active (have no end date, are still running) // Filter only time entries that are active (have no end date, are still running)
'active' => [ 'active' => [
'boolean', 'string',
'in:true,false',
], ],
// Limit the number of returned time entries // Limit the number of returned time entries
'limit' => [ 'limit' => [

View File

@@ -43,11 +43,15 @@ class ImportDatabaseHelper
private int $createdCount; private int $createdCount;
/**
* @var array<string, array<int, string>>
*/
private array $validate; private array $validate;
/** /**
* @param class-string<TModel> $model * @param class-string<TModel> $model
* @param array<string> $identifiers * @param array<string> $identifiers
* @param array<string, array<int, string>> $validate
*/ */
public function __construct(string $model, array $identifiers, bool $attachToExisting = false, ?Closure $queryModifier = null, ?Closure $afterCreate = null, array $validate = []) public function __construct(string $model, array $identifiers, bool $attachToExisting = false, ?Closure $queryModifier = null, ?Closure $afterCreate = null, array $validate = [])
{ {

View File

@@ -80,6 +80,21 @@ return [
'sslmode' => 'prefer', 'sslmode' => 'prefer',
], ],
'pgsql_test' => [
'driver' => 'pgsql',
'url' => env('DATABASE_URL'),
'host' => env('DB_TEST_HOST', '127.0.0.1'),
'port' => env('DB_TEST_PORT', '5432'),
'database' => env('DB_TEST_DATABASE', 'forge'),
'username' => env('DB_TEST_USERNAME', 'forge'),
'password' => env('DB_TEST_PASSWORD', ''),
'charset' => 'utf8',
'prefix' => '',
'prefix_indexes' => true,
'search_path' => 'public',
'sslmode' => 'prefer',
],
'sqlsrv' => [ 'sqlsrv' => [
'driver' => 'sqlsrv', 'driver' => 'sqlsrv',
'url' => env('DATABASE_URL'), 'url' => env('DATABASE_URL'),

View File

@@ -8,6 +8,7 @@ namespace Database\Seeders;
use App\Models\Client; use App\Models\Client;
use App\Models\Organization; use App\Models\Organization;
use App\Models\Project; use App\Models\Project;
use App\Models\Tag;
use App\Models\Task; use App\Models\Task;
use App\Models\TimeEntry; use App\Models\TimeEntry;
use App\Models\User; use App\Models\User;
@@ -109,6 +110,7 @@ class DatabaseSeeder extends Seeder
{ {
DB::table((new TimeEntry())->getTable())->delete(); DB::table((new TimeEntry())->getTable())->delete();
DB::table((new Task())->getTable())->delete(); DB::table((new Task())->getTable())->delete();
DB::table((new Tag())->getTable())->delete();
DB::table((new Project())->getTable())->delete(); DB::table((new Project())->getTable())->delete();
DB::table((new Client())->getTable())->delete(); DB::table((new Client())->getTable())->delete();
DB::table((new User())->getTable())->delete(); DB::table((new User())->getTable())->delete();

View File

@@ -10,6 +10,9 @@ const ClientResource = z
}) })
.passthrough(); .passthrough();
const ClientCollection = z.array(ClientResource); const ClientCollection = z.array(ClientResource);
const v1_import_import_Body = z
.object({ type: z.string(), data: z.string() })
.passthrough();
const OrganizationResource = z const OrganizationResource = z
.object({ id: z.string(), name: z.string(), is_personal: z.string() }) .object({ id: z.string(), name: z.string(), is_personal: z.string() })
.passthrough(); .passthrough();
@@ -72,10 +75,21 @@ const updateTimeEntry_Body = z
tags: z.union([z.array(z.string()), z.null()]).optional(), tags: z.union([z.array(z.string()), z.null()]).optional(),
}) })
.passthrough(); .passthrough();
const UserResource = z
.object({
id: z.string(),
name: z.string(),
email: z.string(),
role: z.string(),
is_placeholder: z.boolean(),
})
.passthrough();
const UserCollection = z.array(UserResource);
export const schemas = { export const schemas = {
ClientResource, ClientResource,
ClientCollection, ClientCollection,
v1_import_import_Body,
OrganizationResource, OrganizationResource,
ProjectResource, ProjectResource,
ProjectCollection, ProjectCollection,
@@ -87,6 +101,8 @@ export const schemas = {
TimeEntryCollection, TimeEntryCollection,
createTimeEntry_Body, createTimeEntry_Body,
updateTimeEntry_Body, updateTimeEntry_Body,
UserResource,
UserCollection,
}; };
const endpoints = makeApi([ const endpoints = makeApi([
@@ -306,6 +322,76 @@ const endpoints = makeApi([
}, },
], ],
}, },
{
method: 'post',
path: '/v1/organizations/:organization/import',
alias: 'v1.import.import',
requestFormat: 'json',
parameters: [
{
name: 'body',
type: 'Body',
schema: v1_import_import_Body,
},
{
name: 'organization',
type: 'Path',
schema: z.string().uuid(),
},
],
response: z
.object({
report: z
.object({
clients: z
.object({ created: z.number().int() })
.passthrough(),
projects: z
.object({ created: z.number().int() })
.passthrough(),
tasks: z
.object({ created: z.number().int() })
.passthrough(),
'time-entries': z
.object({ created: z.number().int() })
.passthrough(),
tags: z
.object({ created: z.number().int() })
.passthrough(),
users: z
.object({ created: z.number().int() })
.passthrough(),
})
.passthrough(),
})
.passthrough(),
errors: [
{
status: 400,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 403,
description: `Authorization error`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 404,
description: `Not found`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 422,
description: `Validation error`,
schema: z
.object({
message: z.string(),
errors: z.record(z.array(z.string())),
})
.passthrough(),
},
],
},
{ {
method: 'get', method: 'get',
path: '/v1/organizations/:organization/projects', path: '/v1/organizations/:organization/projects',
@@ -664,7 +750,7 @@ const endpoints = makeApi([
{ {
name: 'active', name: 'active',
type: 'Query', type: 'Query',
schema: z.string().optional(), schema: z.enum(['true', 'false']).optional(),
}, },
{ {
name: 'limit', name: 'limit',
@@ -824,6 +910,78 @@ const endpoints = makeApi([
}, },
], ],
}, },
{
method: 'get',
path: '/v1/organizations/:organization/users',
alias: 'v1.users.index',
requestFormat: 'json',
parameters: [
{
name: 'organization',
type: 'Path',
schema: z.string().uuid(),
},
],
response: z.object({ data: UserCollection }).passthrough(),
errors: [
{
status: 403,
description: `Authorization error`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 404,
description: `Not found`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 422,
description: `Validation error`,
schema: z
.object({
message: z.string(),
errors: z.record(z.array(z.string())),
})
.passthrough(),
},
],
},
{
method: 'post',
path: '/v1/organizations/:organization/users/:user/invite-placeholder',
alias: 'v1.users.invite-placeholder',
requestFormat: 'json',
parameters: [
{
name: 'body',
type: 'Body',
schema: z.object({}).partial().passthrough(),
},
{
name: 'organization',
type: 'Path',
schema: z.string().uuid(),
},
{
name: 'user',
type: 'Path',
schema: z.string().uuid(),
},
],
response: z.string(),
errors: [
{
status: 403,
description: `Authorization error`,
schema: z.object({ message: z.string() }).passthrough(),
},
{
status: 404,
description: `Not found`,
schema: z.object({ message: z.string() }).passthrough(),
},
],
},
]); ]);
export const api = new Zodios('http://solidtime.test/api', endpoints); export const api = new Zodios('http://solidtime.test/api', endpoints);

View File

@@ -21,8 +21,7 @@
<env name="APP_ENV" value="testing"/> <env name="APP_ENV" value="testing"/>
<env name="BCRYPT_ROUNDS" value="4"/> <env name="BCRYPT_ROUNDS" value="4"/>
<env name="CACHE_DRIVER" value="array"/> <env name="CACHE_DRIVER" value="array"/>
<env name="DB_CONNECTION" value="pgsql"/> <env name="DB_CONNECTION" value="pgsql_test"/>
<env name="DB_HOST" value="pgsql_test"/>
<env name="MAIL_MAILER" value="array"/> <env name="MAIL_MAILER" value="array"/>
<env name="PULSE_ENABLED" value="false"/> <env name="PULSE_ENABLED" value="false"/>
<env name="QUEUE_CONNECTION" value="sync"/> <env name="QUEUE_CONNECTION" value="sync"/>

View File

@@ -20,7 +20,7 @@ export default defineConfig({
/* Opt out of parallel tests on CI. */ /* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined, workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html', reporter: process.env.CI ? 'list' : 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: { use: {
/* Base URL to use in actions like `await page.goto('/')`. */ /* Base URL to use in actions like `await page.goto('/')`. */

View File

@@ -143,7 +143,7 @@ class TimeEntryEndpointTest extends ApiEndpointTestAbstract
// Act // Act
$response = $this->getJson(route('api.v1.time-entries.index', [ $response = $this->getJson(route('api.v1.time-entries.index', [
$data->organization->getKey(), $data->organization->getKey(),
'active' => true, 'active' => 'true',
'user_id' => $data->user->getKey(), 'user_id' => $data->user->getKey(),
])); ]));