Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions app/Http/Controllers/ApplicationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ private function renderPublicProject(
$mergedProps = $props;

if ($reviewerPreview && $project) {
PublicEntityAccess::rememberReviewerPreview($request, $project);

$mergedProps['reviewerPreview'] = [
'obfuscationcode' => $project->obfuscationcode,
'samples_count' => $project->studies()->count(),
Expand Down
2 changes: 1 addition & 1 deletion app/Http/Controllers/UserPreferencesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ public function update(UpdateUserPreferencesRequest $request): RedirectResponse
'preferences' => $preferences === [] ? null : $preferences,
])->save();

return back();
return to_route('profile.show');
}
}
13 changes: 13 additions & 0 deletions app/Jobs/DataBackupJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,19 @@ class DataBackupJob implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;

/**
* Number of times the job may be attempted.
*/
public int $tries = 3;

/**
* Delay (in seconds) between retry attempts, to ride out transient
* pg_dump failures caused by concurrent schema changes (e.g. deployments).
*
* @var array<int, int>
*/
public array $backoff = [60, 300];

/**
* Execute the job.
*/
Expand Down
68 changes: 68 additions & 0 deletions app/Support/Public/PublicEntityAccess.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace App\Support\Public;

use App\Models\Dataset;
use App\Models\Project;
use App\Models\Study;
use App\Models\User;
use Illuminate\Auth\Access\AuthorizationException;
Expand All @@ -11,12 +12,19 @@

class PublicEntityAccess
{
public const REVIEWER_PREVIEW_SESSION_KEY = 'reviewer_preview_obfuscationcode';

public static function authorizeStudyAccess(Request $request, Study $study, bool $reviewerPreview = false): void
{
if ($reviewerPreview) {
return;
}

if (self::requestIncludesReviewerObfuscation($request)
&& self::hasValidReviewerObfuscation($request, $study->project)) {
return;
}

if (! Gate::forUser($request->user())->check('viewStudy', $study)) {
throw new AuthorizationException;
}
Expand All @@ -32,6 +40,13 @@ public static function authorizeDatasetAccess(Request $request, Dataset $dataset
? $dataset->study
: $dataset->study()->first();

if (self::requestIncludesReviewerObfuscation($request)) {
$project = $study?->project ?? $dataset->project;
if (self::hasValidReviewerObfuscation($request, $project)) {
return;
}
}

if ($study === null) {
throw new AuthorizationException;
}
Expand All @@ -50,4 +65,57 @@ public static function authorizeDatasetAccess(Request $request, Dataset $dataset

throw new AuthorizationException;
}

public static function rememberReviewerPreview(Request $request, Project $project): void
{
if (! $request->hasSession() || $project->is_archived) {
return;
}

$code = $project->obfuscationcode;
if (! is_string($code) || $code === '') {
return;
}

$request->session()->put(self::REVIEWER_PREVIEW_SESSION_KEY, $code);
}

protected static function requestIncludesReviewerObfuscation(Request $request): bool
{
return $request->filled('obfuscationcode')
|| self::reviewerObfuscationFromSession($request) !== null;
}

protected static function hasValidReviewerObfuscation(Request $request, ?Project $project): bool
{
if ($project === null || $project->is_archived) {
return false;
}

$expected = $project->obfuscationcode;
if (! is_string($expected) || $expected === '') {
return false;
}

$provided = $request->filled('obfuscationcode')
? (string) $request->query('obfuscationcode')
: self::reviewerObfuscationFromSession($request);

if (! is_string($provided) || $provided === '') {
return false;
}

return hash_equals($expected, $provided);
}

protected static function reviewerObfuscationFromSession(Request $request): ?string
{
if (! $request->hasSession()) {
return null;
}

$code = $request->session()->get(self::REVIEWER_PREVIEW_SESSION_KEY);

return is_string($code) && $code !== '' ? $code : null;
}
}
3 changes: 2 additions & 1 deletion config/database.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
'dump' => [
'use_single_transaction',
'timeout' => 620 * 100, // 51 minute timeout
'exclude_tables' => ['versions'],
// mols/fps are ephemeral RDKit search caches dropped & rebuilt daily by nmrxiv:index-molecules, excluded to avoid pg_dump racing that DDL
'exclude_tables' => ['versions', 'mols', 'fps'],
],
'search_path' => 'public',
],
Expand Down
3 changes: 3 additions & 0 deletions deployment/docker-compose.dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ services:
entrypoint: /bin/sh
stdin_open: true
tty: true
environment:
NODE_OPTIONS: "--max-old-space-size=12288"
mem_limit: 16g
networks:
- nmrxiv_net
volumes:
Expand Down
12 changes: 7 additions & 5 deletions resources/js/Pages/Public/Project/Dataset.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,13 @@ export default {
};
},
mounted() {
axios
.get(route("bioschemas.id", this.dataset.data.identifier))
.then((response) => {
this.schema = response.data;
});
if (this.dataset?.data?.identifier) {
axios
.get(route("bioschemas.id", this.dataset.data.identifier))
.then((response) => {
this.schema = response.data;
});
}
},
};
</script>
12 changes: 7 additions & 5 deletions resources/js/Pages/Public/Project/Show.vue
Original file line number Diff line number Diff line change
Expand Up @@ -628,11 +628,13 @@ export default {
},

mounted() {
axios
.get(route("bioschemas.id", this.project.data.identifier))
.then((response) => {
this.schema = response.data;
});
if (this.project?.data?.identifier) {
axios
.get(route("bioschemas.id", this.project.data.identifier))
.then((response) => {
this.schema = response.data;
});
}

this.handleEditQueryParam();
},
Expand Down
12 changes: 7 additions & 5 deletions resources/js/Pages/Public/Sample/Dataset.vue
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,13 @@ export default {
};
},
mounted() {
axios
.get(route("bioschemas.id", this.dataset.data.identifier))
.then((response) => {
this.schema = response.data;
});
if (this.dataset?.data?.identifier) {
axios
.get(route("bioschemas.id", this.dataset.data.identifier))
.then((response) => {
this.schema = response.data;
});
}
},
};
</script>
12 changes: 7 additions & 5 deletions resources/js/Pages/Public/Sample/Show.vue
Original file line number Diff line number Diff line change
Expand Up @@ -723,11 +723,13 @@ export default {
},
},
mounted() {
axios
.get(route("bioschemas.id", this.study.data.identifier))
.then((response) => {
this.schema = response.data;
});
if (this.study?.data?.identifier) {
axios
.get(route("bioschemas.id", this.study.data.identifier))
.then((response) => {
this.schema = response.data;
});
}
},
methods: {
datasetHref(dataset) {
Expand Down
84 changes: 50 additions & 34 deletions resources/js/Shared/SpectraViewer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,9 @@ export default {
url() {
return String(this.$page.props.url);
},
reviewerObfuscationCode() {
return this.$page.props.reviewerPreview?.obfuscationcode ?? null;
},
nmriumURL() {
const raw = this.$page.props.nmriumURL;
if (!raw) {
Expand Down Expand Up @@ -457,6 +460,45 @@ export default {
getDefaultSpectrumTab(this.$page)
);
},
nmriumInfoUrl(path) {
if (!this.reviewerObfuscationCode) {
return path;
}

const separator = path.includes("?") ? "&" : "?";

return (
path +
separator +
"obfuscationcode=" +
encodeURIComponent(this.reviewerObfuscationCode)
);
},
loadNmriumInfo(iframe, path) {
this.infoLog("Loading Spectra from NMRium JSON..");
axios
.get(this.nmriumInfoUrl(path))
.then((response) => {
let nmrium_info = response.data;
if (nmrium_info) {
this.postLoadToIframe(iframe, {
data: nmrium_info,
type: "nmrium",
});
} else if (this.study.download_url) {
this.loadFromURL([this.study.download_url]);
} else {
this.updateLoadingStatus(false);
}
})
.catch((error) => {
this.updateLoadingStatus(false);
this.spectraError =
error?.response?.status === 403
? "You do not have permission to view these spectra."
: "Unable to load spectra.";
});
},
loadSpectra() {
if (this.study) {
const iframe = window.frames.NMRiumIframe;
Expand All @@ -467,42 +509,16 @@ export default {

if (iframe) {
if (this.dataset && this.dataset.has_nmrium) {
this.infoLog("Loading Spectra from NMRium JSON..");
axios
.get("/datasets/" + this.dataset.id + "/nmriumInfo")
.then((response) => {
let nmrium_info = response.data;
if (nmrium_info) {
this.postLoadToIframe(iframe, {
data: nmrium_info,
type: "nmrium",
});
} else {
let urls = [];
urls.push(this.study.download_url);
this.loadFromURL(urls);
}
});
this.loadNmriumInfo(
iframe,
"/datasets/" + this.dataset.id + "/nmriumInfo"
);
} else {
if (this.study.has_nmrium) {
this.infoLog("Loading Spectra from NMRium JSON..");
axios
.get(
"/studies/" + this.study.id + "/nmriumInfo"
)
.then((response) => {
let nmrium_info = response.data;
if (nmrium_info) {
this.postLoadToIframe(iframe, {
data: nmrium_info,
type: "nmrium",
});
} else {
let urls = [];
urls.push(this.study.download_url);
this.loadFromURL(urls);
}
});
this.loadNmriumInfo(
iframe,
"/studies/" + this.study.id + "/nmriumInfo"
);
} else {
if (this.study.download_url) {
let urls = [];
Expand Down
5 changes: 3 additions & 2 deletions routes/console.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@

Schedule::command('nmrxiv:publish-embargo-projects')->daily();
Schedule::command('nmrxiv:delete-projects')->daily();
Schedule::command('nmrxiv:index-molecules')->daily();
// Staggered away from the backup dump (both defaulted to ->daily(), i.e. midnight) since this rebuilds the mols/fps RDKit tables via DROP/CREATE DDL
Schedule::command('nmrxiv:index-molecules')->dailyAt('02:00');
Schedule::command('nmrxiv:index-spectra-metadata-stats')->daily();
Schedule::command('nmrxiv:delete-citations')->weekly();
Schedule::command('nmrxiv:delete-authors')->weekly();
if (App::environment('production')) {
Schedule::command('nmrxiv:backup-postgres-dump')
->daily()
->dailyAt('04:00')
->onOneServer()
->withoutOverlapping();
}
Expand Down
Loading
Loading