Skip to content

Commit 65e56f9

Browse files
authored
Merge pull request #1211 from NFDI4Chem/development
Development
2 parents 37814ce + f93b359 commit 65e56f9

File tree

74 files changed

+10313
-4709
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+10313
-4709
lines changed

.github/workflows/dev-build.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
push: true
5050
# Pass proxy settings and release version as build arguments
5151
build-args: |
52-
RELEASE_VERSION=fsu-app
52+
RELEASE_VERSION=app-dev-latest
5353
# Tag the image for development environment
5454
tags: ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:app-dev-latest
5555
# Enable build cache for faster builds
@@ -65,7 +65,7 @@ jobs:
6565
push: true
6666
# Pass proxy settings and release version as build arguments
6767
build-args: |
68-
RELEASE_VERSION=fsu-worker
68+
RELEASE_VERSION=worker-dev-latest
6969
# Tag the worker image for development environment
7070
tags: ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:worker-dev-latest
7171
# Enable build cache for faster builds

.github/workflows/prod-build.yml

Lines changed: 61 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,126 +1,79 @@
1-
# GitHub Actions workflow for building and deploying NMRXiv to production
2-
# Triggered when a new release is published
3-
name : Setup, Build and Publish to Production
1+
# GitHub Actions workflow for building and deploying NMRXiv to production environment
2+
# This workflow runs tests, builds Docker images, and deploys to the FSU production environment
3+
name: Setup, Build and Publish to Prod
44

5-
# Workflow triggers
5+
# Trigger the workflow on pushes to the production branch
66
on:
77
push:
88
branches: [production]
99

10-
# Environment variables used throughout the workflow
10+
# Environment variables used across all jobs
1111
env:
12-
DOCKER_HUB_USERNAME : ${{ secrets.DOCKER_HUB_USERNAME }}
13-
DOCKER_HUB_PASSWORD : ${{ secrets.DOCKER_HUB_PASSWORD }}
12+
DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }}
13+
DOCKER_HUB_PASSWORD: ${{ secrets.DOCKER_HUB_PASSWORD }}
1414
REPOSITORY_NAME: nmrxiv
1515
REPOSITORY_NAMESPACE: nfdi4chem
1616

1717
jobs:
18-
# Job 1: Run PHP unit tests with PostgreSQL database
19-
php-unit-test:
20-
name: Run tests
21-
runs-on: ubuntu-latest
22-
23-
# Set up PostgreSQL service for testing
24-
services:
25-
postgres:
26-
image: postgres:13
27-
env:
28-
POSTGRES_USER: postgres
29-
POSTGRES_PASSWORD: postgres
30-
POSTGRES_DB: nmrxiv
31-
ports:
32-
- 5432:5432
33-
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
34-
35-
steps:
36-
# Check out the repository code
37-
- uses: actions/checkout@v4
38-
39-
# Set up PHP 8.2 with required extensions
40-
- name: Setup PHP
41-
uses: shivammathur/setup-php@v2
42-
with:
43-
php-version: '8.2'
44-
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, pdo_pgsql, bcmath, soap, intl, gd, exif, iconv
45-
coverage: pcov
46-
47-
# Install PHP dependencies via Composer
48-
- name: Install composer dependencies
49-
run: composer install --ignore-platform-reqs
50-
51-
# Configure Laravel application for testing
52-
- name: Prepare Laravel Application
53-
run: |
54-
php -r "file_exists('.env') || copy('.env.ci.test', '.env');"
55-
echo AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID_DEV }} >> .env
56-
echo AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY_DEV }} >> .env
57-
echo MEILISEARCH_KEY=${{ secrets.MEILISEARCH_KEY_DEV }} >> .env
58-
echo MEILISEARCH_PUBLICKEY=${{ secrets.MEILISEARCH_PUBLICKEY_DEV }} >> .env
59-
echo TWITTER_CLIENT_ID=${{ secrets.TWITTER_CLIENT_ID_DEV }} >> .env
60-
echo TWITTER_CLIENT_SECRET=${{ secrets.TWITTER_CLIENT_SECRET_DEV }} >> .env
61-
echo GITHUB_CLIENT_ID=${{ secrets.CLIENT_ID_GITHUB_DEV }} >> .env
62-
echo GITHUB_CLIENT_SECRET=${{ secrets.CLIENT_SECRET_GITHUB_DEV }} >> .env
63-
php artisan key:generate
64-
php artisan migrate --seed
65-
66-
# Install and build front-end assets
67-
- name: Install front-end dependencies
68-
run: |
69-
npm install
70-
npm run build
71-
72-
# Execute PHPUnit tests with coverage collection
73-
- name: Run tests and collect coverage
74-
run: vendor/bin/phpunit --coverage-clover coverage.xml
75-
76-
# Upload test coverage results to Codecov
77-
- name: Upload coverage to Codecov
78-
uses: codecov/codecov-action@v3
79-
env:
80-
CODECOV_TOKEN: ${{ secrets.CODECOV_ORG_TOKEN }}
81-
82-
# Job 2: Build Docker images and deploy to production
18+
# Build Docker images and deploy to production environment
8319
setup-build-publish-deploy:
8420
name: Build & deploy to production
8521
runs-on: ubuntu-latest
86-
needs: php-unit-test # Wait for tests to pass before deploying
22+
# Only run this job if tests pass successfully
23+
# needs: php-unit-test
24+
# Use the Dev environment for deployment secrets and protection rules
8725
environment:
88-
name: Production
26+
name: Prod
8927
steps:
90-
# Check out the repository code
91-
- name: Checkout
92-
uses: actions/checkout@v4
28+
# Checkout the repository code for building Docker images
29+
- name: Checkout
30+
uses: actions/checkout@v4
31+
32+
# Authenticate with Docker Hub for pushing images
33+
- name: Log in to Docker Hub
34+
uses: docker/login-action@v3
35+
with:
36+
username: ${{ env.DOCKER_HUB_USERNAME }}
37+
password: ${{ env.DOCKER_HUB_PASSWORD }}
38+
39+
# Set up Docker Buildx for multi-platform builds
40+
- name: Set up Docker Buildx
41+
uses: docker/setup-buildx-action@v3
9342

94-
# Authenticate with Docker Hub registry
95-
- name: Log in to Docker Hub
96-
uses: docker/login-action@v3
97-
with:
98-
username: ${{ env.DOCKER_HUB_USERNAME }}
99-
password: ${{ env.DOCKER_HUB_PASSWORD }}
43+
# Build and push the main application Docker image for FSU deployment
44+
- name: Build and push App Docker image
45+
uses: docker/build-push-action@v6
46+
with:
47+
context: .
48+
file: ./deployment/Dockerfile
49+
push: true
50+
# Pass proxy settings and release version as build arguments
51+
build-args: |
52+
RELEASE_VERSION=app-latest
53+
# Tag the image for production environment
54+
tags: ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:app-latest
55+
# Enable build cache for faster builds
56+
cache-from: type=gha
57+
cache-to: type=gha,mode=max
10058

101-
# Build and push app Docker image for general public deployment
102-
- name: Build and push App Docker image
103-
uses: docker/build-push-action@v5
104-
with:
105-
context: .
106-
file: ./deployment/Dockerfile
107-
push: true
108-
build-args: |
109-
RELEASE_VERSION=app-latest
110-
tags: |
111-
${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:app-latest
112-
${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:app-${{ github.event.release.tag_name }}
59+
# Build and push the worker Docker image for background job processing
60+
- name: Build and push Worker Docker image
61+
uses: docker/build-push-action@v6
62+
with:
63+
context: .
64+
file: ./deployment/Dockerfile.worker
65+
push: true
66+
# Pass proxy settings and release version as build arguments
67+
build-args: |
68+
RELEASE_VERSION=worker-latest
69+
# Tag the worker image for production environment
70+
tags: ${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:worker-latest
71+
# Enable build cache for faster builds
72+
cache-from: type=gha
73+
cache-to: type=gha,mode=max
11374

114-
# Build and push worker Docker image for general public deployment
115-
- name: Build and push Worker Docker image
116-
uses: docker/build-push-action@v5
117-
with:
118-
context: .
119-
file: ./deployment/Dockerfile.worker
120-
push: true
121-
build-args: |
122-
RELEASE_VERSION=worker-latest
123-
# Fixed: Combined duplicate tags into a single tags block with pipe syntax
124-
tags: |
125-
${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:worker-latest
126-
${{ env.REPOSITORY_NAMESPACE }}/${{ env.REPOSITORY_NAME }}:worker-${{ github.event.release.tag_name }}
75+
# Optional: Add deployment step if needed
76+
# - name: Deploy to production environment
77+
# run: |
78+
# echo "Add your deployment commands here"
79+
# # Example: kubectl apply -f k8s/ or docker-compose up -d

.phpunit.cache/test-results

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"version":1,"defects":{"Tests\\Feature\\ApiTokenPermissionsTest::test_api_token_permissions_can_be_updated":8,"Tests\\Feature\\AuthenticationTest::test_login_screen_can_be_rendered":8,"Tests\\Feature\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":8,"Tests\\Feature\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":8,"Tests\\Feature\\BrowserSessionsTest::test_other_browser_sessions_can_be_logged_out":8,"Tests\\Feature\\CreateApiTokenTest::test_api_tokens_can_be_created":8,"Tests\\Feature\\CreateTeamTest::test_teams_can_be_created":8,"Tests\\Feature\\DeleteAccountTest::test_user_accounts_can_be_deleted":1,"Tests\\Feature\\DeleteAccountTest::test_correct_password_must_be_provided_before_account_can_be_deleted":1,"Tests\\Feature\\DeleteApiTokenTest::test_api_tokens_can_be_deleted":8,"Tests\\Feature\\DeleteTeamTest::test_teams_can_be_deleted":8,"Tests\\Feature\\DeleteTeamTest::test_personal_teams_cant_be_deleted":8,"Tests\\Feature\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":8,"Tests\\Feature\\EmailVerificationTest::test_email_can_be_verified":8,"Tests\\Feature\\EmailVerificationTest::test_email_can_not_verified_with_invalid_hash":8,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":7,"Tests\\Feature\\InviteTeamMemberTest::test_team_members_can_be_invited_to_team":8,"Tests\\Feature\\InviteTeamMemberTest::test_team_member_invitations_can_be_cancelled":8,"Tests\\Feature\\LeaveTeamTest::test_users_can_leave_teams":8,"Tests\\Feature\\LeaveTeamTest::test_team_owners_cant_leave_their_own_team":8,"Tests\\Feature\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":8,"Tests\\Feature\\PasswordConfirmationTest::test_password_can_be_confirmed":8,"Tests\\Feature\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":8,"Tests\\Feature\\PasswordResetTest::test_reset_password_link_screen_can_be_rendered":8,"Tests\\Feature\\PasswordResetTest::test_reset_password_link_can_be_requested":8,"Tests\\Feature\\PasswordResetTest::test_reset_password_screen_can_be_rendered":8,"Tests\\Feature\\PasswordResetTest::test_password_can_be_reset_with_valid_token":8,"Tests\\Feature\\ProfileInformationTest::test_profile_information_can_be_updated":8,"Tests\\Feature\\RegistrationTest::test_registration_screen_can_be_rendered":8,"Tests\\Feature\\RegistrationTest::test_registration_screen_cannot_be_rendered_if_support_is_disabled":1,"Tests\\Feature\\RegistrationTest::test_new_users_can_register":8,"Tests\\Feature\\RemoveTeamMemberTest::test_team_members_can_be_removed_from_teams":8,"Tests\\Feature\\RemoveTeamMemberTest::test_only_team_owner_can_remove_team_members":8,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_two_factor_authentication_can_be_enabled":8,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_recovery_codes_can_be_regenerated":8,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_two_factor_authentication_can_be_disabled":8,"Tests\\Feature\\UpdatePasswordTest::test_password_can_be_updated":8,"Tests\\Feature\\UpdatePasswordTest::test_current_password_must_be_correct":8,"Tests\\Feature\\UpdatePasswordTest::test_new_passwords_must_match":8,"Tests\\Feature\\UpdateTeamMemberRoleTest::test_team_member_roles_can_be_updated":8,"Tests\\Feature\\UpdateTeamMemberRoleTest::test_only_team_owner_can_update_team_member_roles":8,"Tests\\Feature\\UpdateTeamNameTest::test_team_names_can_be_updated":8},"times":{"Tests\\Unit\\ExampleTest::test_example":0.002,"Tests\\Feature\\ExampleTest::test_the_application_returns_a_successful_response":0.012,"Tests\\Feature\\ApiTokenPermissionsTest::test_api_token_permissions_can_be_updated":0.043,"Tests\\Feature\\AuthenticationTest::test_login_screen_can_be_rendered":0.033,"Tests\\Feature\\AuthenticationTest::test_users_can_authenticate_using_the_login_screen":0.054,"Tests\\Feature\\AuthenticationTest::test_users_can_not_authenticate_with_invalid_password":0.05,"Tests\\Feature\\BrowserSessionsTest::test_other_browser_sessions_can_be_logged_out":0.09,"Tests\\Feature\\CreateApiTokenTest::test_api_tokens_can_be_created":0.009,"Tests\\Feature\\CreateTeamTest::test_teams_can_be_created":0.009,"Tests\\Feature\\DeleteAccountTest::test_user_accounts_can_be_deleted":0.005,"Tests\\Feature\\DeleteAccountTest::test_correct_password_must_be_provided_before_account_can_be_deleted":0,"Tests\\Feature\\DeleteApiTokenTest::test_api_tokens_can_be_deleted":0.006,"Tests\\Feature\\DeleteTeamTest::test_teams_can_be_deleted":0.057,"Tests\\Feature\\DeleteTeamTest::test_personal_teams_cant_be_deleted":0.212,"Tests\\Feature\\EmailVerificationTest::test_email_verification_screen_can_be_rendered":0.018,"Tests\\Feature\\EmailVerificationTest::test_email_can_be_verified":0.006,"Tests\\Feature\\EmailVerificationTest::test_email_can_not_verified_with_invalid_hash":0.008,"Tests\\Feature\\InviteTeamMemberTest::test_team_members_can_be_invited_to_team":0.018,"Tests\\Feature\\InviteTeamMemberTest::test_team_member_invitations_can_be_cancelled":0.007,"Tests\\Feature\\LeaveTeamTest::test_users_can_leave_teams":0.011,"Tests\\Feature\\LeaveTeamTest::test_team_owners_cant_leave_their_own_team":0.007,"Tests\\Feature\\PasswordConfirmationTest::test_confirm_password_screen_can_be_rendered":0.011,"Tests\\Feature\\PasswordConfirmationTest::test_password_can_be_confirmed":0.047,"Tests\\Feature\\PasswordConfirmationTest::test_password_is_not_confirmed_with_invalid_password":0.208,"Tests\\Feature\\PasswordResetTest::test_reset_password_link_screen_can_be_rendered":0.01,"Tests\\Feature\\PasswordResetTest::test_reset_password_link_can_be_requested":0.214,"Tests\\Feature\\PasswordResetTest::test_reset_password_screen_can_be_rendered":0.227,"Tests\\Feature\\PasswordResetTest::test_password_can_be_reset_with_valid_token":0.23,"Tests\\Feature\\ProfileInformationTest::test_profile_information_can_be_updated":0.078,"Tests\\Feature\\RegistrationTest::test_registration_screen_can_be_rendered":0.006,"Tests\\Feature\\RegistrationTest::test_registration_screen_cannot_be_rendered_if_support_is_disabled":0,"Tests\\Feature\\RegistrationTest::test_new_users_can_register":0.014,"Tests\\Feature\\RemoveTeamMemberTest::test_team_members_can_be_removed_from_teams":0.009,"Tests\\Feature\\RemoveTeamMemberTest::test_only_team_owner_can_remove_team_members":0.009,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_two_factor_authentication_can_be_enabled":0.009,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_recovery_codes_can_be_regenerated":0.008,"Tests\\Feature\\TwoFactorAuthenticationSettingsTest::test_two_factor_authentication_can_be_disabled":0.007,"Tests\\Feature\\UpdatePasswordTest::test_password_can_be_updated":0.049,"Tests\\Feature\\UpdatePasswordTest::test_current_password_must_be_correct":0.089,"Tests\\Feature\\UpdatePasswordTest::test_new_passwords_must_match":0.088,"Tests\\Feature\\UpdateTeamMemberRoleTest::test_team_member_roles_can_be_updated":0.011,"Tests\\Feature\\UpdateTeamMemberRoleTest::test_only_team_owner_can_update_team_member_roles":0.189,"Tests\\Feature\\UpdateTeamNameTest::test_team_names_can_be_updated":0.008}}

app/Actions/Draft/CreateDraft.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<?php
2+
3+
namespace App\Actions\Draft;
4+
5+
use App\Models\Draft;
6+
use App\Models\User;
7+
use Illuminate\Support\Str;
8+
9+
class CreateDraft
10+
{
11+
/**
12+
* Create a new draft for the user.
13+
*/
14+
public function execute(User $user): Draft
15+
{
16+
[$user_id, $team_id] = $user->getUserTeamData();
17+
18+
$id = Str::uuid();
19+
$environment = env('APP_ENV', 'local');
20+
21+
$path = $this->generateDraftPath($environment, $user_id, $id);
22+
$name = $this->generateDraftName($id);
23+
24+
return Draft::create([
25+
'name' => $name,
26+
'slug' => Str::slug($name),
27+
'description' => '',
28+
'relative_url' => $this->generateRelativeUrl($id),
29+
'path' => $path,
30+
'owner_id' => $user_id,
31+
'team_id' => $team_id ?: null,
32+
'key' => $id,
33+
]);
34+
}
35+
36+
/**
37+
* Generate draft file path.
38+
*/
39+
private function generateDraftPath(string $environment, int $user_id, string $id): string
40+
{
41+
return preg_replace(
42+
'~//+~',
43+
'/',
44+
$environment.'/'.$user_id.'/drafts/'.$id
45+
);
46+
}
47+
48+
/**
49+
* Generate draft name from UUID.
50+
*/
51+
private function generateDraftName(string $id): string
52+
{
53+
return 'Untitled Project (Draft: '.explode('-', $id)[0].')';
54+
}
55+
56+
/**
57+
* Generate relative URL for draft.
58+
*/
59+
private function generateRelativeUrl(string $id): string
60+
{
61+
return rtrim(
62+
preg_replace('~//+~', '/', '/'.$id),
63+
'/'
64+
);
65+
}
66+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
3+
namespace App\Actions\Draft;
4+
5+
use App\Models\Draft;
6+
use App\Models\FileSystemObject;
7+
use App\Models\Sample;
8+
use App\Models\Study;
9+
use App\Models\User;
10+
use App\Models\Validation;
11+
use Illuminate\Support\Facades\DB;
12+
use Illuminate\Support\Facades\Validator;
13+
use Illuminate\Support\Str;
14+
15+
class CreateDraftStudy
16+
{
17+
/**
18+
* Create a study for draft processing.
19+
*/
20+
public function create(array $input, Draft $draft, ?FileSystemObject $folder = null, ?Validation $validation = null): Study
21+
{
22+
$this->validateInput($input);
23+
24+
return DB::transaction(function () use ($input, $draft, $folder, $validation) {
25+
$study = Study::create([
26+
'name' => $input['name'],
27+
'slug' => Str::slug($input['name'], '-'),
28+
'description' => $input['description'] ?? '',
29+
'color' => $input['color'] ?? null,
30+
'starred' => $input['starred'] ?? null,
31+
'location' => $input['location'] ?? null,
32+
'obfuscationcode' => Str::random(40),
33+
'type' => $input['type'] ?? null,
34+
'uuid' => Str::uuid(),
35+
'access' => $input['access'] ?? 'restricted',
36+
'access_type' => $input['access_type'] ?? 'viewer',
37+
'team_id' => $input['team_id'],
38+
'project_id' => $input['project_id'],
39+
'owner_id' => $input['owner_id'],
40+
'is_public' => $input['is_public'] ?? false,
41+
'license_id' => $input['license_id'] ?? null,
42+
'study_photo_path' => $input['study_photo_path'] ?? null,
43+
// Draft-specific fields
44+
'draft_id' => $draft->id,
45+
'fs_id' => $folder?->id,
46+
]);
47+
48+
// Associate validation if provided
49+
if ($validation) {
50+
$study->validation()->associate($validation);
51+
$study->save();
52+
}
53+
54+
// Create sample for the study
55+
$this->createStudySample($study);
56+
57+
// Attach user with creator role
58+
$user = User::find($input['owner_id']);
59+
if ($user) {
60+
$study->users()->attach($user, ['role' => 'creator']);
61+
}
62+
63+
return $study;
64+
});
65+
}
66+
67+
/**
68+
* Validate the input for creating a draft study.
69+
*/
70+
private function validateInput(array $input): void
71+
{
72+
Validator::make($input, [
73+
'name' => ['required', 'string', 'max:255'],
74+
'project_id' => ['required'],
75+
'owner_id' => ['required'],
76+
])->validate();
77+
}
78+
79+
/**
80+
* Create sample for study.
81+
*/
82+
private function createStudySample(Study $study): void
83+
{
84+
$sample = Sample::create([
85+
'name' => $study->name.'_sample',
86+
'slug' => Str::slug($study->name.'_sample', '-'),
87+
'study_id' => $study->id,
88+
'project_id' => $study->project->id,
89+
]);
90+
$study->sample()->save($sample);
91+
}
92+
}

0 commit comments

Comments
 (0)