Fix file submissions preview

This commit is contained in:
Julien Nahum 2024-01-13 19:57:39 +01:00
parent bf98497711
commit 91432c4aed
12 changed files with 73 additions and 69 deletions

View File

@ -48,7 +48,7 @@ class Handler extends ExceptionHandler
{ {
return $request->expectsJson() return $request->expectsJson()
? response()->json(['message' => $exception->getMessage()], 401) ? response()->json(['message' => $exception->getMessage()], 401)
: redirect()->guest(url('/login')); : redirect(front_url('login'));
} }
public function report(Throwable $exception) public function report(Throwable $exception)

View File

@ -15,7 +15,8 @@ class FormSubmissionController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth', ['except' => ['submissionFile']]);
$this->middleware('signed', ['only' => ['submissionFile']]);
} }
public function submissions(string $id) public function submissions(string $id)
@ -51,9 +52,6 @@ class FormSubmissionController extends Controller
public function submissionFile($id, $fileName) public function submissionFile($id, $fileName)
{ {
$form = Form::findOrFail((int) $id);
$this->authorize('view', $form);
$fileName = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $id).'/' $fileName = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $id).'/'
.urldecode($fileName); .urldecode($fileName);
@ -63,8 +61,12 @@ class FormSubmissionController extends Controller
], 404); ], 404);
} }
if (config('filesystems.default') !== 's3') {
return response()->file(Storage::path($fileName));
}
return redirect( return redirect(
Storage::temporaryUrl($fileName, now()->addMinute()) Storage::temporaryUrl($fileName, now()->addMinute())
); );
} }
} }

View File

@ -15,7 +15,7 @@ class Authenticate extends Middleware
protected function redirectTo($request) protected function redirectTo($request)
{ {
if (! $request->expectsJson()) { if (! $request->expectsJson()) {
return redirect('/login'); return redirect(front_url('login'));
} }
} }
} }

View File

@ -50,7 +50,11 @@ class FormSubmissionResource extends JsonResource
return $file !== null && $file; return $file !== null && $file;
})->map(function ($file) { })->map(function ($file) {
return [ return [
'file_url' => route('open.forms.submissions.file', [$this->form_id, $file]), 'file_url' => \URL::signedRoute(
'open.forms.submissions.file',
[$this->form_id, $file],
now()->addMinutes(10)
),
'file_name' => $file, 'file_name' => $file,
]; ];
}); });

View File

@ -117,6 +117,7 @@ import VButton from '~/components/global/VButton.vue'
import FormCleanings from '../../pages/forms/show/FormCleanings.vue' import FormCleanings from '../../pages/forms/show/FormCleanings.vue'
import VTransition from '~/components/global/transitions/VTransition.vue' import VTransition from '~/components/global/transitions/VTransition.vue'
import {pendingSubmission} from "~/composables/forms/pendingSubmission.js"; import {pendingSubmission} from "~/composables/forms/pendingSubmission.js";
import clonedeep from "clone-deep";
export default { export default {
components: { VTransition, VButton, OpenFormButton, OpenForm, FormCleanings }, components: { VTransition, VButton, OpenFormButton, OpenForm, FormCleanings },
@ -176,29 +177,24 @@ export default {
this.loading = true this.loading = true
// this.closeAlert() // this.closeAlert()
form.post('/forms/' + this.form.slug + '/answer').then((data) => { form.post('/forms/' + this.form.slug + '/answer').then((data) => {
this.$logEvent('form_submission', { useAmplitude().logEvent('form_submission', {
workspace_id: this.form.workspace_id, workspace_id: this.form.workspace_id,
form_id: this.form.id form_id: this.form.id
}) })
if (this.isIframe) { const payload = clonedeep({
window.parent.postMessage({
type: 'form-submitted',
form: {
slug: this.form.slug,
id: this.form.id
},
submission_data: form.data()
}, '*')
}
window.postMessage({
type: 'form-submitted', type: 'form-submitted',
form: { form: {
slug: this.form.slug, slug: this.form.slug,
id: this.form.id id: this.form.id
}, },
submission_data: form.data() submission_data: form.data()
}, '*') })
if (this.isIframe) {
window.parent.postMessage(payload, '*')
}
window.postMessage(payload, '*')
try { try {
this.pendingSubmission.remove() this.pendingSubmission.remove()
@ -221,7 +217,7 @@ export default {
this.confetti.play() this.confetti.play()
} }
}).catch((error) => { }).catch((error) => {
console.log('here') console.error(error)
if (error.response && error.data && error.data.message) { if (error.response && error.data && error.data.message) {
useAlert().error(error.data.message) useAlert().error(error.data.message)
} }

View File

@ -1,20 +1,20 @@
<template> <template>
<p class="text-xs"> <p class="text-xs">
<span v-for="file in value" :key="file.file_url" <span v-for="file in parsedFiles" :key="file.file_url"
class="whitespace-nowrap rounded-md transition-colors hover:decoration-none" class="whitespace-nowrap rounded-md transition-colors hover:decoration-none"
:class="{'open-file text-gray-700 dark:text-gray-300 truncate':!isImage(file.file_url), 'open-file-img':isImage(file.file_url)}" :class="{'open-file text-gray-700 dark:text-gray-300 truncate':!file.is_image, 'open-file-img':file.is_image}"
> >
<a class="text-gray-700 dark:text-gray-300" :href="file.file_url" target="_blank" <a class="text-gray-700 dark:text-gray-300" :href="file.file_url" target="_blank"
rel="nofollow" rel="nofollow"
> >
<div v-if="isImage(file.file_url)" class="w-8 h-8"> <div v-if="file.is_image" class="w-8 h-8">
<img class="object-cover h-full w-full rounded" :src="file.file_url"/> <img class="object-cover h-full w-full rounded" :src="file.file_url" @error="failedImages.push(file.file_url)"/>
</div> </div>
<span v-else <span v-else
class="py-1 px-2" class="py-1 px-2"
> >
<a :href="file.file_url" target="_blank" download>{{ displayedFileName(file.file_name) }}</a> <a :href="file.file_url" target="_blank" download>{{ file.displayed_file_name }}</a>
</span> </span>
</a> </a>
</span> </span>
</p> </p>
@ -31,25 +31,36 @@ export default {
}, },
data() { data() {
return {} return {
failedImages: []
}
}, },
computed: {}, computed: {
mounted() { parsedFiles() {
return this.value.map((file) => {
return {
file_name: file.file_name,
file_url: file.file_url,
displayed_file_name: this.displayedFileName(file.file_name),
is_image: !this.failedImages.includes(file.file_url) && this.isImage(file.file_name)
}
})
}
}, },
methods: { methods: {
isImage(url) { isImage(fileName) {
return ['png', 'gif', 'jpg', 'jpeg', 'tif'].some((suffix) => { return ['png', 'gif', 'jpg', 'jpeg', 'tif'].some((suffix) => {
return url && url.endsWith(suffix) return fileName && fileName.endsWith(suffix)
}) })
}, },
displayedFileName(fileName) { displayedFileName(fileName) {
const extension = fileName.substr(fileName.lastIndexOf(".") + 1) const extension = fileName.substr(fileName.lastIndexOf(".") + 1)
const filename = fileName.substr(0, fileName.lastIndexOf(".")) const filename = fileName.substr(0, fileName.lastIndexOf("."))
if (filename.length > 12) { if (filename.length > 10) {
return filename.substr(0, 12) + '(...).' + extension return filename.substr(0, 10) + '[...].' + extension
} }
return filename + '.' + extension return filename + '.' + extension
} }

View File

@ -47,8 +47,9 @@ export function getOpnRequestsOptions(request, opts) {
addPasswordToFormRequest(request, opts) addPasswordToFormRequest(request, opts)
addCustomDomainHeader(request, opts) addCustomDomainHeader(request, opts)
if (!opts.baseURL) opts.baseURL = config.public.apiBase
return { return {
baseURL: config.public.apiBase,
async onResponseError({response}) { async onResponseError({response}) {
const authStore = useAuthStore() const authStore = useAuthStore()

View File

@ -1,18 +1,19 @@
import opnformConfig from "~/opnform.config.js";
async function storeLocalFile(file, options={}) {
let formData = new FormData()
formData.append('file', file)
const response = await opnFetch('/upload-file', {
method: 'POST',
body: formData
})
response.extension = file.name.split('.').pop()
return response
}
export const storeFile = async (file, options = {}) => { export const storeFile = async (file, options = {}) => {
if(!opnformConfig.s3_enabled) { // If not s3 then upload to local temp if(!useRuntimeConfig().public.s3Enabled) return storeLocalFile(file, options)
let formData = new FormData()
formData.append('file', file)
const response = await useOpnApi('/upload-file', {
method: 'POST',
body: formData
})
response.data.extension = file.name.split('.').pop()
return response.data
}
const response = await useOpnApi(options.signedStorageUrl ? options.signedStorageUrl : '/vapor/signed-storage-url', { const response = await opnFetch(options.signedStorageUrl || 'vapor/signed-storage-url', {
method: 'POST', method: 'POST',
body: options.data, body: options.data,
bucket: options.bucket || '', bucket: options.bucket || '',
@ -24,26 +25,13 @@ export const storeFile = async (file, options = {}) => {
...options.options ...options.options
}) })
console.log(response)
const headers = response.data.headers
if ('Host' in headers) {
delete headers.Host
}
if (typeof options.progress === 'undefined') {
options.progress = () => {}
}
// Upload to S3 // Upload to S3
await useFetch(response.data.url,{ await useFetch(response.url,{
method: 'PUT', method: 'PUT',
body: file, body: file,
headers: headers,
}) })
response.data.extension = file.name.split('.').pop() response.extension = file.name.split('.').pop()
return response.data return response
} }

View File

@ -16,6 +16,5 @@ export default {
"zapier_integration": "https://zapier.com/developer/public-invite/146950/58db583730cc46b821614468d94c35de/", "zapier_integration": "https://zapier.com/developer/public-invite/146950/58db583730cc46b821614468d94c35de/",
"book_onboarding": "https://zcal.co/i/YQVGEULQ", "book_onboarding": "https://zcal.co/i/YQVGEULQ",
"feature_requests": "https://opnform.canny.io/feature-requests" "feature_requests": "https://opnform.canny.io/feature-requests"
}, }
"production": false
} }

View File

@ -138,7 +138,7 @@ useOpnSeoMeta({
}) })
useHead({ useHead({
titleTemplate: (titleChunk) => { titleTemplate: (titleChunk) => {
if (form && form.value.is_pro && form.value.seo_meta.page_title) { if (form && form.value?.is_pro && form.value?.seo_meta.page_title) {
// Disable template if custom SEO title // Disable template if custom SEO title
return titleChunk return titleChunk
} }

View File

@ -57,7 +57,7 @@ export default {
}, },
redirectIfSubscribed () { redirectIfSubscribed () {
if (this.user.is_subscribed) { if (this.user.is_subscribed) {
this.$logEvent('subscribed', { plan: this.user.has_enterprise_subscription ? 'enterprise' : 'pro' }) useAmplitude().logEvent('subscribed', { plan: this.user.has_enterprise_subscription ? 'enterprise' : 'pro' })
this.$crisp.push(['set', 'session:event', [[['subscribed', { plan: this.user.has_enterprise_subscription ? 'enterprise' : 'pro' }, 'blue']]]]) this.$crisp.push(['set', 'session:event', [[['subscribed', { plan: this.user.has_enterprise_subscription ? 'enterprise' : 'pro' }, 'blue']]]])
this.$router.push({ name: 'home' }) this.$router.push({ name: 'home' })

View File

@ -86,7 +86,10 @@ Route::group(['middleware' => 'auth:api'], function () {
Route::get('/{id}/submissions', [FormSubmissionController::class, 'submissions'])->name('submissions'); Route::get('/{id}/submissions', [FormSubmissionController::class, 'submissions'])->name('submissions');
Route::get('/{id}/submissions/export', [FormSubmissionController::class, 'export'])->name('submissions.export'); Route::get('/{id}/submissions/export', [FormSubmissionController::class, 'export'])->name('submissions.export');
Route::get('/{id}/submissions/file/{filename}', [FormSubmissionController::class, 'submissionFile'])->name('submissions.file'); Route::get('/{id}/submissions/file/{filename}', [FormSubmissionController::class, 'submissionFile'])
->middleware('signed')
->withoutMiddleware(['auth:api'])
->name('submissions.file');
Route::delete('/{id}/records/{recordid}/delete', [RecordController::class, 'delete'])->name('records.delete'); Route::delete('/{id}/records/{recordid}/delete', [RecordController::class, 'delete'])->name('records.delete');