From e6905b7bb491547097d6f599aaab5121fe7b3a83 Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Tue, 17 Oct 2023 12:39:45 +0530
Subject: [PATCH 01/27] URL input validation rule (#223)
---
app/Http/Requests/AnswerFormRequest.php | 3 ++-
app/Rules/ValidUrl.php | 33 +++++++++++++++++++++++++
2 files changed, 35 insertions(+), 1 deletion(-)
create mode 100644 app/Rules/ValidUrl.php
diff --git a/app/Http/Requests/AnswerFormRequest.php b/app/Http/Requests/AnswerFormRequest.php
index 503e7b5..be80850 100644
--- a/app/Http/Requests/AnswerFormRequest.php
+++ b/app/Http/Requests/AnswerFormRequest.php
@@ -12,6 +12,7 @@ use Illuminate\Validation\Rule;
use Illuminate\Http\Request;
use App\Rules\ValidHCaptcha;
use App\Rules\ValidPhoneInputRule;
+use App\Rules\ValidUrl;
class AnswerFormRequest extends FormRequest
{
@@ -171,7 +172,7 @@ class AnswerFormRequest extends FormRequest
$this->requestRules[$property['id'].'.*'] = [new StorageFile($this->maxFileSize, [], $this->form)];
return ['array'];
}
- return ['url'];
+ return [new ValidUrl];
case 'files':
$allowedFileTypes = [];
if(!empty($property['allowed_file_types'])){
diff --git a/app/Rules/ValidUrl.php b/app/Rules/ValidUrl.php
new file mode 100644
index 0000000..cc694ec
--- /dev/null
+++ b/app/Rules/ValidUrl.php
@@ -0,0 +1,33 @@
+
Date: Fri, 20 Oct 2023 14:24:21 +0530
Subject: [PATCH 02/27] copy and paste support for file and image upload (#224)
---
resources/js/components/forms/FileInput.vue | 20 ++++++++++-----
resources/js/components/forms/ImageInput.vue | 27 +++++++++++++++-----
2 files changed, 35 insertions(+), 12 deletions(-)
diff --git a/resources/js/components/forms/FileInput.vue b/resources/js/components/forms/FileInput.vue
index d5197cd..b0ac5b7 100644
--- a/resources/js/components/forms/FileInput.vue
+++ b/resources/js/components/forms/FileInput.vue
@@ -115,9 +115,9 @@
class="font-semibold text-nt-blue hover:text-nt-blue-dark focus:outline-none focus:underline transition duration-150 ease-in-out"
@click="openFileUpload"
>
- Upload {{ multiple ? 'file(s)' : 'a file' }}
+ Upload {{ multiple ? 'file(s)' : 'a file' }},
- or drag and drop
+ use drag and drop or paste it
Up to {{ mbLimit }}mb
@@ -198,6 +198,10 @@ export default {
if(this.disabled){
this.showUploadModal = false
}
+ document.removeEventListener('paste', this.onUploadPasteEvent)
+ if(this.showUploadModal){
+ document.addEventListener("paste", this.onUploadPasteEvent)
+ }
}
},
files: {
@@ -237,11 +241,15 @@ export default {
onUploadDropEvent (e) {
this.uploadDragoverEvent = false
this.uploadDragoverTracking = false
- this.droppedFiles(e)
+ this.droppedFiles(e.dataTransfer.files)
},
- droppedFiles (e) {
- const droppedFiles = e.dataTransfer.files
-
+ onUploadPasteEvent (e) {
+ if(!this.showUploadModal) return
+ this.uploadDragoverEvent = false
+ this.uploadDragoverTracking = false
+ this.droppedFiles(e.clipboardData.files)
+ },
+ droppedFiles (droppedFiles) {
if (!droppedFiles) return
for (let i = 0; i < droppedFiles.length; i++) {
diff --git a/resources/js/components/forms/ImageInput.vue b/resources/js/components/forms/ImageInput.vue
index 11de7c4..3020f1b 100644
--- a/resources/js/components/forms/ImageInput.vue
+++ b/resources/js/components/forms/ImageInput.vue
@@ -86,9 +86,9 @@
class="font-semibold text-nt-blue hover:text-nt-blue-dark focus:outline-none focus:underline transition duration-150 ease-in-out"
@click="openFileUpload"
>
- Upload your image
+ Upload your image,
- or drag and drop
+ use drag and drop or paste it
.jpg, .jpeg, .png, .bmp, .gif, .svg up to 5mb
@@ -130,6 +130,17 @@ export default {
}
},
+ watch: {
+ showUploadModal: {
+ handler (val) {
+ document.removeEventListener('paste', this.onUploadPasteEvent)
+ if(this.showUploadModal){
+ document.addEventListener("paste", this.onUploadPasteEvent)
+ }
+ }
+ }
+ },
+
methods: {
clearUrl () {
this.$set(this.form, this.name, null)
@@ -141,11 +152,15 @@ export default {
onUploadDropEvent (e) {
this.uploadDragoverEvent = false
this.uploadDragoverTracking = false
- this.droppedFiles(e)
+ this.droppedFiles(e.dataTransfer.files)
},
- droppedFiles (e) {
- const droppedFiles = e.dataTransfer.files
-
+ onUploadPasteEvent (e) {
+ if(!this.showUploadModal) return
+ this.uploadDragoverEvent = false
+ this.uploadDragoverTracking = false
+ this.droppedFiles(e.clipboardData.files)
+ },
+ droppedFiles (droppedFiles) {
if (!droppedFiles) return
this.file = droppedFiles[0]
From 4614dc0f1868ddd554d218cbcca78afa5e61fcca Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Fri, 20 Oct 2023 14:30:35 +0530
Subject: [PATCH 03/27] Pre-fill support for file input (#222)
Co-authored-by: Julien Nahum
---
app/Http/Requests/UploadAssetRequest.php | 21 +++++++-----
app/Http/Resources/FormSubmissionResource.php | 4 ++-
app/Jobs/Form/StoreFormSubmissionJob.php | 9 +++++
app/Rules/StorageFile.php | 5 +++
resources/js/components/forms/FileInput.vue | 34 +++++++++++++++----
.../forms/fields/components/FieldOptions.vue | 7 +++-
6 files changed, 63 insertions(+), 17 deletions(-)
diff --git a/app/Http/Requests/UploadAssetRequest.php b/app/Http/Requests/UploadAssetRequest.php
index 05aaf72..51413a6 100644
--- a/app/Http/Requests/UploadAssetRequest.php
+++ b/app/Http/Requests/UploadAssetRequest.php
@@ -16,15 +16,20 @@ class UploadAssetRequest extends FormRequest
*/
public function rules()
{
+ $fileTypes = [
+ 'png',
+ 'jpeg',
+ 'jpg',
+ 'bmp',
+ 'gif',
+ 'svg'
+ ];
+ if ($this->offsetExists('type') && $this->get('type') === 'files') {
+ $fileTypes = [];
+ }
+
return [
- 'url' => ['required',new StorageFile(self::FORM_ASSET_MAX_SIZE, [
- 'png',
- 'jpeg',
- 'jpg',
- 'bmp',
- 'gif',
- 'svg'
- ])],
+ 'url' => ['required', new StorageFile(self::FORM_ASSET_MAX_SIZE, $fileTypes)],
];
}
}
diff --git a/app/Http/Resources/FormSubmissionResource.php b/app/Http/Resources/FormSubmissionResource.php
index e500dd1..6018e9e 100644
--- a/app/Http/Resources/FormSubmissionResource.php
+++ b/app/Http/Resources/FormSubmissionResource.php
@@ -46,7 +46,9 @@ class FormSubmissionResource extends JsonResource
});
foreach ($fileFields as $field) {
if (isset($data[$field['id']]) && !empty($data[$field['id']])) {
- $data[$field['id']] = collect($data[$field['id']])->map(function ($file) {
+ $data[$field['id']] = collect($data[$field['id']])->filter(function ($file) {
+ return $file !== null && $file;
+ })->map(function ($file) {
return [
'file_url' => route('open.forms.submissions.file', [$this->form_id, $file]),
'file_name' => $file,
diff --git a/app/Jobs/Form/StoreFormSubmissionJob.php b/app/Jobs/Form/StoreFormSubmissionJob.php
index 54f2933..0c4e526 100644
--- a/app/Jobs/Form/StoreFormSubmissionJob.php
+++ b/app/Jobs/Form/StoreFormSubmissionJob.php
@@ -4,6 +4,7 @@ namespace App\Jobs\Form;
use App\Events\Forms\FormSubmitted;
use App\Http\Controllers\Forms\PublicFormController;
+use App\Http\Controllers\Forms\FormController;
use App\Http\Requests\AnswerFormRequest;
use App\Models\Forms\Form;
use App\Models\Forms\FormSubmission;
@@ -162,6 +163,14 @@ class StoreFormSubmissionJob implements ShouldQueue
return null;
}
+ if(filter_var($value, FILTER_VALIDATE_URL) !== FALSE && str_contains($value, parse_url(config('app.url'))['host'])) { // In case of prefill we have full url so convert to s3
+ $fileName = basename($value);
+ $path = FormController::ASSETS_UPLOAD_PATH . '/' . $fileName;
+ $newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
+ Storage::move($path, $newPath.'/'.$fileName);
+ return $fileName;
+ }
+
if($this->isSkipForUpload($value)) {
return $value;
}
diff --git a/app/Rules/StorageFile.php b/app/Rules/StorageFile.php
index f2f0a30..b7df2eb 100644
--- a/app/Rules/StorageFile.php
+++ b/app/Rules/StorageFile.php
@@ -40,6 +40,11 @@ class StorageFile implements Rule
*/
public function passes($attribute, $value): bool
{
+ // If full path then no need to validate
+ if (filter_var($value, FILTER_VALIDATE_URL) !== FALSE) {
+ return true;
+ }
+
// This is use when updating a record, and file uploads aren't changed.
if($this->form){
$newPath = Str::of(PublicFormController::FILE_UPLOAD_PATH)->replace('?', $this->form->id);
diff --git a/resources/js/components/forms/FileInput.vue b/resources/js/components/forms/FileInput.vue
index b0ac5b7..d7ffbd2 100644
--- a/resources/js/components/forms/FileInput.vue
+++ b/resources/js/components/forms/FileInput.vue
@@ -156,6 +156,7 @@
+
+
diff --git a/resources/js/pages/templates/types-show.vue b/resources/js/pages/templates/types-show.vue
new file mode 100644
index 0000000..b91ef87
--- /dev/null
+++ b/resources/js/pages/templates/types-show.vue
@@ -0,0 +1,233 @@
+
+
+
+
+
+
+
+
+ We could not find this type.
+
+
+
+
+
+
+ {{ type.name }}
+
+
+ {{ type.meta_title }}
+
+
+ {{ type.meta_description }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ No templates found.
+
+
+
+
+
+
+
+
+ {{ type.description }}
+
+
+
+
+
+
+
+
+ Other Types
+
+
+
+ View All Templates
+
+
+
+
+
+ {{ row.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/router/routes.js b/resources/js/router/routes.js
index 281ff78..8d67a56 100644
--- a/resources/js/router/routes.js
+++ b/resources/js/router/routes.js
@@ -70,6 +70,8 @@ export default [
{ path: '/my-templates', name: 'my_templates', component: page('templates/my_templates.vue') },
{ path: '/form-templates', name: 'templates', component: page('templates/templates.vue') },
{ path: '/form-templates/:slug', name: 'templates.show', component: page('templates/show.vue') },
+ { path: '/form-templates/types/:slug', name: 'templates.types.show', component: page('templates/types-show.vue') },
+ { path: '/form-templates/industries/:slug', name: 'templates.industries.show', component: page('templates/industries-show.vue') },
{ path: '*', component: page('errors/404.vue') }
]
diff --git a/resources/js/store/modules/open/templates.js b/resources/js/store/modules/open/templates.js
index 3e229fa..15f96de 100644
--- a/resources/js/store/modules/open/templates.js
+++ b/resources/js/store/modules/open/templates.js
@@ -91,12 +91,26 @@ export const actions = {
context.commit('stopLoading')
})
},
- loadAll (context) {
+ loadAll (context, options=null) {
context.commit('startLoading')
context.dispatch('loadTypesAndIndustries')
- return axios.get(templatesEndpoint).then((response) => {
- context.commit('append', response.data)
- context.commit('setAllLoaded', true)
+
+ // Prepare with options
+ let queryStr = ''
+ if(options !== null){
+ for (const [key, value] of Object.entries(options)) {
+ queryStr += '&' + encodeURIComponent(key) + '=' + encodeURIComponent(value)
+ }
+ queryStr = queryStr.slice(1)
+ }
+ return axios.get((queryStr) ? templatesEndpoint + '?' + queryStr : templatesEndpoint).then((response) => {
+ if(options !== null){
+ context.commit('set', response.data)
+ context.commit('setAllLoaded', false)
+ } else {
+ context.commit('append', response.data)
+ context.commit('setAllLoaded', true)
+ }
context.commit('stopLoading')
}).catch((error) => {
context.commit('stopLoading')
@@ -108,17 +122,5 @@ export const actions = {
}
context.commit('stopLoading')
return Promise.resolve()
- },
- loadWithLimit (context, limit) {
- context.commit('startLoading')
- context.dispatch('loadTypesAndIndustries')
-
- return axios.get(templatesEndpoint + '?limit=' + limit).then((response) => {
- context.commit('set', response.data)
- context.commit('setAllLoaded', false)
- context.commit('stopLoading')
- }).catch((error) => {
- context.commit('stopLoading')
- })
}
}
From b043407a4fb100ebb7a52abba3430a7757c9783b Mon Sep 17 00:00:00 2001
From: Valentin Ouvrard
Date: Wed, 25 Oct 2023 19:12:08 +1100
Subject: [PATCH 05/27] feat(s3-aws): Implement use_path_style_endpoint for
Minio usage (#228)
* feat(s3-aws): Implement use_path_style_endpoint for Minio usage (https://min.io/)
* feat(s3): moove default path style to true
---------
Co-authored-by: Valentin Ouvrard
Co-authored-by: Julien Nahum
---
config/filesystems.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/config/filesystems.php b/config/filesystems.php
index 94c8112..617e720 100644
--- a/config/filesystems.php
+++ b/config/filesystems.php
@@ -63,6 +63,7 @@ return [
'bucket' => env('AWS_BUCKET'),
'url' => env('AWS_URL'),
'endpoint' => env('AWS_ENDPOINT'),
+ 'use_path_style_endpoint' => env('AWS_USE_PATH_STYLE_ENDPOINT', true),
],
],
From 8e5acab8bffeb842e4b913a6b0f435836f223c83 Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Wed, 25 Oct 2023 17:55:53 +0200
Subject: [PATCH 06/27] Fix AI generation + remove valet tls
---
app/Jobs/Form/GenerateAiForm.php | 38 ++++++++++++++------------------
vite.config.js | 5 ++---
2 files changed, 19 insertions(+), 24 deletions(-)
diff --git a/app/Jobs/Form/GenerateAiForm.php b/app/Jobs/Form/GenerateAiForm.php
index 3e19a3e..86125f4 100644
--- a/app/Jobs/Form/GenerateAiForm.php
+++ b/app/Jobs/Form/GenerateAiForm.php
@@ -3,11 +3,9 @@
namespace App\Jobs\Form;
use App\Console\Commands\GenerateTemplate;
-use App\Http\Requests\AiGenerateFormRequest;
use App\Models\Forms\AI\AiFormCompletion;
use App\Service\OpenAi\GptCompleter;
use Illuminate\Bus\Queueable;
-use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
@@ -40,6 +38,7 @@ class GenerateAiForm implements ShouldQueue
]);
$completer = (new GptCompleter(config('services.openai.api_key')))
+ ->useStreaming()
->setSystemMessage('You are a robot helping to generate forms.');
try {
@@ -53,29 +52,11 @@ class GenerateAiForm implements ShouldQueue
'result' => $this->cleanOutput($completer->getArray())
]);
} catch (\Exception $e) {
- $this->completion->update([
- 'status' => AiFormCompletion::STATUS_FAILED,
- 'result' => ['error' => $e->getMessage()]
- ]);
+ $this->onError($e);
}
}
- public function generateForm(AiGenerateFormRequest $request)
- {
- $completer = (new GptCompleter(config('services.openai.api_key')))
- ->setSystemMessage('You are a robot helping to generate forms.');
- $completer->completeChat([
- ["role" => "user", "content" => Str::of(GenerateTemplate::FORM_STRUCTURE_PROMPT)
- ->replace('[REPLACE]', $request->form_prompt)->toString()]
- ], 3000);
-
- return $this->success([
- 'message' => 'Form successfully generated!',
- 'form' => $this->cleanOutput($completer->getArray())
- ]);
- }
-
private function cleanOutput($formData)
{
// Add property uuids
@@ -85,4 +66,19 @@ class GenerateAiForm implements ShouldQueue
return $formData;
}
+
+ /**
+ * Handle a job failure.
+ */
+ public function failed(\Throwable $exception): void
+ {
+ $this->onError($exception);
+ }
+
+ private function onError(\Throwable $e) {
+ $this->completion->update([
+ 'status' => AiFormCompletion::STATUS_FAILED,
+ 'result' => ['error' => $e->getMessage()]
+ ]);
+ }
}
diff --git a/vite.config.js b/vite.config.js
index d5d52e9..2aea756 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -1,4 +1,4 @@
-import {defineConfig} from 'vite'
+import { defineConfig } from 'vite'
import laravel from 'laravel-vite-plugin'
import vue from '@vitejs/plugin-vue2'
import { sentryVitePlugin } from '@sentry/vite-plugin'
@@ -7,8 +7,7 @@ const plugins = [
laravel({
input: [
'resources/js/app.js'
- ],
- valetTls: 'opnform.test'
+ ]
}),
vue({
template: {
From 8a2e071c5623e9e5262553bed16917e304451996 Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Thu, 26 Oct 2023 16:52:16 +0530
Subject: [PATCH 07/27] Combine integrations & notifications sections (#229)
* Combine integrations & notifications sections
* New section Form Access
---
app/Http/Resources/FormResource.php | 1 +
app/Models/Forms/Form.php | 5 ++
resources/js/components/common/Button.vue | 10 ++-
.../open/forms/components/FormEditor.vue | 10 +--
.../form-components/FormAboutSubmission.vue | 24 ------
.../components/form-components/FormAccess.vue | 74 +++++++++++++++++
.../form-components/FormIntegrations.vue | 74 -----------------
.../form-components/FormNotifications.vue | 25 +++++-
.../form-components/FormSecurityPrivacy.vue | 3 -
.../components/FormNotificationsWebhook.vue | 82 +++++++++++++++++++
10 files changed, 197 insertions(+), 111 deletions(-)
create mode 100644 resources/js/components/open/forms/components/form-components/FormAccess.vue
delete mode 100644 resources/js/components/open/forms/components/form-components/FormIntegrations.vue
create mode 100644 resources/js/components/open/forms/components/form-components/components/FormNotificationsWebhook.vue
diff --git a/app/Http/Resources/FormResource.php b/app/Http/Resources/FormResource.php
index 9cdc44c..0cc8949 100644
--- a/app/Http/Resources/FormResource.php
+++ b/app/Http/Resources/FormResource.php
@@ -29,6 +29,7 @@ class FormResource extends JsonResource
'views_count' => $this->views_count,
'submissions_count' => $this->submissions_count,
'notifies' => $this->notifies,
+ 'notifies_webhook' => $this->notifies_webhook,
'notifies_slack' => $this->notifies_slack,
'notifies_discord' => $this->notifies_discord,
'send_submission_confirmation' => $this->send_submission_confirmation,
diff --git a/app/Models/Forms/Form.php b/app/Models/Forms/Form.php
index 2e2ae0d..ede1be7 100644
--- a/app/Models/Forms/Form.php
+++ b/app/Models/Forms/Form.php
@@ -279,6 +279,11 @@ class Form extends Model
}
+ public function getNotifiesWebhookAttribute()
+ {
+ return !empty($this->webhook_url);
+ }
+
public function getNotifiesDiscordAttribute()
{
return !empty($this->discord_webhook_url);
diff --git a/resources/js/components/common/Button.vue b/resources/js/components/common/Button.vue
index f393d75..85e21d8 100644
--- a/resources/js/components/common/Button.vue
+++ b/resources/js/components/common/Button.vue
@@ -1,5 +1,8 @@
-
diff --git a/resources/js/components/open/forms/components/form-components/FormAccess.vue b/resources/js/components/open/forms/components/form-components/FormAccess.vue
new file mode 100644
index 0000000..e98c9e2
--- /dev/null
+++ b/resources/js/components/open/forms/components/form-components/FormAccess.vue
@@ -0,0 +1,74 @@
+
+
+
+
+
+ Form Access
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/components/open/forms/components/form-components/FormIntegrations.vue b/resources/js/components/open/forms/components/form-components/FormIntegrations.vue
deleted file mode 100644
index 7d7ac77..0000000
--- a/resources/js/components/open/forms/components/form-components/FormIntegrations.vue
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-
- Integrations
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/resources/js/components/open/forms/components/form-components/FormNotifications.vue b/resources/js/components/open/forms/components/form-components/FormNotifications.vue
index 016e874..f76513a 100644
--- a/resources/js/components/open/forms/components/form-components/FormNotifications.vue
+++ b/resources/js/components/open/forms/components/form-components/FormNotifications.vue
@@ -6,8 +6,7 @@
:class="{'text-blue-600':isCollapseOpen, 'text-gray-500':!isCollapseOpen}" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
-
- Notifications
+ Notifications & Integrations
@@ -16,6 +15,22 @@
+
+
+
+
+
+ Zapier Integration
+
+
+
@@ -27,9 +42,10 @@ import FormNotificationsOption from './components/FormNotificationsOption.vue'
import FormNotificationsSlack from './components/FormNotificationsSlack.vue'
import FormNotificationsDiscord from './components/FormNotificationsDiscord.vue'
import FormNotificationsSubmissionConfirmation from './components/FormNotificationsSubmissionConfirmation.vue'
+import FormNotificationsWebhook from './components/FormNotificationsWebhook.vue'
export default {
- components: { FormNotificationsSubmissionConfirmation, FormNotificationsSlack, FormNotificationsDiscord, FormNotificationsOption, Collapse, ProTag },
+ components: { FormNotificationsSubmissionConfirmation, FormNotificationsSlack, FormNotificationsDiscord, FormNotificationsOption, Collapse, ProTag, FormNotificationsWebhook },
props: {
},
data () {
@@ -47,7 +63,8 @@ export default {
set (value) {
this.$store.commit('open/working_form/set', value)
}
- }
+ },
+ zapierUrl: () => window.config.links.zapier_integration
},
watch: {
diff --git a/resources/js/components/open/forms/components/form-components/FormSecurityPrivacy.vue b/resources/js/components/open/forms/components/form-components/FormSecurityPrivacy.vue
index 1c54110..3186629 100644
--- a/resources/js/components/open/forms/components/form-components/FormSecurityPrivacy.vue
+++ b/resources/js/components/open/forms/components/form-components/FormSecurityPrivacy.vue
@@ -17,9 +17,6 @@
label="Protect your form with a Captcha"
help="If enabled we will make sure respondant is a human"
/>
-
diff --git a/resources/js/components/open/forms/components/form-components/components/FormNotificationsWebhook.vue b/resources/js/components/open/forms/components/form-components/components/FormNotificationsWebhook.vue
new file mode 100644
index 0000000..eec9102
--- /dev/null
+++ b/resources/js/components/open/forms/components/form-components/components/FormNotificationsWebhook.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+ Webhook Notifications
+
+
+
+
+
+
+ Webhook Notifications
+
+
+
+
+
+
+
+
+
From 7c03d20cc481965325f009536c3c74d99c3d4139 Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Thu, 26 Oct 2023 18:28:35 +0530
Subject: [PATCH 08/27] Number input as Scale (#227)
* Number input as Scale
* scale input
---------
Co-authored-by: Julien Nahum
---
resources/js/components/forms/ScaleInput.vue | 102 ++++++++++++++++++
resources/js/components/forms/index.js | 4 +-
.../components/open/forms/OpenFormField.vue | 7 ++
.../forms/fields/components/FieldOptions.vue | 46 +++++++-
resources/js/config/form-themes.js | 18 ++++
5 files changed, 173 insertions(+), 4 deletions(-)
create mode 100644 resources/js/components/forms/ScaleInput.vue
diff --git a/resources/js/components/forms/ScaleInput.vue b/resources/js/components/forms/ScaleInput.vue
new file mode 100644
index 0000000..ea162cf
--- /dev/null
+++ b/resources/js/components/forms/ScaleInput.vue
@@ -0,0 +1,102 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/components/forms/index.js b/resources/js/components/forms/index.js
index a81e16e..04e542d 100644
--- a/resources/js/components/forms/index.js
+++ b/resources/js/components/forms/index.js
@@ -15,6 +15,7 @@ import ImageInput from './ImageInput.vue'
import RatingInput from './RatingInput.vue'
import FlatSelectInput from './FlatSelectInput.vue'
import ToggleSwitchInput from './ToggleSwitchInput.vue'
+import ScaleInput from './ScaleInput.vue'
// Components that are registered globaly.
[
@@ -32,7 +33,8 @@ import ToggleSwitchInput from './ToggleSwitchInput.vue'
ImageInput,
RatingInput,
FlatSelectInput,
- ToggleSwitchInput
+ ToggleSwitchInput,
+ ScaleInput
].forEach(Component => {
Vue.component(Component.name, Component)
})
diff --git a/resources/js/components/open/forms/OpenFormField.vue b/resources/js/components/open/forms/OpenFormField.vue
index eda7871..b96ae6d 100644
--- a/resources/js/components/open/forms/OpenFormField.vue
+++ b/resources/js/components/open/forms/OpenFormField.vue
@@ -150,6 +150,9 @@ export default {
if (field.type === 'number' && field.is_rating && field.rating_max_value) {
return 'RatingInput'
}
+ if (field.type === 'number' && field.is_scale && field.scale_max_value) {
+ return 'ScaleInput'
+ }
if (['select', 'multi_select'].includes(field.type) && field.without_dropdown) {
return 'FlatSelectInput'
}
@@ -302,6 +305,10 @@ export default {
inputProperties.accept = (this.form.is_pro && field.allowed_file_types) ? field.allowed_file_types : ""
} else if (field.type === 'number' && field.is_rating) {
inputProperties.numberOfStars = parseInt(field.rating_max_value)
+ } else if (field.type === 'number' && field.is_scale) {
+ inputProperties.minScale = parseInt(field.scale_min_value) ?? 1
+ inputProperties.maxScale = parseInt(field.scale_max_value) ?? 5
+ inputProperties.stepScale = parseInt(field.scale_step_value) ?? 1
} else if (field.type === 'number' || (field.type === 'phone_number' && field.use_simple_text_input)) {
inputProperties.pattern = '/\d*'
} else if (field.type === 'phone_number' && !field.use_simple_text_input) {
diff --git a/resources/js/components/open/forms/fields/components/FieldOptions.vue b/resources/js/components/open/forms/fields/components/FieldOptions.vue
index 1e208c6..a2eb163 100644
--- a/resources/js/components/open/forms/fields/components/FieldOptions.vue
+++ b/resources/js/components/open/forms/fields/components/FieldOptions.vue
@@ -73,13 +73,36 @@
Rating
- If enabled then this field will be star rating input.
+ Transform this field into a star rating input.
+
+
+ Scale
+
+
+ Transform this field into a scale/score input.
+
+
+
+
+
+
@@ -510,8 +533,25 @@ export default {
}
},
initRating() {
- if (this.field.is_rating && !this.field.rating_max_value) {
- this.$set(this.field, 'rating_max_value', 5)
+ if (this.field.is_rating) {
+ this.$set(this.field, 'is_scale', false)
+ if (!this.field.rating_max_value) {
+ this.$set(this.field, 'rating_max_value', 5)
+ }
+ }
+ },
+ initScale () {
+ if (this.field.is_scale) {
+ this.$set(this.field, 'is_rating', false)
+ if (!this.field.scale_min_value) {
+ this.$set(this.field, 'scale_min_value', 1)
+ }
+ if (!this.field.scale_max_value) {
+ this.$set(this.field, 'scale_max_value', 5)
+ }
+ if (!this.field.scale_step_value) {
+ this.$set(this.field, 'scale_step_value', 1)
+ }
}
},
onFieldOptionsChange(val) {
diff --git a/resources/js/config/form-themes.js b/resources/js/config/form-themes.js
index dab4996..1e4a15e 100644
--- a/resources/js/config/form-themes.js
+++ b/resources/js/config/form-themes.js
@@ -25,6 +25,12 @@ export const themes = {
label: 'text-gray-700 dark:text-gray-300 font-semibold',
input: 'relative w-full rounded-lg border-gray-300 flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full px-4 bg-white text-gray-700 placeholder-gray-400 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-600 shadow-sm text-base focus:outline-none focus:ring-2 focus:border-transparent',
help: 'text-gray-400 dark:text-gray-500'
+ },
+ ScaleInput: {
+ label: 'text-gray-700 dark:text-gray-300 font-semibold',
+ button: 'cursor-pointer text-gray-700 inline-block rounded-lg border-gray-300 px-4 py-2 flex-grow dark:bg-notion-dark-light dark:text-gray-300 text-center',
+ unselectedButton: 'bg-white hover:bg-gray-50 border',
+ help: 'text-gray-400 dark:text-gray-500'
}
},
simple: {
@@ -50,6 +56,12 @@ export const themes = {
label: 'text-gray-700 dark:text-gray-300 font-semibold',
input: 'border-transparent flex-1 appearance-none border border-gray-300 dark:border-gray-600 w-full bg-white text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-1 focus:ring-opacity-100 focus:border-transparent focus:ring-2',
help: 'text-gray-400 dark:text-gray-500'
+ },
+ ScaleInput: {
+ label: 'text-gray-700 dark:text-gray-300 font-semibold',
+ button: 'flex-1 appearance-none border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-gray-50 text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 text-center',
+ unselectedButton: 'bg-white hover:bg-gray-50 border -mx-4',
+ help: 'text-gray-400 dark:text-gray-500'
}
},
notion: {
@@ -75,6 +87,12 @@ export const themes = {
label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
input: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion border border-gray-300 dark:border-gray-600 w-full text-gray-900 bg-notion-input-background dark:bg-notion-dark-light shadow-inner dark:placeholder-gray-500 placeholder-gray-400 text-base focus:outline-none focus:ring-0 focus:ring-opacity-100 focus:border-transparent focus:ring-0 focus:shadow-focus-notion',
help: 'text-notion-input-help dark:text-gray-500'
+ },
+ ScaleInput: {
+ label: 'text-gray-900 dark:text-gray-100 mb-2 block mt-4',
+ button: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 text-center',
+ unselectedButton: 'bg-notion-input-background dark:bg-notion-dark-light hover:bg-gray-50 border',
+ help: 'text-notion-input-help dark:text-gray-500'
}
}
From 2e52518aa770a7f532ebfd35bb4fc718dd5211fc Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Fri, 27 Oct 2023 13:48:08 +0530
Subject: [PATCH 09/27] fix submission confirmation mail submission_id (#230)
* fix submission confirmation mail submission_id
* fix condition
---
app/Mail/Forms/SubmissionConfirmationMail.php | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/Mail/Forms/SubmissionConfirmationMail.php b/app/Mail/Forms/SubmissionConfirmationMail.php
index 4ea01a3..6532e94 100644
--- a/app/Mail/Forms/SubmissionConfirmationMail.php
+++ b/app/Mail/Forms/SubmissionConfirmationMail.php
@@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Str;
use Illuminate\Support\Arr;
+use Vinkla\Hashids\Facades\Hashids;
class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
{
@@ -44,7 +45,7 @@ class SubmissionConfirmationMail extends OpenFormMail implements ShouldQueue
'fields' => $formatter->getFieldsWithValue(),
'form' => $form,
'noBranding' => $form->no_branding,
- 'submission_id' => $this->event->data['submission_id'] ?? null
+ 'submission_id' => (isset($this->event->data['submission_id']) && $this->event->data['submission_id']) ? Hashids::encode($this->event->data['submission_id']) : null
]);
}
From e9174238e4b70c8e7d932895df767c4f4cc5505b Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Wed, 1 Nov 2023 16:58:10 +0100
Subject: [PATCH 10/27] Appsumo (#232)
* Implemented webhooks
* oAuth wip
* Implement the whole auth flow
* Implement file upload limit depending on appsumo license
---
.../Auth/AppSumoAuthController.php | 117 ++++++++++++++++++
.../Controllers/Auth/RegisterController.php | 24 ++--
.../Controllers/Webhook/AppSumoController.php | 91 ++++++++++++++
app/Http/Requests/AnswerFormRequest.php | 10 +-
app/Http/Resources/UserResource.php | 1 +
app/Models/License.php | 45 +++++++
app/Models/User.php | 33 +++--
app/Models/Workspace.php | 25 +++-
config/services.php | 12 +-
...023_10_30_133259_create_licenses_table.php | 39 ++++++
public/img/appsumo/as-Select-dark.png | Bin 0 -> 9252 bytes
public/img/appsumo/as-taco-white-bg.png | Bin 0 -> 9644 bytes
resources/js/components/common/Button.vue | 58 +++++----
.../vendor/appsumo/AppSumoBilling.vue | 77 ++++++++++++
.../vendor/appsumo/AppSumoRegister.vue | 50 ++++++++
.../js/pages/auth/components/RegisterForm.vue | 57 ++++++---
resources/js/pages/auth/register.vue | 31 +++--
resources/js/pages/settings/billing.vue | 37 ++++--
routes/api.php | 6 +
19 files changed, 611 insertions(+), 102 deletions(-)
create mode 100644 app/Http/Controllers/Auth/AppSumoAuthController.php
create mode 100644 app/Http/Controllers/Webhook/AppSumoController.php
create mode 100644 app/Models/License.php
create mode 100644 database/migrations/2023_10_30_133259_create_licenses_table.php
create mode 100644 public/img/appsumo/as-Select-dark.png
create mode 100644 public/img/appsumo/as-taco-white-bg.png
create mode 100644 resources/js/components/vendor/appsumo/AppSumoBilling.vue
create mode 100644 resources/js/components/vendor/appsumo/AppSumoRegister.vue
diff --git a/app/Http/Controllers/Auth/AppSumoAuthController.php b/app/Http/Controllers/Auth/AppSumoAuthController.php
new file mode 100644
index 0000000..505d2d9
--- /dev/null
+++ b/app/Http/Controllers/Auth/AppSumoAuthController.php
@@ -0,0 +1,117 @@
+validate($request, [
+ 'code' => 'required',
+ ]);
+ $accessToken = $this->retrieveAccessToken($request->code);
+ $license = $this->fetchOrCreateLicense($accessToken);
+
+ // If user connected, attach license
+ if (Auth::check()) return $this->attachLicense($license);
+
+ // otherwise start login flow by passing the encrypted license key id
+ if (is_null($license->user_id)) {
+ return redirect(url('/register?appsumo_license='.encrypt($license->id)));
+ }
+
+ return redirect(url('/register?appsumo_error=1'));
+ }
+
+ private function retrieveAccessToken(string $requestCode): string
+ {
+ return Http::withHeaders([
+ 'Content-type' => 'application/json'
+ ])->post('https://appsumo.com/openid/token/', [
+ 'grant_type' => 'authorization_code',
+ 'code' => $requestCode,
+ 'redirect_uri' => route('appsumo.callback'),
+ 'client_id' => config('services.appsumo.client_id'),
+ 'client_secret' => config('services.appsumo.client_secret'),
+ ])->throw()->json('access_token');
+ }
+
+ private function fetchOrCreateLicense(string $accessToken): License
+ {
+ // Fetch license from API
+ $licenseKey = Http::get('https://appsumo.com/openid/license_key/?access_token=' . $accessToken)
+ ->throw()
+ ->json('license_key');
+
+ // Fetch or create license model
+ $license = License::where('license_provider','appsumo')->where('license_key',$licenseKey)->first();
+ if (!$license) {
+ $licenseData = Http::withHeaders([
+ 'X-AppSumo-Licensing-Key' => config('services.appsumo.api_key'),
+ ])->get('https://api.licensing.appsumo.com/v2/licenses/'.$licenseKey)->json();
+
+ // Create new license
+ $license = License::create([
+ 'license_key' => $licenseKey,
+ 'license_provider' => 'appsumo',
+ 'status' => $licenseData['status'] === 'active' ? License::STATUS_ACTIVE : License::STATUS_INACTIVE,
+ 'meta' => $licenseData,
+ ]);
+ }
+
+ return $license;
+ }
+
+ private function attachLicense(License $license) {
+ if (!Auth::check()) {
+ throw new AuthenticationException('User not authenticated');
+ }
+
+ // Attach license if not already attached
+ if (is_null($license->user_id)) {
+ $license->user_id = Auth::id();
+ $license->save();
+ return redirect(url('/home?appsumo_connect=1'));
+ }
+
+ // Licensed already attached
+ return redirect(url('/home?appsumo_error=1'));
+ }
+
+ /**
+ * @param User $user
+ * @param string|null $licenseHash
+ * @return string|null
+ *
+ * Returns null if no license found
+ * Returns true if license was found and attached
+ * Returns false if there was an error (license not found or already attached)
+ */
+ public static function registerWithLicense(User $user, ?string $licenseHash): ?bool
+ {
+ if (!$licenseHash) {
+ return null;
+ }
+ $licenseId = decrypt($licenseHash);
+ $license = License::find($licenseId);
+
+ if ($license && is_null($license->user_id)) {
+ $license->user_id = $user->id;
+ $license->save();
+ return true;
+ }
+
+ return false;
+ }
+}
diff --git a/app/Http/Controllers/Auth/RegisterController.php b/app/Http/Controllers/Auth/RegisterController.php
index 736eff8..df7e99a 100644
--- a/app/Http/Controllers/Auth/RegisterController.php
+++ b/app/Http/Controllers/Auth/RegisterController.php
@@ -3,6 +3,7 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
+use App\Http\Resources\UserResource;
use App\Models\Workspace;
use App\Models\User;
use Illuminate\Contracts\Auth\MustVerifyEmail;
@@ -15,6 +16,8 @@ class RegisterController extends Controller
{
use RegistersUsers;
+ private ?bool $appsumoLicense = null;
+
/**
* Create a new controller instance.
*
@@ -28,8 +31,8 @@ class RegisterController extends Controller
/**
* The user has been registered.
*
- * @param \Illuminate\Http\Request $request
- * @param \App\User $user
+ * @param \Illuminate\Http\Request $request
+ * @param \App\User $user
* @return \Illuminate\Http\JsonResponse
*/
protected function registered(Request $request, User $user)
@@ -38,13 +41,17 @@ class RegisterController extends Controller
return response()->json(['status' => trans('verification.sent')]);
}
- return response()->json($user);
+ return response()->json(array_merge(
+ (new UserResource($user))->toArray($request),
+ [
+ 'appsumo_license' => $this->appsumoLicense,
+ ]));
}
/**
* Get a validator for an incoming registration request.
*
- * @param array $data
+ * @param array $data
* @return \Illuminate\Contracts\Validation\Validator
*/
protected function validator(array $data)
@@ -54,8 +61,9 @@ class RegisterController extends Controller
'email' => 'required|email:filter|max:255|unique:users|indisposable',
'password' => 'required|min:6|confirmed',
'hear_about_us' => 'required|string',
- 'agree_terms' => ['required',Rule::in([true])]
- ],[
+ 'agree_terms' => ['required', Rule::in([true])],
+ 'appsumo_license' => ['nullable'],
+ ], [
'agree_terms' => 'Please agree with the terms and conditions.'
]);
}
@@ -63,7 +71,7 @@ class RegisterController extends Controller
/**
* Create a new user instance after a valid registration.
*
- * @param array $data
+ * @param array $data
* @return \App\User
*/
protected function create(array $data)
@@ -87,6 +95,8 @@ class RegisterController extends Controller
]
], false);
+ $this->appsumoLicense = AppSumoAuthController::registerWithLicense($user, $data['appsumo_license'] ?? null);
+
return $user;
}
}
diff --git a/app/Http/Controllers/Webhook/AppSumoController.php b/app/Http/Controllers/Webhook/AppSumoController.php
new file mode 100644
index 0000000..6bbf900
--- /dev/null
+++ b/app/Http/Controllers/Webhook/AppSumoController.php
@@ -0,0 +1,91 @@
+validateSignature($request);
+
+ if ($request->test) {
+ return $this->success([
+ 'message' => 'Webhook received.',
+ 'event' => $request->event,
+ 'success' => true,
+ ]);
+ }
+
+ // Call the right function depending on the event using match()
+ match ($request->event) {
+ 'activate' => $this->handleActivateEvent($request),
+ 'upgrade', 'downgrade' => $this->handleChangeEvent($request),
+ 'deactivate' => $this->handleDeactivateEvent($request),
+ default => null,
+ };
+
+ return $this->success([
+ 'message' => 'Webhook received.',
+ 'event' => $request->event,
+ 'success' => true,
+ ]);
+ }
+
+ private function handleActivateEvent($request)
+ {
+ $licence = License::firstOrNew([
+ 'license_key' => $request->license_key,
+ 'license_provider' => 'appsumo',
+ 'status' => License::STATUS_ACTIVE,
+ ]);
+ $licence->meta = $request->json()->all();
+ $licence->save();
+ }
+
+ private function handleChangeEvent($request)
+ {
+ // Deactivate old license
+ $oldLicense = License::where([
+ 'license_key' => $request->prev_license_key,
+ 'license_provider' => 'appsumo',
+ ])->firstOrFail();
+ $oldLicense->update([
+ 'status' => License::STATUS_INACTIVE,
+ ]);
+
+ // Create new license
+ License::create([
+ 'license_key' => $request->license_key,
+ 'license_provider' => 'appsumo',
+ 'status' => License::STATUS_ACTIVE,
+ 'meta' => $request->json()->all(),
+ ]);
+ }
+
+ private function handleDeactivateEvent($request)
+ {
+ // Deactivate old license
+ $oldLicense = License::where([
+ 'license_key' => $request->prev_license_key,
+ 'license_provider' => 'appsumo',
+ ])->firstOrFail();
+ $oldLicense->update([
+ 'status' => License::STATUS_INACTIVE,
+ ]);
+ }
+
+ private function validateSignature(Request $request)
+ {
+ $signature = $request->header('x-appsumo-signature');
+ $payload = $request->getContent();
+
+ if ($signature === hash_hmac('sha256', $payload, config('services.appsumo.api_key'))) {
+ throw new UnauthorizedException('Invalid signature.');
+ }
+ }
+}
diff --git a/app/Http/Requests/AnswerFormRequest.php b/app/Http/Requests/AnswerFormRequest.php
index be80850..2a3039f 100644
--- a/app/Http/Requests/AnswerFormRequest.php
+++ b/app/Http/Requests/AnswerFormRequest.php
@@ -16,9 +16,6 @@ use App\Rules\ValidUrl;
class AnswerFormRequest extends FormRequest
{
- const MAX_FILE_SIZE_FREE = 5000000; // 5 MB
- const MAX_FILE_SIZE_PRO = 50000000; // 50 MB
-
public Form $form;
protected array $requestRules = [];
@@ -27,12 +24,7 @@ class AnswerFormRequest extends FormRequest
public function __construct(Request $request)
{
$this->form = $request->form;
-
- $this->maxFileSize = self::MAX_FILE_SIZE_FREE;
- $workspace = $this->form->workspace;
- if ($workspace && $workspace->is_pro) {
- $this->maxFileSize = self::MAX_FILE_SIZE_PRO;
- }
+ $this->maxFileSize = $this->form->workspace->max_file_size;
}
/**
diff --git a/app/Http/Resources/UserResource.php b/app/Http/Resources/UserResource.php
index cfabaa0..5e8de1d 100644
--- a/app/Http/Resources/UserResource.php
+++ b/app/Http/Resources/UserResource.php
@@ -21,6 +21,7 @@ class UserResource extends JsonResource
'template_editor' => $this->template_editor,
'has_customer_id' => $this->has_customer_id,
'has_forms' => $this->has_forms,
+ 'active_license' => $this->licenses()->active()->first(),
] : [];
return array_merge(parent::toArray($request), $personalData);
diff --git a/app/Models/License.php b/app/Models/License.php
new file mode 100644
index 0000000..bb72f34
--- /dev/null
+++ b/app/Models/License.php
@@ -0,0 +1,45 @@
+ 'array',
+ ];
+
+ public function user()
+ {
+ return $this->belongsTo(User::class);
+ }
+
+ public function scopeActive($query)
+ {
+ return $query->where('status', self::STATUS_ACTIVE);
+ }
+
+ public function getMaxFileSizeAttribute()
+ {
+ return [
+ 1 => 25000000, // 25 MB,
+ 2 => 50000000, // 50 MB,
+ 3 => 75000000, // 75 MB,
+ ][$this->meta['tier']];
+ }
+}
diff --git a/app/Models/User.php b/app/Models/User.php
index 538136b..3ce5c21 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -13,6 +13,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Cashier\Billable;
use Tymon\JWTAuth\Contracts\JWTSubject;
+
class User extends Authenticatable implements JWTSubject
{
use Notifiable, HasFactory, Billable;
@@ -80,7 +81,9 @@ class User extends Authenticatable implements JWTSubject
public function getIsSubscribedAttribute()
{
- return $this->subscribed() || in_array($this->email, config('opnform.extra_pro_users_emails'));
+ return $this->subscribed()
+ || in_array($this->email, config('opnform.extra_pro_users_emails'))
+ || !is_null($this->activeLicense());
}
public function getHasCustomerIdAttribute()
@@ -138,7 +141,7 @@ class User extends Authenticatable implements JWTSubject
public function forms()
{
- return $this->hasMany(Form::class,'creator_id');
+ return $this->hasMany(Form::class, 'creator_id');
}
public function formTemplates()
@@ -146,6 +149,16 @@ class User extends Authenticatable implements JWTSubject
return $this->hasMany(Template::class, 'creator_id');
}
+ public function licenses()
+ {
+ return $this->hasMany(License::class);
+ }
+
+ public function activeLicense(): License
+ {
+ return $this->licenses()->active()->first();
+ }
+
/**
* =================================
* Oauth Related
@@ -187,26 +200,26 @@ class User extends Authenticatable implements JWTSubject
})->first()?->onTrial();
}
- public static function boot ()
+ public static function boot()
{
parent::boot();
- static::deleting(function(User $user) {
+ static::deleting(function (User $user) {
// Remove user's workspace if he's the only one with this workspace
foreach ($user->workspaces as $workspace) {
if ($workspace->users()->count() == 1) {
$workspace->delete();
}
}
- });
+ });
}
public function scopeWithActiveSubscription($query)
{
- return $query->whereHas('subscriptions', function($query) {
- $query->where(function($q){
- $q->where('stripe_status', 'trialing')
- ->orWhere('stripe_status', 'active');
- });
+ return $query->whereHas('subscriptions', function ($query) {
+ $query->where(function ($q) {
+ $q->where('stripe_status', 'trialing')
+ ->orWhere('stripe_status', 'active');
+ });
});
}
diff --git a/app/Models/Workspace.php b/app/Models/Workspace.php
index 5974a59..c0efa23 100644
--- a/app/Models/Workspace.php
+++ b/app/Models/Workspace.php
@@ -2,8 +2,8 @@
namespace App\Models;
+use App\Http\Requests\AnswerFormRequest;
use App\Models\Forms\Form;
-use App\Models\User;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
@@ -11,6 +11,9 @@ class Workspace extends Model
{
use HasFactory;
+ const MAX_FILE_SIZE_FREE = 5000000; // 5 MB
+ const MAX_FILE_SIZE_PRO = 50000000; // 50 MB
+
protected $fillable = [
'name',
'icon',
@@ -37,6 +40,26 @@ class Workspace extends Model
return false;
}
+ public function getMaxFileSizeAttribute()
+ {
+ if(is_null(config('cashier.key'))){
+ return self::MAX_FILE_SIZE_PRO;
+ }
+
+ // Return max file size depending on subscription
+ foreach ($this->owners as $owner) {
+ if ($owner->is_subscribed) {
+ if ($license = $owner->activeLicense()) {
+ // In case of special License
+ return $license->max_file_size;
+ }
+ }
+ return self::MAX_FILE_SIZE_PRO;
+ }
+
+ return self::MAX_FILE_SIZE_FREE;
+ }
+
public function getIsEnterpriseAttribute()
{
if(is_null(config('cashier.key'))){
diff --git a/config/services.php b/config/services.php
index fe0ad40..7e2b9d1 100644
--- a/config/services.php
+++ b/config/services.php
@@ -45,7 +45,7 @@ return [
],
'notion' => [
- 'worker' => env('NOTION_WORKER','https://notion-forms-worker.notionforms.workers.dev/v1')
+ 'worker' => env('NOTION_WORKER', 'https://notion-forms-worker.notionforms.workers.dev/v1')
],
'openai' => [
@@ -53,8 +53,14 @@ return [
],
'unsplash' => [
- 'access_key' => env('UNSPLASH_ACCESS_KEY'),
- 'secret_key' => env('UNSPLASH_SECRET_KEY'),
+ 'access_key' => env('UNSPLASH_ACCESS_KEY'),
+ 'secret_key' => env('UNSPLASH_SECRET_KEY'),
+ ],
+
+ 'appsumo' => [
+ 'client_id' => env('APPSUMO_CLIENT_ID'),
+ 'client_secret' => env('APPSUMO_CLIENT_SECRET'),
+ 'api_key' => env('APPSUMO_API_KEY'),
],
'google_analytics_code' => env('GOOGLE_ANALYTICS_CODE'),
diff --git a/database/migrations/2023_10_30_133259_create_licenses_table.php b/database/migrations/2023_10_30_133259_create_licenses_table.php
new file mode 100644
index 0000000..034cde5
--- /dev/null
+++ b/database/migrations/2023_10_30_133259_create_licenses_table.php
@@ -0,0 +1,39 @@
+id();
+ $table->string('license_key');
+ $table->unsignedBigInteger('user_id')->nullable();
+ $table->string('license_provider');
+ $table->string('status');
+ $table->json('meta');
+ $table->timestamps();
+
+ $table->index(['license_key', 'license_provider']);
+ $table->index(['user_id', 'license_provider']);
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('licenses');
+ }
+};
diff --git a/public/img/appsumo/as-Select-dark.png b/public/img/appsumo/as-Select-dark.png
new file mode 100644
index 0000000000000000000000000000000000000000..44dc132c88bdf7884b751b607b25db7e967eb7f8
GIT binary patch
literal 9252
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
-
-
+
+
-
-
+
-
-
-
-
+
+
+
+
@@ -72,11 +72,11 @@ export default {
target: {
type: String,
default: '_self'
- },
+ }
},
computed: {
- btnClasses() {
+ btnClasses () {
const sizes = this.sizes
const colorShades = this.colorShades
return `v-btn ${sizes['p-y']} ${sizes['p-x']}
@@ -84,14 +84,14 @@ export default {
${colorShades?.text} transition ease-in duration-200 text-center text-${sizes?.font} font-medium focus:outline-none focus:ring-2
focus:ring-offset-2 rounded-lg flex items-center hover:no-underline`
},
- colorShades() {
+ colorShades () {
if (this.color === 'blue') {
return {
main: 'bg-blue-600',
hover: 'hover:bg-blue-700',
ring: 'focus:ring-blue-500',
'ring-offset': 'focus:ring-offset-blue-200',
- text: 'text-white',
+ text: 'text-white'
}
} else if (this.color === 'outline-blue') {
return {
@@ -99,7 +99,15 @@ export default {
hover: 'hover:bg-blue-600',
ring: 'focus:ring-blue-500',
'ring-offset': 'focus:ring-offset-blue-200',
- text: 'text-blue-600 hover:text-white',
+ text: 'text-blue-600 hover:text-white'
+ }
+ } else if (this.color === 'outline-gray') {
+ return {
+ main: 'bg-transparent border border-gray-300',
+ hover: 'hover:bg-gray-500',
+ ring: 'focus:ring-gray-500',
+ 'ring-offset': 'focus:ring-offset-gray-200',
+ text: 'text-gray-500 hover:text-white'
}
} else if (this.color === 'red') {
return {
@@ -107,7 +115,7 @@ export default {
hover: 'hover:bg-red-700',
ring: 'focus:ring-red-500',
'ring-offset': 'focus:ring-offset-red-200',
- text: 'text-white',
+ text: 'text-white'
}
} else if (this.color === 'gray') {
return {
@@ -115,7 +123,7 @@ export default {
hover: 'hover:bg-gray-700',
ring: 'focus:ring-gray-500',
'ring-offset': 'focus:ring-offset-gray-200',
- text: 'text-white',
+ text: 'text-white'
}
} else if (this.color === 'light-gray') {
return {
@@ -123,7 +131,7 @@ export default {
hover: 'hover:bg-gray-100',
ring: 'focus:ring-gray-500',
'ring-offset': 'focus:ring-offset-gray-300',
- text: 'text-gray-700',
+ text: 'text-gray-700'
}
} else if (this.color === 'green') {
return {
@@ -131,7 +139,7 @@ export default {
hover: 'hover:bg-green-700',
ring: 'focus:ring-green-500',
'ring-offset': 'focus:ring-offset-green-200',
- text: 'text-white',
+ text: 'text-white'
}
} else if (this.color === 'yellow') {
return {
@@ -139,7 +147,7 @@ export default {
hover: 'hover:bg-yellow-700',
ring: 'focus:ring-yellow-500',
'ring-offset': 'focus:ring-offset-yellow-200',
- text: 'text-white',
+ text: 'text-white'
}
} else if (this.color === 'white') {
return {
@@ -147,12 +155,12 @@ export default {
hover: 'hover:bg-gray-200',
ring: 'focus:ring-white-500',
'ring-offset': 'focus:ring-offset-white-200',
- text: 'text-gray-700',
+ text: 'text-gray-700'
}
}
console.error('Unknown color')
},
- sizes() {
+ sizes () {
if (this.size === 'small') {
return {
font: 'sm',
@@ -169,8 +177,8 @@ export default {
},
methods: {
- onClick(event) {
- this.$emit('click',event)
+ onClick (event) {
+ this.$emit('click', event)
}
}
}
diff --git a/resources/js/components/vendor/appsumo/AppSumoBilling.vue b/resources/js/components/vendor/appsumo/AppSumoBilling.vue
new file mode 100644
index 0000000..5dcffbd
--- /dev/null
+++ b/resources/js/components/vendor/appsumo/AppSumoBilling.vue
@@ -0,0 +1,77 @@
+
+
+
+
![AppSumo]()
+
![AppSumo]()
+
+
+ Your AppSumo lifetime deal tier {{ licenseTier }} license is active. Here's a reminder of your plan details:
+
+
+ - Number of Forms: {{ tierFeatures.form_quantity }}
+ - Custom domains: {{ tierFeatures.domain_names }}
+ - File Size Uploads: {{ tierFeatures.file_upload_size }}
+
+
+
+ Mangage in AppSumo
+
+
+
+
+
+
diff --git a/resources/js/components/vendor/appsumo/AppSumoRegister.vue b/resources/js/components/vendor/appsumo/AppSumoRegister.vue
new file mode 100644
index 0000000..71573cc
--- /dev/null
+++ b/resources/js/components/vendor/appsumo/AppSumoRegister.vue
@@ -0,0 +1,50 @@
+
+
+
![AppSumo]()
+
![AppSumo]()
+
+ We found your AppSumo Lifetime deal license! Just complete the registration form to finalize the activation of
+ your license.
+
+
+
+
![AppSumo]()
+
![AppSumo]()
+
+ Invalid AppSumo license. The license was probably already attached to an OpnForm account. Please contact support.
+
+
+
+
+
diff --git a/resources/js/pages/auth/components/RegisterForm.vue b/resources/js/pages/auth/components/RegisterForm.vue
index a06f82d..7eadc7d 100644
--- a/resources/js/pages/auth/components/RegisterForm.vue
+++ b/resources/js/pages/auth/components/RegisterForm.vue
@@ -1,6 +1,6 @@
diff --git a/resources/js/pages/settings/billing.vue b/resources/js/pages/settings/billing.vue
index 17164eb..47a497f 100644
--- a/resources/js/pages/settings/billing.vue
+++ b/resources/js/pages/settings/billing.vue
@@ -1,14 +1,21 @@
-
Billing details
-
Manage your billing. Download invoices, update your plan, or cancel it at any
- time.
+
+ Billing details
+
-
-
- Manage Subscription
-
-
+
+ Manage your billing. Download invoices, update your plan, or cancel it at any
+ time.
+
+
+
+ Manage Subscription
+
+
+
+
+
@@ -16,11 +23,13 @@
import axios from 'axios'
import VButton from '../../components/common/Button.vue'
import SeoMeta from '../../mixins/seo-meta.js'
+import { mapGetters } from 'vuex'
+import AppSumoBilling from '../../components/vendor/appsumo/AppSumoBilling.vue'
export default {
- components: {VButton},
- scrollToTop: false,
+ components: { AppSumoBilling, VButton },
mixins: [SeoMeta],
+ scrollToTop: false,
data: () => ({
metaTitle: 'Billing',
@@ -28,7 +37,7 @@ export default {
}),
methods: {
- openBillingDashboard() {
+ openBillingDashboard () {
this.billingLoading = true
axios.get('/api/subscription/billing-portal').then((response) => {
const url = response.data.portal_url
@@ -39,6 +48,12 @@ export default {
this.billingLoading = false
})
}
+ },
+
+ computed: {
+ ...mapGetters({
+ user: 'auth/user'
+ })
}
}
diff --git a/routes/api.php b/routes/api.php
index 9ec4b92..cfb0aba 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -129,6 +129,12 @@ Route::group(['middleware' => 'guest:api'], function () {
Route::get('oauth/{driver}/callback', [OAuthController::class, 'handleCallback'])->name('oauth.callback');
});
+
+Route::group(['prefix' => 'appsumo'], function () {
+ Route::get('oauth/callback', [\App\Http\Controllers\Auth\AppSumoAuthController::class, 'handleCallback'])->name('appsumo.callback');
+ Route::post('webhook', [\App\Http\Controllers\Webhook\AppSumoController::class, 'handle'])->name('appsumo.webhook');
+});
+
/*
* Public Forms related routes
*/
From dc5a828b8e51c6b529bc575d1c4fb49e43a4d038 Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Wed, 1 Nov 2023 18:14:01 +0100
Subject: [PATCH 11/27] Fix the user issue
---
app/Models/User.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/Models/User.php b/app/Models/User.php
index 3ce5c21..f9cabff 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -154,7 +154,7 @@ class User extends Authenticatable implements JWTSubject
return $this->hasMany(License::class);
}
- public function activeLicense(): License
+ public function activeLicense(): ?License
{
return $this->licenses()->active()->first();
}
From 04a367d12073dd2fc55ee2c6252bcbf0b8f63801 Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Wed, 1 Nov 2023 20:17:39 +0100
Subject: [PATCH 12/27] Added license webhook logs
---
.../Controllers/Webhook/AppSumoController.php | 21 ++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/app/Http/Controllers/Webhook/AppSumoController.php b/app/Http/Controllers/Webhook/AppSumoController.php
index 6bbf900..ffc0e46 100644
--- a/app/Http/Controllers/Webhook/AppSumoController.php
+++ b/app/Http/Controllers/Webhook/AppSumoController.php
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Webhook;
use App\Http\Controllers\Controller;
use App\Models\License;
+use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;
use Illuminate\Validation\UnauthorizedException;
@@ -14,6 +15,7 @@ class AppSumoController extends Controller
$this->validateSignature($request);
if ($request->test) {
+ Log::info('[APPSUMO] test request received', $request->toArray());
return $this->success([
'message' => 'Webhook received.',
'event' => $request->event,
@@ -21,6 +23,8 @@ class AppSumoController extends Controller
]);
}
+ Log::info('[APPSUMO] request received', $request->toArray());
+
// Call the right function depending on the event using match()
match ($request->event) {
'activate' => $this->handleActivateEvent($request),
@@ -45,6 +49,7 @@ class AppSumoController extends Controller
]);
$licence->meta = $request->json()->all();
$licence->save();
+ Log::info('[APPSUMO] activating license', $request->toArray());
}
private function handleChangeEvent($request)
@@ -58,13 +63,23 @@ class AppSumoController extends Controller
'status' => License::STATUS_INACTIVE,
]);
+ Log::info('[APPSUMO] De-activating license', [
+ 'license_key' => $request->prev_license_key,
+ 'license_id' => $oldLicense->id,
+ ]);
+
// Create new license
- License::create([
+ $license = License::create([
'license_key' => $request->license_key,
'license_provider' => 'appsumo',
'status' => License::STATUS_ACTIVE,
'meta' => $request->json()->all(),
]);
+ Log::info('[APPSUMO] creating new license',
+ [
+ 'license_key' => $license->license_key,
+ 'license_id' => $license->id,
+ ]);
}
private function handleDeactivateEvent($request)
@@ -77,6 +92,10 @@ class AppSumoController extends Controller
$oldLicense->update([
'status' => License::STATUS_INACTIVE,
]);
+ Log::info('[APPSUMO] De-activating license', [
+ 'license_key' => $request->prev_license_key,
+ 'license_id' => $oldLicense->id,
+ ]);
}
private function validateSignature(Request $request)
From 88a1e18055ed6e9207cb242649676a6d1d1baf8e Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Wed, 1 Nov 2023 20:36:03 +0100
Subject: [PATCH 13/27] Fix and improve license webhook controller
---
.../Controllers/Webhook/AppSumoController.php | 46 ++++++++-----------
1 file changed, 18 insertions(+), 28 deletions(-)
diff --git a/app/Http/Controllers/Webhook/AppSumoController.php b/app/Http/Controllers/Webhook/AppSumoController.php
index ffc0e46..b1154e8 100644
--- a/app/Http/Controllers/Webhook/AppSumoController.php
+++ b/app/Http/Controllers/Webhook/AppSumoController.php
@@ -42,39 +42,30 @@ class AppSumoController extends Controller
private function handleActivateEvent($request)
{
- $licence = License::firstOrNew([
- 'license_key' => $request->license_key,
- 'license_provider' => 'appsumo',
- 'status' => License::STATUS_ACTIVE,
- ]);
- $licence->meta = $request->json()->all();
- $licence->save();
- Log::info('[APPSUMO] activating license', $request->toArray());
+ $this->createLicense($request->json()->all());
}
private function handleChangeEvent($request)
{
- // Deactivate old license
- $oldLicense = License::where([
- 'license_key' => $request->prev_license_key,
- 'license_provider' => 'appsumo',
- ])->firstOrFail();
- $oldLicense->update([
- 'status' => License::STATUS_INACTIVE,
- ]);
+ $this->deactivateLicense($request->prev_license_key);
+ $this->createLicense($request->json()->all());
+ }
- Log::info('[APPSUMO] De-activating license', [
- 'license_key' => $request->prev_license_key,
- 'license_id' => $oldLicense->id,
- ]);
+ private function handleDeactivateEvent($request)
+ {
+ $this->deactivateLicense($request->license_key);
+ }
- // Create new license
- $license = License::create([
- 'license_key' => $request->license_key,
+ private function createLicense(array $licenseData)
+ {
+ $license = License::firstOrNew([
+ 'license_key' => $licenseData['license_key'],
'license_provider' => 'appsumo',
'status' => License::STATUS_ACTIVE,
- 'meta' => $request->json()->all(),
]);
+ $license->meta = $licenseData;
+ $license->save();
+
Log::info('[APPSUMO] creating new license',
[
'license_key' => $license->license_key,
@@ -82,18 +73,17 @@ class AppSumoController extends Controller
]);
}
- private function handleDeactivateEvent($request)
+ private function deactivateLicense(string $licenseKey)
{
- // Deactivate old license
$oldLicense = License::where([
- 'license_key' => $request->prev_license_key,
+ 'license_key' => $licenseKey,
'license_provider' => 'appsumo',
])->firstOrFail();
$oldLicense->update([
'status' => License::STATUS_INACTIVE,
]);
Log::info('[APPSUMO] De-activating license', [
- 'license_key' => $request->prev_license_key,
+ 'license_key' => $licenseKey,
'license_id' => $oldLicense->id,
]);
}
From cf0e92365008f012d6be9ead3647d75777cb3e05 Mon Sep 17 00:00:00 2001
From: Chirag Chhatrala <60499540+chiragchhatrala@users.noreply.github.com>
Date: Fri, 3 Nov 2023 17:09:33 +0530
Subject: [PATCH 14/27] remove extra files (#233)
Co-authored-by: Forms Dev
---
.../forms/fields/FormBlockOptionsModal.vue | 204 ------
.../forms/fields/FormFieldOptionsModal.vue | 579 ------------------
2 files changed, 783 deletions(-)
delete mode 100644 resources/js/components/open/forms/fields/FormBlockOptionsModal.vue
delete mode 100644 resources/js/components/open/forms/fields/FormFieldOptionsModal.vue
diff --git a/resources/js/components/open/forms/fields/FormBlockOptionsModal.vue b/resources/js/components/open/forms/fields/FormBlockOptionsModal.vue
deleted file mode 100644
index 7c2e4e0..0000000
--- a/resources/js/components/open/forms/fields/FormBlockOptionsModal.vue
+++ /dev/null
@@ -1,204 +0,0 @@
-
-
-
-
-
- Configure "{{ field.name }}" block
-
-
-
-
-
-
-
- General
-
-
- Exclude this field or make it required.
-
-
- Hidden
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Close
-
-
-
-
- Field not found.
-
-
-
-
-
diff --git a/resources/js/components/open/forms/fields/FormFieldOptionsModal.vue b/resources/js/components/open/forms/fields/FormFieldOptionsModal.vue
deleted file mode 100644
index fa4428c..0000000
--- a/resources/js/components/open/forms/fields/FormFieldOptionsModal.vue
+++ /dev/null
@@ -1,579 +0,0 @@
-
-
-
-
-
- Configure "{{ field.name }}" block
-
-
-
-
-
-
-
- General
-
-
- Exclude this field or make it required.
-
-
- Hidden
-
-
- Required
-
-
- Disabled
-
-
-
-
-
-
- Checkbox
-
-
- Advanced options for checkbox.
-
-
- Use toggle switch
-
-
- If enabled, checkbox will be replaced with a toggle switch
-
-
-
-
-
-
- File uploads
-
-
- Allow multiple files
-
-
-
-
-
-
-
- Number Options
-
-
- Rating
-
-
- If enabled then this field will be star rating input.
-
-
-
-
-
-
-
-
- Text Options
-
-
- Keep it simple or make it a multi-lines input.
-
-
- Multi-lines input
-
-
-
-
-
-
- Date Options
-
-
- Date Range
-
-
- Adds an end date. This cannot be used with the time option yet.
-
-
- Date with time
-
-
- Include time. Or not. This cannot be used with the date range option yet.
-
-
-
-
- Prefill with 'today'
-
-
- if enabled we will pre-fill this field with the current date
-
-
-
- Disable past dates
-
-
-
- Disable future dates
-
-
-
-
-
-
- Select Options
-
-
- Advanced options for your select/multiselect fields.
-
-
-
- Allow respondent to create new options
-
-
- Always show all select options
-
-
Options won't be in a dropdown anymore, but will all be visible
-
-
-
-
-
- Customization
-
-
-
- Change your form field name, pre-fill a value, add hints, etc.
-
-
-
-
-
- Hide field name
-
-
-
-
- Pre-filled value
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Advanced Options
-
-
-
- Generates a unique id on submission
-
-
- If you enable this, we will hide this field and fill it a unique id (UUID format) on each new form submission
-
-
-
- Generates an auto-incremented id on submission
-
-
- If you enable this, we will hide this field and fill it a unique number on each new form submission
-
-
-
-
-
-
-
-
- Close
-
-
-
-
- Field not found.
-
-
-
-
-
From 8de1c94291a9c74cf454a80f7197d55bcf27c0cd Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Mon, 6 Nov 2023 15:05:21 +0530
Subject: [PATCH 15/27] Fix the template API (#234)
---
app/Http/Controllers/TemplateController.php | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/app/Http/Controllers/TemplateController.php b/app/Http/Controllers/TemplateController.php
index 8a89614..04a1b2a 100644
--- a/app/Http/Controllers/TemplateController.php
+++ b/app/Http/Controllers/TemplateController.php
@@ -15,7 +15,7 @@ class TemplateController extends Controller
{
$limit = null;
if ($request->offsetExists('limit') && $request->get('limit') > 0) {
- $limit = (int) $request->get('limit');
+ $limit = (int)$request->get('limit');
}
$onlyMy = false;
@@ -24,12 +24,18 @@ class TemplateController extends Controller
}
$templates = Template::limit($limit)
- ->when(Auth::check() && !$onlyMy, function ($query) {
- $query->where('publicly_listed', true);
- $query->orWhere('creator_id', Auth::id());
+ ->when(Auth::check(), function ($query) use ($onlyMy) {
+ if ($onlyMy) {
+ $query->where('creator_id', Auth::id());
+ } else {
+ $query->where(function ($query) {
+ $query->where('publicly_listed', true)
+ ->orWhere('creator_id', Auth::id());
+ });
+ }
})
- ->when(Auth::check() && $onlyMy, function ($query) {
- $query->where('creator_id', Auth::id());
+ ->when(!Auth::check(), function ($query) {
+ return $query->publiclyListed();
})
->orderByDesc('created_at')
->get();
From 00e98f6bc6c982d5f4adc49c40a44eef4d71ebd7 Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Tue, 7 Nov 2023 12:31:35 +0100
Subject: [PATCH 16/27] Remove stripe keys from docker file
---
.env.docker | 3 ---
1 file changed, 3 deletions(-)
diff --git a/.env.docker b/.env.docker
index b907e68..e1fc74b 100644
--- a/.env.docker
+++ b/.env.docker
@@ -53,9 +53,6 @@ MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
JWT_TTL=1440
JWT_SECRET=
-STRIPE_KEY=
-STRIPE_SECRET=
-
MUX_WORKSPACE_ID=
MUX_API_TOKEN=
From 796b69f60f390113ef5bb18c23f5ae753a8e4fec Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Wed, 8 Nov 2023 21:56:24 +0100
Subject: [PATCH 17/27] Fix license change AppSumo
---
.../Controllers/Webhook/AppSumoController.php | 20 ++++++++++++-------
1 file changed, 13 insertions(+), 7 deletions(-)
diff --git a/app/Http/Controllers/Webhook/AppSumoController.php b/app/Http/Controllers/Webhook/AppSumoController.php
index b1154e8..c4fd44b 100644
--- a/app/Http/Controllers/Webhook/AppSumoController.php
+++ b/app/Http/Controllers/Webhook/AppSumoController.php
@@ -47,8 +47,10 @@ class AppSumoController extends Controller
private function handleChangeEvent($request)
{
- $this->deactivateLicense($request->prev_license_key);
- $this->createLicense($request->json()->all());
+ $license = $this->deactivateLicense($request->prev_license_key);
+ $this->createLicense(array_merge($request->json()->all(), [
+ 'user_id' => $license->user_id,
+ ]));
}
private function handleDeactivateEvent($request)
@@ -56,7 +58,7 @@ class AppSumoController extends Controller
$this->deactivateLicense($request->license_key);
}
- private function createLicense(array $licenseData)
+ private function createLicense(array $licenseData): License
{
$license = License::firstOrNew([
'license_key' => $licenseData['license_key'],
@@ -71,21 +73,25 @@ class AppSumoController extends Controller
'license_key' => $license->license_key,
'license_id' => $license->id,
]);
+
+ return $license;
}
- private function deactivateLicense(string $licenseKey)
+ private function deactivateLicense(string $licenseKey): License
{
- $oldLicense = License::where([
+ $license = License::where([
'license_key' => $licenseKey,
'license_provider' => 'appsumo',
])->firstOrFail();
- $oldLicense->update([
+ $license->update([
'status' => License::STATUS_INACTIVE,
]);
Log::info('[APPSUMO] De-activating license', [
'license_key' => $licenseKey,
- 'license_id' => $oldLicense->id,
+ 'license_id' => $license->id,
]);
+
+ return $license;
}
private function validateSignature(Request $request)
From 9e2bf1c2802a6b986cbf43d5ff0ad5f4f432426f Mon Sep 17 00:00:00 2001
From: Julien Nahum
Date: Wed, 8 Nov 2023 22:18:22 +0100
Subject: [PATCH 18/27] Set user when upgrading from AppSumo
---
app/Http/Controllers/Webhook/AppSumoController.php | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/Http/Controllers/Webhook/AppSumoController.php b/app/Http/Controllers/Webhook/AppSumoController.php
index c4fd44b..dfd0375 100644
--- a/app/Http/Controllers/Webhook/AppSumoController.php
+++ b/app/Http/Controllers/Webhook/AppSumoController.php
@@ -66,6 +66,7 @@ class AppSumoController extends Controller
'status' => License::STATUS_ACTIVE,
]);
$license->meta = $licenseData;
+ $license->user_id = $licenseData['user_id'] ?? null;
$license->save();
Log::info('[APPSUMO] creating new license',
From 6ffe614a0ead2237dcf5480eb28685327d1b9e0f Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Fri, 10 Nov 2023 19:17:07 +0530
Subject: [PATCH 19/27] fix template api for public list (#237)
---
app/Http/Controllers/TemplateController.php | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/Http/Controllers/TemplateController.php b/app/Http/Controllers/TemplateController.php
index 04a1b2a..1137a5d 100644
--- a/app/Http/Controllers/TemplateController.php
+++ b/app/Http/Controllers/TemplateController.php
@@ -35,7 +35,7 @@ class TemplateController extends Controller
}
})
->when(!Auth::check(), function ($query) {
- return $query->publiclyListed();
+ $query->where('publicly_listed', true);
})
->orderByDesc('created_at')
->get();
From e99a0552bbd4d5d70236bc427af25aad1cc8cb26 Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Wed, 15 Nov 2023 14:38:53 +0530
Subject: [PATCH 20/27] Fix template limit slider (#239)
---
app/Http/Controllers/TemplateController.php | 17 ++++++-----------
.../pages/welcome/TemplatesSlider.vue | 9 ++++++---
2 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/app/Http/Controllers/TemplateController.php b/app/Http/Controllers/TemplateController.php
index 1137a5d..151f0d6 100644
--- a/app/Http/Controllers/TemplateController.php
+++ b/app/Http/Controllers/TemplateController.php
@@ -13,18 +13,10 @@ class TemplateController extends Controller
{
public function index(Request $request)
{
- $limit = null;
- if ($request->offsetExists('limit') && $request->get('limit') > 0) {
- $limit = (int)$request->get('limit');
- }
+ $limit = (int)$request->get('limit', 0);
+ $onlyMy = (bool)$request->get('onlymy', false);
- $onlyMy = false;
- if ($request->offsetExists('onlymy') && $request->get('onlymy')) {
- $onlyMy = true;
- }
-
- $templates = Template::limit($limit)
- ->when(Auth::check(), function ($query) use ($onlyMy) {
+ $templates = Template::when(Auth::check(), function ($query) use ($onlyMy) {
if ($onlyMy) {
$query->where('creator_id', Auth::id());
} else {
@@ -37,6 +29,9 @@ class TemplateController extends Controller
->when(!Auth::check(), function ($query) {
$query->where('publicly_listed', true);
})
+ ->when($limit > 0, function ($query) use ($limit) {
+ $query->limit($limit);
+ })
->orderByDesc('created_at')
->get();
diff --git a/resources/js/components/pages/welcome/TemplatesSlider.vue b/resources/js/components/pages/welcome/TemplatesSlider.vue
index 1f0309a..eb82868 100644
--- a/resources/js/components/pages/welcome/TemplatesSlider.vue
+++ b/resources/js/components/pages/welcome/TemplatesSlider.vue
@@ -21,7 +21,7 @@
class="w-full inline-flex flex-nowrap overflow-hidden [mask-image:_linear-gradient(to_right,transparent_0,_black_128px,_black_calc(100%-128px),transparent_100%)]"
>
@@ -42,7 +42,10 @@ export default {
computed: {
...mapState({
templates: state => state['open/templates'].content
- })
+ }),
+ sliderTemplates () {
+ return this.templates.slice(0, 20)
+ }
},
watch: {
@@ -54,7 +57,7 @@ export default {
},
mounted() {
- store.dispatch('open/templates/loadAll', {'limit':10})
+ store.dispatch('open/templates/loadAll', { limit: 20 })
},
methods: {
From 7b3be36ba5a692ace448cc197dbf2599b03446b4 Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Wed, 15 Nov 2023 14:47:39 +0530
Subject: [PATCH 21/27] =?UTF-8?q?Rename=20=E2=80=9CPublic=E2=80=9D=20statu?=
=?UTF-8?q?s=20to=20=E2=80=9CPublished=E2=80=9D=20(#236)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Co-authored-by: Julien Nahum
---
.../forms/components/form-components/FormInformation.vue | 6 +++---
resources/js/pages/forms/show/index.vue | 9 ++++++---
resources/js/pages/home.vue | 6 +++++-
3 files changed, 14 insertions(+), 7 deletions(-)
diff --git a/resources/js/components/open/forms/components/form-components/FormInformation.vue b/resources/js/components/open/forms/components/form-components/FormInformation.vue
index 26a53f4..07de6a4 100644
--- a/resources/js/components/open/forms/components/form-components/FormInformation.vue
+++ b/resources/js/components/open/forms/components/form-components/FormInformation.vue
@@ -84,15 +84,15 @@ export default {
copyFormId: null,
visibilityOptions: [
{
- name: "Public",
+ name: "Published",
value: "public"
},
{
- name: "Draft (form won't be accessible)",
+ name: "Draft - not publicly accessible",
value: "draft"
},
{
- name: "Closed",
+ name: "Closed - won\'t accept new submissions",
value: "closed"
}
],
diff --git a/resources/js/pages/forms/show/index.vue b/resources/js/pages/forms/show/index.vue
index 6b4d92b..095eb53 100644
--- a/resources/js/pages/forms/show/index.vue
+++ b/resources/js/pages/forms/show/index.vue
@@ -49,14 +49,17 @@
- {{ form.submissions_count }}
submission{{ form.submissions_count > 0 ? 's' : '' }}
- - Closed
- - Edited {{ form.last_edited_human }}
+ - Edited {{ form.last_edited_human }}
-
+
Draft - not publicly accessible
+
+ Closed - won't accept new submissions
+
diff --git a/resources/js/pages/home.vue b/resources/js/pages/home.vue
index 5424c22..883813e 100644
--- a/resources/js/pages/home.vue
+++ b/resources/js/pages/home.vue
@@ -65,11 +65,15 @@
Edited {{ form.last_edited_human }}
-
+
Draft
+
+ Closed
+
From c51d7397fad85c985d72d5e0d1ea4c8502d4d18c Mon Sep 17 00:00:00 2001
From: Nicolas Hedger <649677+nhedger@users.noreply.github.com>
Date: Wed, 15 Nov 2023 10:55:31 +0100
Subject: [PATCH 22/27] feat: add support for MySQL (#238)
* ci: run tests on mysql
* adapt migration to support default values on MySQL
---------
Co-authored-by: Julien Nahum
---
.github/workflows/laravel.yml | 41 ++++++++++++++++++-
.../2021_05_19_140326_create_forms_table.php | 11 ++---
...4_234028_create_form_submissions_table.php | 3 +-
.../2022_05_10_144947_form_statistic.php | 3 +-
...133641_add_removed_properties_to_forms.php | 3 +-
...22_09_22_092205_create_templates_table.php | 3 +-
...9_26_084721_add_questions_to_templates.php | 3 +-
...table_submissions_button_text_to_forms.php | 3 +-
...023_07_20_073728_add_seo_meta_to_forms.php | 3 +-
...710_add_notification_settings_to_forms.php | 3 +-
...3_09_01_052507_add_fields_to_templates.php | 7 ++--
11 files changed, 66 insertions(+), 17 deletions(-)
diff --git a/.github/workflows/laravel.yml b/.github/workflows/laravel.yml
index aa89141..7635c20 100644
--- a/.github/workflows/laravel.yml
+++ b/.github/workflows/laravel.yml
@@ -27,6 +27,24 @@ jobs:
ports:
# Maps tcp port 5432 on service container to the host
- 5432:5432
+ mysql:
+ # Docker Hub image
+ image: mysql:8
+ # Provide the password for mysql
+ env:
+ MYSQL_ROOT_PASSWORD: test
+ MYSQL_DATABASE: test
+ MYSQL_USER: test
+ MYSQL_PASSWORD: test
+ # Set health checks to wait until mysql has started
+ options: >-
+ --health-cmd="mysqladmin ping"
+ --health-interval=10s
+ --health-timeout=5s
+ --health-retries=5
+ ports:
+ # Maps tcp port 3306 on service container to the host
+ - 3306:3306
concurrency:
group: 'run-tests'
@@ -35,8 +53,22 @@ jobs:
fail-fast: true
matrix:
php: [ 8.2 ]
+ connection: [ pgsql, mysql ]
+ include:
+ - connection: pgsql
+ host: localhost
+ port: 5432
+ user: postgres
+ password: postgres
+ database: postgres
+ - connection: mysql
+ host: '127.0.0.1'
+ port: 3306
+ user: root
+ password: test
+ database: test
- name: Run Feature & Unit tests (PHP ${{ matrix.php }})
+ name: Run Feature & Unit tests (PHP ${{ matrix.php }} - ${{ matrix.connection }})
steps:
- name: Checkout code
@@ -80,6 +112,13 @@ jobs:
- name: Run tests (Unit and Feature)
run: ./vendor/bin/pest -p
+ env:
+ DB_CONNECTION: ${{ matrix.connection }}
+ DB_HOST: ${{ matrix.host }}
+ DB_PORT: ${{ matrix.port }}
+ DB_DATABASE: ${{ matrix.database }}
+ DB_USERNAME: ${{ matrix.user }}
+ DB_PASSWORD: ${{ matrix.password }}
- name: "Archive log results"
if: always()
diff --git a/database/migrations/2021_05_19_140326_create_forms_table.php b/database/migrations/2021_05_19_140326_create_forms_table.php
index 7622b97..a4b6af4 100644
--- a/database/migrations/2021_05_19_140326_create_forms_table.php
+++ b/database/migrations/2021_05_19_140326_create_forms_table.php
@@ -1,6 +1,7 @@
timestamps();
$table->boolean('notifies')->default(false);
$table->text('description')->nullable();
- $table->text('submit_button_text')->default('Submit');
+ $table->text('submit_button_text')->default(new Expression("('Submit')"));
$table->boolean('re_fillable')->default(false);
- $table->text('re_fill_button_text')->default('Fill Again');
+ $table->text('re_fill_button_text')->default(new Expression("('Fill Again')"));
$table->string('color')->default('#3B82F6');
$table->boolean('uppercase_labels')->default(true);
$table->boolean('no_branding')->default(false);
$table->boolean('hide_title')->default(false);
- $table->text('submitted_text')->default('Amazing, we saved your answers. Thank you for your time and have a great day!');
+ $table->text('submitted_text')->default(new Expression("('Amazing, we saved your answers. Thank you for your time and have a great day!')"));
$table->string('dark_mode')->default('auto');
$table->string('webhook_url')->nullable();
$table->boolean('send_submission_confirmation')->default(false);
@@ -45,13 +46,13 @@ class CreateFormsTable extends Migration
$table->timestamp('closes_at')->nullable();
$table->text('closed_text')->nullable();
$table->string('notification_subject')->default("We saved your answers");
- $table->text('notification_body')->default('Hello there 👋
This is a confirmation that your submission was successfully saved.
');
+ $table->text('notification_body')->default(new Expression("('Hello there 👋
This is a confirmation that your submission was successfully saved.
')"));
$table->boolean('notifications_include_submission')->default(true);
$table->boolean('use_captcha')->default(false);
$table->boolean('can_be_indexed')->default(true);
$table->string('password')->nullable()->default(null);
$table->string('notification_sender')->default("OpenForm");
- $table->jsonb('tags')->default('[]');
+ $table->jsonb('tags')->default(new Expression('(JSON_ARRAY())'));
});
}
diff --git a/database/migrations/2021_05_24_234028_create_form_submissions_table.php b/database/migrations/2021_05_24_234028_create_form_submissions_table.php
index 64cadd7..b292103 100644
--- a/database/migrations/2021_05_24_234028_create_form_submissions_table.php
+++ b/database/migrations/2021_05_24_234028_create_form_submissions_table.php
@@ -1,6 +1,7 @@
id();
$table->foreignIdFor(\App\Models\Forms\Form::class,'form_id');
- $table->jsonb('data')->default('{}');
+ $table->jsonb('data')->default(new Expression("(JSON_OBJECT())"));
$table->timestamps();
});
}
diff --git a/database/migrations/2022_05_10_144947_form_statistic.php b/database/migrations/2022_05_10_144947_form_statistic.php
index 85ace26..295e093 100644
--- a/database/migrations/2022_05_10_144947_form_statistic.php
+++ b/database/migrations/2022_05_10_144947_form_statistic.php
@@ -1,6 +1,7 @@
id();
$table->foreignIdFor(\App\Models\Forms\Form::class,'form_id');
- $table->jsonb('data')->default('{}');
+ $table->jsonb('data')->default(new Expression("(JSON_OBJECT())"));
$table->date('date');
});
}
diff --git a/database/migrations/2022_08_18_133641_add_removed_properties_to_forms.php b/database/migrations/2022_08_18_133641_add_removed_properties_to_forms.php
index 19997ed..263052c 100644
--- a/database/migrations/2022_08_18_133641_add_removed_properties_to_forms.php
+++ b/database/migrations/2022_08_18_133641_add_removed_properties_to_forms.php
@@ -1,6 +1,7 @@
jsonb('removed_properties')->default('[]');
+ $table->jsonb('removed_properties')->default(new Expression("(JSON_ARRAY())"));
});
}
diff --git a/database/migrations/2022_09_22_092205_create_templates_table.php b/database/migrations/2022_09_22_092205_create_templates_table.php
index 4642c58..34cf3be 100644
--- a/database/migrations/2022_09_22_092205_create_templates_table.php
+++ b/database/migrations/2022_09_22_092205_create_templates_table.php
@@ -1,6 +1,7 @@
string('slug');
$table->text('description');
$table->string('image_url');
- $table->jsonb('structure')->default('{}');
+ $table->jsonb('structure')->default(new Expression("(JSON_OBJECT())"));
});
}
diff --git a/database/migrations/2022_09_26_084721_add_questions_to_templates.php b/database/migrations/2022_09_26_084721_add_questions_to_templates.php
index 449fe40..dad19e1 100644
--- a/database/migrations/2022_09_26_084721_add_questions_to_templates.php
+++ b/database/migrations/2022_09_26_084721_add_questions_to_templates.php
@@ -1,6 +1,7 @@
jsonb('questions')->default('{}');
+ $table->jsonb('questions')->default(new Expression("(JSON_ARRAY())"));
});
}
diff --git a/database/migrations/2023_03_13_094806_add_editable_submissions_button_text_to_forms.php b/database/migrations/2023_03_13_094806_add_editable_submissions_button_text_to_forms.php
index e77e68b..d412da9 100644
--- a/database/migrations/2023_03_13_094806_add_editable_submissions_button_text_to_forms.php
+++ b/database/migrations/2023_03_13_094806_add_editable_submissions_button_text_to_forms.php
@@ -1,6 +1,7 @@
text('editable_submissions_button_text')->default('Edit submission');
+ $table->text('editable_submissions_button_text')->default(new Expression("('Edit submission')"));
});
}
diff --git a/database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php b/database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php
index e0a67d7..0a91a8a 100644
--- a/database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php
+++ b/database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php
@@ -1,6 +1,7 @@
json('seo_meta')->default('{}');
+ $table->json('seo_meta')->default(new Expression("(JSON_OBJECT())"));
});
}
diff --git a/database/migrations/2023_08_23_100710_add_notification_settings_to_forms.php b/database/migrations/2023_08_23_100710_add_notification_settings_to_forms.php
index b091f6b..21f96d1 100644
--- a/database/migrations/2023_08_23_100710_add_notification_settings_to_forms.php
+++ b/database/migrations/2023_08_23_100710_add_notification_settings_to_forms.php
@@ -1,6 +1,7 @@
json('notification_settings')->default('{}')->nullable(true);
+ $table->json('notification_settings')->default(new Expression("(JSON_OBJECT())"))->nullable(true);
});
}
diff --git a/database/migrations/2023_09_01_052507_add_fields_to_templates.php b/database/migrations/2023_09_01_052507_add_fields_to_templates.php
index 3adfc83..2edfab3 100644
--- a/database/migrations/2023_09_01_052507_add_fields_to_templates.php
+++ b/database/migrations/2023_09_01_052507_add_fields_to_templates.php
@@ -1,6 +1,7 @@
boolean('publicly_listed')->default(false);
- $table->jsonb('industries')->default('[]');
- $table->jsonb('types')->default('[]');
+ $table->jsonb('industries')->default(new Expression("(JSON_ARRAY())"));
+ $table->jsonb('types')->default(new Expression("(JSON_ARRAY())"));
$table->string('short_description')->nullable();
- $table->jsonb('related_templates')->default('[]');
+ $table->jsonb('related_templates')->default(new Expression("(JSON_ARRAY())"));
$table->string('image_url',500)->nullable()->change();
});
}
From 0960af03732fa98f415b0b4c56e7425cab4e9c15 Mon Sep 17 00:00:00 2001
From: Nicolas Hedger <649677+nhedger@users.noreply.github.com>
Date: Thu, 16 Nov 2023 12:28:04 +0100
Subject: [PATCH 23/27] fix: migrations not passing on pgsql (#242)
* fix: migration not passing on pgsql
* pin postgres version
---
.github/workflows/laravel.yml | 2 +-
.../2021_05_19_140326_create_forms_table.php | 11 ++++++--
...4_234028_create_form_submissions_table.php | 11 ++++++--
.../2022_05_10_144947_form_statistic.php | 11 ++++++--
...133641_add_removed_properties_to_forms.php | 11 ++++++--
...22_09_22_092205_create_templates_table.php | 11 ++++++--
...9_26_084721_add_questions_to_templates.php | 11 ++++++--
...023_07_20_073728_add_seo_meta_to_forms.php | 11 ++++++--
...710_add_notification_settings_to_forms.php | 11 ++++++--
...3_09_01_052507_add_fields_to_templates.php | 28 ++++++++++++++++---
10 files changed, 97 insertions(+), 21 deletions(-)
diff --git a/.github/workflows/laravel.yml b/.github/workflows/laravel.yml
index 7635c20..6d24a2b 100644
--- a/.github/workflows/laravel.yml
+++ b/.github/workflows/laravel.yml
@@ -12,7 +12,7 @@ jobs:
services:
postgres:
# Docker Hub image
- image: postgres
+ image: postgres:14
# Provide the password for postgres
env:
POSTGRES_PASSWORD: postgres
diff --git a/database/migrations/2021_05_19_140326_create_forms_table.php b/database/migrations/2021_05_19_140326_create_forms_table.php
index a4b6af4..7aa63a9 100644
--- a/database/migrations/2021_05_19_140326_create_forms_table.php
+++ b/database/migrations/2021_05_19_140326_create_forms_table.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CreateFormsTable extends Migration
@@ -14,7 +15,9 @@ class CreateFormsTable extends Migration
*/
public function up()
{
- Schema::create('forms', function (Blueprint $table) {
+ $driver = DB::getDriverName();
+
+ Schema::create('forms', function (Blueprint $table) use ($driver) {
$table->id();
$table->foreignIdFor(\App\Models\Workspace::class,'workspace_id');
$table->string('title');
@@ -52,7 +55,11 @@ class CreateFormsTable extends Migration
$table->boolean('can_be_indexed')->default(true);
$table->string('password')->nullable()->default(null);
$table->string('notification_sender')->default("OpenForm");
- $table->jsonb('tags')->default(new Expression('(JSON_ARRAY())'));
+ if ($driver === 'mysql') {
+ $table->jsonb('tags')->default(new Expression('(JSON_ARRAY())'));
+ } else {
+ $table->jsonb('tags')->default('[]');
+ }
});
}
diff --git a/database/migrations/2021_05_24_234028_create_form_submissions_table.php b/database/migrations/2021_05_24_234028_create_form_submissions_table.php
index b292103..b7e48af 100644
--- a/database/migrations/2021_05_24_234028_create_form_submissions_table.php
+++ b/database/migrations/2021_05_24_234028_create_form_submissions_table.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class CreateFormSubmissionsTable extends Migration
@@ -14,10 +15,16 @@ class CreateFormSubmissionsTable extends Migration
*/
public function up()
{
- Schema::create('form_submissions', function (Blueprint $table) {
+ $driver = DB::getDriverName();
+
+ Schema::create('form_submissions', function (Blueprint $table) use ($driver) {
$table->id();
$table->foreignIdFor(\App\Models\Forms\Form::class,'form_id');
- $table->jsonb('data')->default(new Expression("(JSON_OBJECT())"));
+ if ($driver === 'mysql') {
+ $table->jsonb('data')->default(new Expression("(JSON_OBJECT())"));
+ } else {
+ $table->jsonb('data')->default("{}");
+ }
$table->timestamps();
});
}
diff --git a/database/migrations/2022_05_10_144947_form_statistic.php b/database/migrations/2022_05_10_144947_form_statistic.php
index 295e093..7cafb53 100644
--- a/database/migrations/2022_05_10_144947_form_statistic.php
+++ b/database/migrations/2022_05_10_144947_form_statistic.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@@ -14,10 +15,16 @@ return new class extends Migration
*/
public function up()
{
- Schema::create('form_statistics', function (Blueprint $table) {
+ $driver = DB::getDriverName();
+
+ Schema::create('form_statistics', function (Blueprint $table) use ($driver) {
$table->id();
$table->foreignIdFor(\App\Models\Forms\Form::class,'form_id');
- $table->jsonb('data')->default(new Expression("(JSON_OBJECT())"));
+ if ($driver === 'mysql') {
+ $table->jsonb('data')->default(new Expression("(JSON_OBJECT())"));
+ } else {
+ $table->jsonb('data')->default("{}");
+ }
$table->date('date');
});
}
diff --git a/database/migrations/2022_08_18_133641_add_removed_properties_to_forms.php b/database/migrations/2022_08_18_133641_add_removed_properties_to_forms.php
index 263052c..03389a2 100644
--- a/database/migrations/2022_08_18_133641_add_removed_properties_to_forms.php
+++ b/database/migrations/2022_08_18_133641_add_removed_properties_to_forms.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@@ -14,8 +15,14 @@ return new class extends Migration
*/
public function up()
{
- Schema::table('forms', function (Blueprint $table) {
- $table->jsonb('removed_properties')->default(new Expression("(JSON_ARRAY())"));
+ $driver = DB::getDriverName();
+
+ Schema::table('forms', function (Blueprint $table) use ($driver) {
+ if ($driver === 'mysql') {
+ $table->jsonb('removed_properties')->default(new Expression("(JSON_ARRAY())"));
+ } else {
+ $table->jsonb('removed_properties')->default("[]");
+ }
});
}
diff --git a/database/migrations/2022_09_22_092205_create_templates_table.php b/database/migrations/2022_09_22_092205_create_templates_table.php
index 34cf3be..0f7094f 100644
--- a/database/migrations/2022_09_22_092205_create_templates_table.php
+++ b/database/migrations/2022_09_22_092205_create_templates_table.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@@ -14,14 +15,20 @@ return new class extends Migration
*/
public function up()
{
- Schema::create('templates', function (Blueprint $table) {
+ $driver = DB::getDriverName();
+
+ Schema::create('templates', function (Blueprint $table) use ($driver) {
$table->id();
$table->timestamps();
$table->string('name');
$table->string('slug');
$table->text('description');
$table->string('image_url');
- $table->jsonb('structure')->default(new Expression("(JSON_OBJECT())"));
+ if ($driver === 'mysql') {
+ $table->jsonb('structure')->default(new Expression("(JSON_OBJECT())"));
+ } else {
+ $table->jsonb('structure')->default('{}');
+ }
});
}
diff --git a/database/migrations/2022_09_26_084721_add_questions_to_templates.php b/database/migrations/2022_09_26_084721_add_questions_to_templates.php
index dad19e1..1fd0c50 100644
--- a/database/migrations/2022_09_26_084721_add_questions_to_templates.php
+++ b/database/migrations/2022_09_26_084721_add_questions_to_templates.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@@ -14,8 +15,14 @@ return new class extends Migration
*/
public function up()
{
- Schema::table('templates', function (Blueprint $table) {
- $table->jsonb('questions')->default(new Expression("(JSON_ARRAY())"));
+ $driver = DB::getDriverName();
+
+ Schema::table('templates', function (Blueprint $table) use ($driver) {
+ if ($driver === 'mysql') {
+ $table->jsonb('questions')->default(new Expression("(JSON_ARRAY())"));
+ } else {
+ $table->jsonb('questions')->default('[]');
+ }
});
}
diff --git a/database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php b/database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php
index 0a91a8a..01fa247 100644
--- a/database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php
+++ b/database/migrations/2023_07_20_073728_add_seo_meta_to_forms.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@@ -14,8 +15,14 @@ return new class extends Migration
*/
public function up()
{
- Schema::table('forms', function (Blueprint $table) {
- $table->json('seo_meta')->default(new Expression("(JSON_OBJECT())"));
+ $driver = DB::getDriverName();
+
+ Schema::table('forms', function (Blueprint $table) use ($driver) {
+ if ($driver === 'mysql') {
+ $table->json('seo_meta')->default(new Expression("(JSON_OBJECT())"));
+ } else {
+ $table->json('seo_meta')->default("{}");
+ }
});
}
diff --git a/database/migrations/2023_08_23_100710_add_notification_settings_to_forms.php b/database/migrations/2023_08_23_100710_add_notification_settings_to_forms.php
index 21f96d1..37f3202 100644
--- a/database/migrations/2023_08_23_100710_add_notification_settings_to_forms.php
+++ b/database/migrations/2023_08_23_100710_add_notification_settings_to_forms.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@@ -14,8 +15,14 @@ return new class extends Migration
*/
public function up()
{
- Schema::table('forms', function (Blueprint $table) {
- $table->json('notification_settings')->default(new Expression("(JSON_OBJECT())"))->nullable(true);
+ $driver = DB::getDriverName();
+
+ Schema::table('forms', function (Blueprint $table) use ($driver) {
+ if ($driver === 'mysql') {
+ $table->json('notification_settings')->default(new Expression("(JSON_OBJECT())"))->nullable(true);
+ } else {
+ $table->json('notification_settings')->default('{}')->nullable(true);
+ }
});
}
diff --git a/database/migrations/2023_09_01_052507_add_fields_to_templates.php b/database/migrations/2023_09_01_052507_add_fields_to_templates.php
index 2edfab3..a4b8e74 100644
--- a/database/migrations/2023_09_01_052507_add_fields_to_templates.php
+++ b/database/migrations/2023_09_01_052507_add_fields_to_templates.php
@@ -3,6 +3,7 @@
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Query\Expression;
use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
@@ -14,12 +15,31 @@ return new class extends Migration
*/
public function up()
{
- Schema::table('templates', function (Blueprint $table) {
+ $driver = DB::getDriverName();
+
+ Schema::table('templates', function (Blueprint $table) use ($driver) {
$table->boolean('publicly_listed')->default(false);
- $table->jsonb('industries')->default(new Expression("(JSON_ARRAY())"));
- $table->jsonb('types')->default(new Expression("(JSON_ARRAY())"));
+
+ if ($driver === 'mysql') {
+ $table->jsonb('industries')->default(new Expression("(JSON_ARRAY())"));
+ } else {
+ $table->jsonb('industries')->default('[]');
+ }
+
+ if ($driver === 'mysql') {
+ $table->jsonb('types')->default(new Expression("(JSON_ARRAY())"));
+ } else {
+ $table->jsonb('types')->default('[]');
+ }
+
$table->string('short_description')->nullable();
- $table->jsonb('related_templates')->default(new Expression("(JSON_ARRAY())"));
+
+ if ($driver === 'mysql') {
+ $table->jsonb('related_templates')->default(new Expression("(JSON_ARRAY())"));
+ } else {
+ $table->jsonb('related_templates')->default('[]');
+ }
+
$table->string('image_url',500)->nullable()->change();
});
}
From 64e79f34f2dbea65c43b10f0ab8898a359658b17 Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Mon, 20 Nov 2023 13:38:50 +0530
Subject: [PATCH 24/27] Custom Plan (#243)
---
.../components/pages/pricing/CustomPlan.vue | 37 +++++++++++++++++++
.../components/pages/pricing/PricingTable.vue | 4 ++
2 files changed, 41 insertions(+)
create mode 100644 resources/js/components/pages/pricing/CustomPlan.vue
diff --git a/resources/js/components/pages/pricing/CustomPlan.vue b/resources/js/components/pages/pricing/CustomPlan.vue
new file mode 100644
index 0000000..6180fbf
--- /dev/null
+++ b/resources/js/components/pages/pricing/CustomPlan.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
Custom plan
+
Get a custom file upload limit, enterprise-level support, custom contract, payment via invoice/PO etc.
+
+
+
+ Contact us
+
+
+
+
+
+
+
+
diff --git a/resources/js/components/pages/pricing/PricingTable.vue b/resources/js/components/pages/pricing/PricingTable.vue
index d79cdba..4c4e305 100644
--- a/resources/js/components/pages/pricing/PricingTable.vue
+++ b/resources/js/components/pages/pricing/PricingTable.vue
@@ -93,6 +93,8 @@
+
+
@@ -104,11 +106,13 @@ import {mapGetters} from 'vuex'
import axios from 'axios'
import MonthlyYearlySelector from './MonthlyYearlySelector.vue'
import CheckoutDetailsModal from './CheckoutDetailsModal.vue'
+import CustomPlan from './CustomPlan.vue'
export default {
components: {
MonthlyYearlySelector,
CheckoutDetailsModal,
+ CustomPlan
},
props: {
homePage: {
From d65c1be9b520eb8dfef9f4420aecf6450ba90904 Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Tue, 28 Nov 2023 15:53:04 +0530
Subject: [PATCH 25/27] Create common function for userIsFormOwner & rewrite
protected form (#244)
* Create common function for userIsFormOwner & rewrite protected form
* fix testcase
---
app/Http/Kernel.php | 2 +-
...ordProtectedForm.php => ProtectedForm.php} | 42 +++++++++++--------
app/Http/Resources/FormResource.php | 18 +++-----
app/Models/User.php | 5 +++
routes/api.php | 2 +-
tests/Feature/Forms/FormPasswordTest.php | 4 +-
6 files changed, 39 insertions(+), 34 deletions(-)
rename app/Http/Middleware/Form/{PasswordProtectedForm.php => ProtectedForm.php} (53%)
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index a786d57..970e320 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -80,6 +80,6 @@ class Kernel extends HttpKernel
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'pro-form' => \App\Http\Middleware\Form\ProForm::class,
- 'password-protected-form' => \App\Http\Middleware\Form\PasswordProtectedForm::class,
+ 'protected-form' => \App\Http\Middleware\Form\ProtectedForm::class,
];
}
diff --git a/app/Http/Middleware/Form/PasswordProtectedForm.php b/app/Http/Middleware/Form/ProtectedForm.php
similarity index 53%
rename from app/Http/Middleware/Form/PasswordProtectedForm.php
rename to app/Http/Middleware/Form/ProtectedForm.php
index b4e2e15..6bbc947 100644
--- a/app/Http/Middleware/Form/PasswordProtectedForm.php
+++ b/app/Http/Middleware/Form/ProtectedForm.php
@@ -7,7 +7,7 @@ use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-class PasswordProtectedForm
+class ProtectedForm
{
const PASSWORD_HEADER_NAME = 'form-password';
@@ -20,26 +20,34 @@ class PasswordProtectedForm
*/
public function handle(Request $request, Closure $next)
{
- if ($request->route('slug')) {
- $form = Form::where('slug',$request->route('slug'))->firstOrFail();
- $request->merge([
- 'form' => $form,
- ]);
- $userIsFormOwner = Auth::check() && Auth::user()->workspaces()->find($form->workspace_id) !== null;
- if (!$userIsFormOwner && $form->has_password) {
- if($this->hasCorrectPassword($request, $form)){
- return $next($request);
- }
-
- return response([
- 'status' => 'Unauthorized',
- 'message' => 'Form is password protected.',
- ], 403);
- }
+ if (!$request->route('slug')) {
+ return $next($request);
}
+
+ $form = Form::where('slug',$request->route('slug'))->firstOrFail();
+ $request->merge([
+ 'form' => $form,
+ ]);
+ $userIsFormOwner = Auth::check() && Auth::user()->ownsForm($form);
+ if (!$userIsFormOwner && $this->isProtected($request, $form)) {
+ return response([
+ 'status' => 'Unauthorized',
+ 'message' => 'Form is protected.',
+ ], 403);
+ }
+
return $next($request);
}
+ public static function isProtected(Request $request, Form $form)
+ {
+ if (!$form->has_password) {
+ return false;
+ }
+
+ return !self::hasCorrectPassword($request, $form);
+ }
+
public static function hasCorrectPassword(Request $request, Form $form)
{
return $request->headers->has(self::PASSWORD_HEADER_NAME) && $request->headers->get(self::PASSWORD_HEADER_NAME) == hash('sha256', $form->password);
diff --git a/app/Http/Resources/FormResource.php b/app/Http/Resources/FormResource.php
index 0cc8949..7f6a86f 100644
--- a/app/Http/Resources/FormResource.php
+++ b/app/Http/Resources/FormResource.php
@@ -2,7 +2,7 @@
namespace App\Http\Resources;
-use App\Http\Middleware\Form\PasswordProtectedForm;
+use App\Http\Middleware\Form\ProtectedForm;
use App\Http\Resources\UserResource;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Facades\Auth;
@@ -20,8 +20,8 @@ class FormResource extends JsonResource
*/
public function toArray($request)
{
- if(!$this->userIsFormOwner() && $this->doesMissPassword($request)){
- return $this->getPasswordProtectedForm();
+ if(!$this->userIsFormOwner() && ProtectedForm::isProtected($request, $this->resource)){
+ return $this->getProtectedForm();
}
$ownerData = $this->userIsFormOwner() ? [
@@ -96,14 +96,7 @@ class FormResource extends JsonResource
return $this;
}
- private function doesMissPassword(Request $request)
- {
- if (!$this->has_password) return false;
-
- return !PasswordProtectedForm::hasCorrectPassword($request, $this->resource);
- }
-
- private function getPasswordProtectedForm()
+ private function getProtectedForm()
{
return [
'id' => $this->id,
@@ -131,8 +124,7 @@ class FormResource extends JsonResource
private function userIsFormOwner() {
return $this->extra?->userIsOwner ??
(
- Auth::check()
- && Auth::user()->workspaces()->find($this->workspace_id) !== null
+ Auth::check() && Auth::user()->ownsForm($this->resource)
);
}
diff --git a/app/Models/User.php b/app/Models/User.php
index f9cabff..764d927 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -61,6 +61,11 @@ class User extends Authenticatable implements JWTSubject
protected $withCount = ['workspaces'];
+ public function ownsForm(Form $form)
+ {
+ return $this->workspaces()->find($form->workspace_id) !== null;
+ }
+
/**
* Get the profile photo URL attribute.
*
diff --git a/routes/api.php b/routes/api.php
index cfb0aba..1719bea 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -139,7 +139,7 @@ Route::group(['prefix' => 'appsumo'], function () {
* Public Forms related routes
*/
Route::prefix('forms')->name('forms.')->group(function () {
- Route::middleware('password-protected-form')->group(function () {
+ Route::middleware('protected-form')->group(function () {
Route::post('{slug}/answer', [PublicFormController::class, 'answer'])->name('answer');
// Form content endpoints (user lists, relation lists etc.)
diff --git a/tests/Feature/Forms/FormPasswordTest.php b/tests/Feature/Forms/FormPasswordTest.php
index 15274a2..03d2650 100644
--- a/tests/Feature/Forms/FormPasswordTest.php
+++ b/tests/Feature/Forms/FormPasswordTest.php
@@ -54,7 +54,7 @@ it('can not submit form without password for guest user', function () {
->assertStatus(403)
->assertJson([
'status' => 'Unauthorized',
- 'message' => 'Form is password protected.'
+ 'message' => 'Form is protected.'
]);
});
@@ -66,7 +66,7 @@ it('can not submit form with wrong password for guest user', function () {
->assertStatus(403)
->assertJson([
'status' => 'Unauthorized',
- 'message' => 'Form is password protected.'
+ 'message' => 'Form is protected.'
]);
});
From 730bdd1b1f38807e743bb167f8ffa6a9040283d0 Mon Sep 17 00:00:00 2001
From: formsdev <136701234+formsdev@users.noreply.github.com>
Date: Tue, 28 Nov 2023 15:54:55 +0530
Subject: [PATCH 26/27] Refactor editor panels (#245)
* Refactor editor panels
* EditorOptionsPanel icon fixes
* manage editor panel open/close
---------
Co-authored-by: Julien Nahum
---
.../open/editors/EditorOptionsPanel.vue | 46 +++++++++++++++++++
.../form-components/FormAboutSubmission.vue | 29 +++++-------
.../components/form-components/FormAccess.vue | 28 ++++-------
.../form-components/FormCustomCode.vue | 24 ++++------
.../form-components/FormCustomSeo.vue | 29 ++++--------
.../form-components/FormCustomization.vue | 29 +++++-------
.../form-components/FormInformation.vue | 23 ++++------
.../form-components/FormNotifications.vue | 23 ++++------
.../form-components/FormSecurityPrivacy.vue | 22 ++++-----
.../form-components/FormStructure.vue | 23 ++++------
tailwind.config.js | 1 +
11 files changed, 132 insertions(+), 145 deletions(-)
create mode 100644 resources/js/components/open/editors/EditorOptionsPanel.vue
diff --git a/resources/js/components/open/editors/EditorOptionsPanel.vue b/resources/js/components/open/editors/EditorOptionsPanel.vue
new file mode 100644
index 0000000..47c6926
--- /dev/null
+++ b/resources/js/components/open/editors/EditorOptionsPanel.vue
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+ {{ name }}
+
+
+
+
+
+
+
+
+
+
diff --git a/resources/js/components/open/forms/components/form-components/FormAboutSubmission.vue b/resources/js/components/open/forms/components/form-components/FormAboutSubmission.vue
index 4407c4a..9cc5f83 100644
--- a/resources/js/components/open/forms/components/form-components/FormAboutSubmission.vue
+++ b/resources/js/components/open/forms/components/form-components/FormAboutSubmission.vue
@@ -1,17 +1,11 @@
-
-
-
-
-
- About Submissions
-
+
+
+
-
+
diff --git a/resources/js/config/form-themes.js b/resources/js/config/form-themes.js
index 1e4a15e..56c761e 100644
--- a/resources/js/config/form-themes.js
+++ b/resources/js/config/form-themes.js
@@ -31,6 +31,14 @@ export const themes = {
button: 'cursor-pointer text-gray-700 inline-block rounded-lg border-gray-300 px-4 py-2 flex-grow dark:bg-notion-dark-light dark:text-gray-300 text-center',
unselectedButton: 'bg-white hover:bg-gray-50 border',
help: 'text-gray-400 dark:text-gray-500'
+ },
+ fileInput: {
+ input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded-lg',
+ inputHover: {
+ light: 'bg-neutral-50',
+ dark: 'bg-notion-dark-light'
+ },
+ uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light rounded-lg shadow-sm max-w-[10rem]'
}
},
simple: {
@@ -62,6 +70,14 @@ export const themes = {
button: 'flex-1 appearance-none border-gray-300 dark:border-gray-600 w-full py-2 px-2 bg-gray-50 text-gray-700 dark:bg-notion-dark-light dark:text-gray-300 text-center',
unselectedButton: 'bg-white hover:bg-gray-50 border -mx-4',
help: 'text-gray-400 dark:text-gray-500'
+ },
+ fileInput: {
+ input: 'min-h-40 border border-dashed border-gray-300 p-4',
+ inputHover: {
+ light: 'bg-neutral-50',
+ dark: 'bg-notion-dark-light'
+ },
+ uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light shadow-sm max-w-[10rem]'
}
},
notion: {
@@ -93,6 +109,14 @@ export const themes = {
button: 'rounded border-transparent flex-1 appearance-none shadow-inner-notion w-full py-2 px-2 bg-notion-input-background dark:bg-notion-dark-light text-gray-900 dark:text-gray-100 text-center',
unselectedButton: 'bg-notion-input-background dark:bg-notion-dark-light hover:bg-gray-50 border',
help: 'text-notion-input-help dark:text-gray-500'
+ },
+ fileInput: {
+ input: 'min-h-40 border border-dashed border-gray-300 p-4 rounded bg-notion-input-background',
+ inputHover: {
+ light: 'bg-neutral-50',
+ dark: 'bg-notion-dark-light'
+ },
+ uploadedFile: 'border border-gray-300 dark:border-gray-600 bg-white dark:bg-notion-dark-light rounded shadow-sm max-w-[10rem]'
}
}