Add Domain Verification on Onboarding Wizard

This commit is contained in:
Mohit Panjwani
2021-06-18 09:51:19 +00:00
26 changed files with 1549 additions and 1401 deletions

View File

@ -3,8 +3,12 @@
namespace Crater\Http\Controllers\V1\Mobile;
use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\DomainEnvironmentRequest;
use Crater\Models\User;
use Crater\Space\EnvironmentManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;
@ -40,4 +44,9 @@ class AuthController extends Controller
'success' => true,
]);
}
public function check()
{
return Auth::check();
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Crater\Http\Controllers\V1\Onboarding;
use Crater\Http\Controllers\Controller;
use Crater\Http\Requests\DomainEnvironmentRequest;
use Crater\Space\EnvironmentManager;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
class AppDomainController extends Controller
{
/**
*
* @param DomainEnvironmentRequest $request
*/
public function __invoke(DomainEnvironmentRequest $request)
{
Artisan::call('optimize:clear');
$environmentManager = new EnvironmentManager();
$results = $environmentManager->saveDomainVariables($request);
if (in_array('error', $results)) {
return response()->json($results);
}
return response()->json([
'success' => false,
]);
}
}

View File

@ -32,16 +32,20 @@ class DatabaseConfigurationController extends Controller
Artisan::call('config:clear');
Artisan::call('cache:clear');
$status = pcntl_fork();
pcntl_wait($status);
$results = $this->environmentManager->saveDatabaseVariables($request);
if (array_key_exists("success", $results)) {
Artisan::call('key:generate --force');
Artisan::call('optimize:clear');
Artisan::call('config:clear');
Artisan::call('cache:clear');
Artisan::call('storage:link');
Artisan::call('migrate --seed --force');
}
return response()->json($results);
}

View File

@ -19,7 +19,6 @@ class FinishController extends Controller
{
\Storage::disk('local')->put('database_created', 'database_created');
$user = User::where('role', 'super admin')->first();
Auth::login($user);
return response()->json(['success' => true]);
}
}

View File

@ -0,0 +1,25 @@
<?php
namespace Crater\Http\Controllers\V1\Onboarding;
use Auth;
use Crater\Http\Controllers\Controller;
use Crater\Models\User;
use Illuminate\Http\Request;
class LoginController extends Controller
{
/**
* Handle the incoming request.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\Response
*/
public function __invoke(Request $request)
{
$user = User::where('role', 'super admin')->first();
Auth::login($user);
return response()->json(['success' => true]);
}
}

View File

@ -30,9 +30,6 @@ class DatabaseEnvironmentRequest extends FormRequest
'required',
'url',
],
'app_domain' => [
'required',
],
'database_connection' => [
'required',
'string',
@ -50,9 +47,6 @@ class DatabaseEnvironmentRequest extends FormRequest
'required',
'url',
],
'app_domain' => [
'required',
],
'database_connection' => [
'required',
'string',

View File

@ -0,0 +1,32 @@
<?php
namespace Crater\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class DomainEnvironmentRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true;
}
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
public function rules()
{
return [
'app_domain' => [
'required',
],
];
}
}

View File

@ -262,6 +262,11 @@ class User extends Authenticatable implements HasMedia
$query->orWhere('users.id', $customer_id);
}
public function scopeWhereSuperAdmin($query)
{
$query->orWhere('role', 'super admin');
}
public function scopeApplyInvoiceFilters($query, array $filters)
{
$filters = collect($filters);

View File

@ -4,6 +4,7 @@ namespace Crater\Space;
use Crater\Http\Requests\DatabaseEnvironmentRequest;
use Crater\Http\Requests\DiskEnvironmentRequest;
use Crater\Http\Requests\DomainEnvironmentRequest;
use Crater\Http\Requests\MailEnvironmentRequest;
use Exception;
use Illuminate\Http\Request;
@ -359,11 +360,11 @@ class EnvironmentManager
}
/**
* Save the disk content to the .env file.
*
* @param Request $request
* @return array
*/
* Save the disk content to the .env file.
*
* @param Request $request
* @return array
*/
public function saveDiskVariables(DiskEnvironmentRequest $request)
{
$diskData = $this->getDiskData($request);
@ -486,4 +487,35 @@ class EnvironmentManager
'old_default_driver' => $oldDefaultDriver,
];
}
/**
* Save sanctum statful domain to the .env file.
*
* @param DomainEnvironmentRequest $request
* @return array
*/
public function saveDomainVariables(DomainEnvironmentRequest $request)
{
try {
file_put_contents($this->envPath, str_replace(
'SANCTUM_STATEFUL_DOMAINS='.env('SANCTUM_STATEFUL_DOMAINS'),
'SANCTUM_STATEFUL_DOMAINS='.$request->app_domain,
file_get_contents($this->envPath)
));
file_put_contents($this->envPath, str_replace(
'SESSION_DOMAIN='.config('session.domain'),
'SESSION_DOMAIN='.explode(':', $request->app_domain)[0],
file_get_contents($this->envPath)
));
} catch (Exception $e) {
return [
'error' => 'domain_verification_failed'
];
}
return [
'success' => 'domain_variable_save_successfully'
];
}
}

View File

@ -126,9 +126,11 @@ global.axios.interceptors.response.use(undefined, function (err) {
} else {
if (
err.response.data &&
err.config.url !== '/api/v1/auth/check' &&
(err.response.statusText === 'Unauthorized' ||
err.response.data === ' Unauthorized.')
) {
console.log(err.response)
// Unauthorized and log out
store.dispatch('notification/showNotification', {
type: 'error',

View File

@ -19,7 +19,7 @@ export default {
stepHeadingContainer: 'heading-section',
stepTitle: 'text-2xl not-italic font-semibold leading-7 text-black',
stepDescription:
'w-full mt-2.5 mb-8 text-sm not-italic leading-snug text-gray-500 lg:w-7/12 md:w-7/12 sm:w-7/12',
'w-full mt-2.5 mb-8 text-sm not-italic text-gray-600 lg:w-7/12 md:w-7/12 sm:w-7/12',
},
variants: {},
}

View File

@ -1,4 +1,4 @@
import i18n from '../plugins/i18n';
import i18n from '../plugins/i18n'
export default {
addClass(el, className) {
@ -51,9 +51,9 @@ export default {
.replace(/(\d{3})(?=\d)/g, '$1' + thousand_separator)
let precisionText = precision
? decimal_separator +
Math.abs(amount - i)
.toFixed(precision)
.slice(2)
Math.abs(amount - i)
.toFixed(precision)
.slice(2)
: ''
let combinedAmountText =
negativeSign + thousandText + amountText + precisionText
@ -104,9 +104,9 @@ export default {
.replace(/(\d{3})(?=\d)/g, '$1' + thousand_separator)
let precisionText = precision
? decimal_separator +
Math.abs(amount - i)
.toFixed(precision)
.slice(2)
Math.abs(amount - i)
.toFixed(precision)
.slice(2)
: ''
let combinedAmountText =
negativeSign + thousandText + amountText + precisionText
@ -130,11 +130,11 @@ export default {
}
let pattern = new RegExp(
'^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$',
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$',
'i'
) // fragment locator
@ -142,12 +142,20 @@ export default {
},
checkValidDomainUrl(url) {
if (url.includes('localhost')) {
if (url.includes('localhost') || url.includes('127.0.0.1')) {
return true
}
let pattern = new RegExp(
'^([0-9A-Za-z-\\.@:%_+~#=]+)+((\\.[a-zA-Z]{2,3})+)(/(.)*)?(\\?(.)*)?'
)
'^(https?:\\/\\/)?' + // protocol
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' + // domain name
'((\\d{1,3}\\.){3}\\d{1,3}))' + // OR ip (v4) address
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' + // port and path
'(\\?[;&a-z\\d%_.~+=-]*)?' + // query string
'(\\#[-a-z\\d_]*)?$',
'i'
) // fragment locator
return !!pattern.test(url)
},

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,6 @@ import SiteSidebar from './partials/TheSiteSidebar.vue'
import BaseModal from '../../components/base/modal/BaseModal'
import { RefreshIcon } from '@vue-hero-icons/solid'
import { mapActions, mapGetters } from 'vuex'
import BaseNotification from '../../components/base/BaseNotification.vue'
export default {
components: {
@ -30,7 +29,6 @@ export default {
SiteFooter,
BaseModal,
RefreshIcon,
BaseNotification,
},
computed: {

View File

@ -1,5 +1,6 @@
<template>
<div class="site-wrapper h-full text-base">
<div class="h-full text-base site-wrapper">
<base-notification />
<div class="container mx-auto">
<router-view></router-view>
</div>

View File

@ -1,6 +1,6 @@
<template>
<div
class="flex flex-col items-center justify-between w-full h-32 pt-10 step-indicator"
class="flex flex-col items-center justify-between w-full h-32 pt-10 step-indicator"
>
<img
id="logo-crater"
@ -22,6 +22,7 @@
import SystemRequirement from './WizardSystemRequirementStep'
import Permission from './WizardPermissionStep'
import Database from './WizardDatabaseStep'
import VerifyDomain from './WizardVerifyDomainStep'
import EmailConfiguration from './WizardEmailConfigStep'
import UserProfile from './WizardUserProfileStep'
import CompanyInfo from './WizardCompanyInfoStep'
@ -32,10 +33,11 @@ export default {
step_1: SystemRequirement,
step_2: Permission,
step_3: Database,
step_4: EmailConfiguration,
step_5: UserProfile,
step_6: CompanyInfo,
step_7: Settings,
step_4: VerifyDomain,
step_5: EmailConfiguration,
step_6: UserProfile,
step_7: CompanyInfo,
step_8: Settings,
},
data() {
return {
@ -69,16 +71,27 @@ export default {
let status = {
profile_complete: data,
}
let response = await axios.post('/api/v1/onboarding/wizard-step', status)
try {
let response = await axios.post(
'/api/v1/onboarding/wizard-step',
status
)
return true
} catch (e) {
if (e?.response?.data?.message === 'The MAC is invalid.') {
window.location.reload()
}
return false
}
},
async setTab(data) {
if (data) {
this.setProfileComplete(data)
let res = await this.setProfileComplete(data)
if (!res) return false
}
this.step++
if (this.step <= 7) {
if (this.step <= 8) {
this.tab = 'step_' + this.step
} else {
// window.location.reload()

View File

@ -297,7 +297,7 @@ export default {
})
}
this.$emit('next', 6)
this.$emit('next', 7)
this.isLoading = false
}
},

View File

@ -16,7 +16,6 @@
</template>
<script>
import { validationMixin } from 'vuelidate'
import Mysql from './database/MysqlDatabase'
import Pgsql from './database/PgsqlDatabase'
import Sqlite from './database/SqliteDatabase'
@ -67,15 +66,11 @@ export default {
async next(databaseData) {
this.isLoading = this.isFetching = true
try {
await window.axios.get('/sanctum/csrf-cookie')
let response = await window.axios.post(
'/api/v1/onboarding/database/config',
databaseData
)
await window.axios.get('/sanctum/csrf-cookie')
if (response.data.success) {
await window.axios.post('/api/v1/onboarding/finish')

View File

@ -67,7 +67,7 @@ export default {
mailConfigData
)
if (response.data.success) {
this.$emit('next', 4)
this.$emit('next', 5)
this.showNotification({
type: 'success',
message: this.$t('wizard.success.' + response.data.success),

View File

@ -246,7 +246,7 @@ export default {
this.uploadAvatar(avatarData)
}
this.$emit('next', 5)
this.$emit('next', 6)
this.isLoading = false
}
return true

View File

@ -0,0 +1,136 @@
<template>
<sw-wizard-step
:title="$t('wizard.verify_domain.title')"
:description="$t('wizard.verify_domain.desc')"
>
<div class="w-full md:w-2/3">
<sw-input-group
:label="$t('wizard.verify_domain.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.formData.app_domain.$error"
v-model.trim="formData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.formData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<p class="mt-4 mb-0 text-sm text-gray-600">Notes:</p>
<ul class="w-full text-gray-600 list-disc list-inside">
<li class="text-sm leading-8">
App domain should not contain
<b class="inline-block px-1 bg-gray-100 rounded-sm">https://</b> or
<b class="inline-block px-1 bg-gray-100 rounded-sm">http</b> in front of
the domain.
</li>
<li class="text-sm leading-8">
If you're accessing the website on a different port, please mention the
port. For example:
<b class="inline-block px-1 bg-gray-100">localhost:8080</b>
</li>
</ul>
<sw-button
:loading="isLoading"
:disabled="isLoading"
class="mt-8"
variant="primary"
@click="verifyDomain"
>
{{ $t('wizard.verify_domain.verify_now') }}
</sw-button>
</sw-wizard-step>
</template>
<script>
import { ArrowRightIcon } from '@vue-hero-icons/solid'
const { required } = require('vuelidate/lib/validators')
import { mapActions } from 'vuex'
export default {
components: {
ArrowRightIcon,
},
data() {
return {
formData: {
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
isLoading: false,
isShow: true,
}
},
validations: {
formData: {
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
computed: {
hasNext() {
return false
},
domainError() {
if (!this.$v.formData.app_domain.$error) {
return ''
}
if (!this.$v.formData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.formData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
},
methods: {
...mapActions('notification', ['showNotification']),
listToggle() {
this.isShow = !this.isShow
},
async verifyDomain() {
this.$v.formData.$touch()
if (this.$v.formData.$invalid) {
return true
}
this.isLoading = true
try {
await window.axios.put('api/v1/onboarding/set-domain', this.formData)
await window.axios.get('/sanctum/csrf-cookie')
await window.axios.post('/api/v1/onboarding/login')
let driverRes = await window.axios.get('/api/v1/auth/check')
if (driverRes.data) {
await this.$emit('next', 4)
}
this.isLoading = false
} catch (e) {
this.showNotification({
type: 'error',
message: this.$t('wizard.errors.domain_verification_failed'),
})
this.isLoading = false
}
},
},
}
</script>

View File

@ -1,7 +1,7 @@
<template>
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.app_url')"
:error="urlError"
@ -16,23 +16,6 @@
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.databaseData.app_domain.$error"
v-model.trim="databaseData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.databaseData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.connection')"
:error="connectionError"
@ -62,9 +45,7 @@
@input="$v.databaseData.database_port.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.db_name')"
:error="nameError"
@ -92,9 +73,7 @@
@input="$v.databaseData.database_username.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group :label="$t('wizard.database.password')">
<sw-input
v-model.trim="databaseData.database_password"
@ -102,7 +81,9 @@
name="name"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group
:label="$t('wizard.database.host')"
:error="hostnameError"
@ -141,7 +122,7 @@ export default {
components: {
SaveIcon,
},
props: {
props: {
configData: {
type: Object,
require: true,
@ -168,7 +149,6 @@ export default {
database_username: null,
database_password: null,
app_url: window.location.origin,
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
@ -197,12 +177,6 @@ export default {
return this.$utils.checkValidUrl(val)
},
},
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
computed: {
@ -219,19 +193,6 @@ export default {
return this.$tc('validation.invalid_url')
}
},
domainError() {
if (!this.$v.databaseData.app_domain.$error) {
return ''
}
if (!this.$v.databaseData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
connectionError() {
if (!this.$v.databaseData.database_connection.$error) {
return ''

View File

@ -1,7 +1,7 @@
<template>
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.app_url')"
:error="urlError"
@ -16,23 +16,6 @@
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.databaseData.app_domain.$error"
v-model.trim="databaseData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.databaseData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.connection')"
:error="connectionError"
@ -62,9 +45,7 @@
@input="$v.databaseData.database_port.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.db_name')"
:error="nameError"
@ -92,9 +73,7 @@
@input="$v.databaseData.database_username.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group :label="$t('wizard.database.password')">
<sw-input
v-model.trim="databaseData.database_password"
@ -134,8 +113,7 @@
<script>
import { SaveIcon } from '@vue-hero-icons/outline'
import { validationMixin } from 'vuelidate'
const { required, numeric, url } = require('vuelidate/lib/validators')
const { required, numeric } = require('vuelidate/lib/validators')
export default {
components: {
@ -168,12 +146,11 @@ export default {
database_username: null,
database_password: null,
app_url: window.location.origin,
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
},
computed: {
computed: {
urlError() {
if (!this.$v.databaseData.app_url.$error) {
return ''
@ -187,19 +164,6 @@ export default {
return this.$tc('validation.invalid_url')
}
},
domainError() {
if (!this.$v.databaseData.app_domain.$error) {
return ''
}
if (!this.$v.databaseData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
connectionError() {
if (!this.$v.databaseData.database_connection.$error) {
return ''
@ -274,12 +238,6 @@ export default {
return this.$utils.checkValidUrl(val)
},
},
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
mounted() {

View File

@ -1,7 +1,7 @@
<template>
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.app_url')"
:error="urlError"
@ -16,23 +16,6 @@
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.databaseData.app_domain.$error"
v-model.trim="databaseData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.databaseData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.connection')"
:error="connectionError"
@ -81,8 +64,7 @@
<script>
import { SaveIcon } from '@vue-hero-icons/outline'
import { validationMixin } from 'vuelidate'
const { required, numeric, url } = require('vuelidate/lib/validators')
const { required } = require('vuelidate/lib/validators')
export default {
components: {
@ -111,7 +93,6 @@ export default {
database_connection: 'mysql',
database_name: null,
app_url: window.location.origin,
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
@ -130,12 +111,6 @@ export default {
return this.$utils.checkValidUrl(val)
},
},
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
computed: {
@ -152,19 +127,6 @@ export default {
return this.$tc('validation.invalid_url')
}
},
domainError() {
if (!this.$v.databaseData.app_domain.$error) {
return ''
}
if (!this.$v.databaseData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
connectionError() {
if (!this.$v.databaseData.database_connection.$error) {
return ''

View File

@ -1,7 +1,7 @@
<template>
<form action="" @submit.prevent="next()">
<div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<div class="grid grid-cols-1 gap-5 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.app_url')"
:error="urlError"
@ -16,23 +16,6 @@
/>
</sw-input-group>
<sw-input-group
:label="$t('wizard.database.app_domain')"
:error="domainError"
required
>
<sw-input
:invalid="$v.databaseData.app_domain.$error"
v-model.trim="databaseData.app_domain"
type="text"
name="name"
placeholder="crater.com"
@input="$v.databaseData.app_domain.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.connection')"
:error="connectionError"
@ -62,9 +45,7 @@
@input="$v.databaseData.database_port.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-4 md:grid-cols-2 lg:mb-6 md:mb-6">
<sw-input-group
:label="$t('wizard.database.db_name')"
:error="nameError"
@ -92,9 +73,7 @@
@input="$v.databaseData.database_username.$touch()"
/>
</sw-input-group>
</div>
<div class="grid grid-cols-1 gap-4 mb-6 md:grid-cols-2">
<sw-input-group :label="$t('wizard.database.password')">
<sw-input
v-model.trim="databaseData.database_password"
@ -134,14 +113,13 @@
<script>
import { SaveIcon } from '@vue-hero-icons/outline'
import { validationMixin } from 'vuelidate'
const { required, numeric, url } = require('vuelidate/lib/validators')
const { required, numeric } = require('vuelidate/lib/validators')
export default {
components: {
SaveIcon,
},
props: {
props: {
configData: {
type: Object,
require: true,
@ -168,7 +146,6 @@ export default {
database_username: null,
database_password: null,
app_url: window.location.origin,
app_domain: window.location.origin.replace(/(^\w+:|^)\/\//, ''),
},
connections: ['sqlite', 'mysql', 'pgsql', 'sqlsrv'],
}
@ -197,12 +174,6 @@ export default {
return this.$utils.checkValidUrl(val)
},
},
app_domain: {
required,
isUrl(val) {
return this.$utils.checkValidDomainUrl(val)
},
},
},
},
computed: {
@ -219,19 +190,6 @@ export default {
return this.$tc('validation.invalid_url')
}
},
domainError() {
if (!this.$v.databaseData.app_domain.$error) {
return ''
}
if (!this.$v.databaseData.app_domain.required) {
return this.$tc('validation.required')
}
if (!this.$v.databaseData.app_domain.isUrl) {
return this.$tc('validation.invalid_domain_url')
}
},
connectionError() {
if (!this.$v.databaseData.database_connection.$error) {
return ''
@ -282,7 +240,7 @@ export default {
}
},
},
mounted() {
mounted() {
for (const key in this.databaseData) {
if (this.configData.hasOwnProperty(key)) {
this.databaseData[key] = this.configData[key]

View File

@ -36,8 +36,10 @@ use Crater\Http\Controllers\V1\Invoice\SendInvoiceController;
use Crater\Http\Controllers\V1\Item\ItemsController;
use Crater\Http\Controllers\V1\Item\UnitsController;
use Crater\Http\Controllers\V1\Mobile\AuthController;
use Crater\Http\Controllers\V1\Onboarding\AppDomainController;
use Crater\Http\Controllers\V1\Onboarding\DatabaseConfigurationController;
use Crater\Http\Controllers\V1\Onboarding\FinishController;
use Crater\Http\Controllers\V1\Onboarding\LoginController;
use Crater\Http\Controllers\V1\Onboarding\OnboardingWizardController;
use Crater\Http\Controllers\V1\Onboarding\PermissionsController;
use Crater\Http\Controllers\V1\Onboarding\RequirementsController;
@ -133,12 +135,17 @@ Route::prefix('/v1')->group(function () {
Route::get('/onboarding/database/config', [DatabaseConfigurationController::class, 'getDatabaseEnvironment']);
Route::put('/onboarding/set-domain', AppDomainController::class);
Route::post('/onboarding/login', LoginController::class);
Route::post('/onboarding/finish', FinishController::class);
});
Route::middleware(['auth:sanctum', 'admin'])->group(function () {
// Bootstrap
//----------------------------------
@ -151,6 +158,12 @@ Route::prefix('/v1')->group(function () {
Route::get('/dashboard', DashboardController::class);
// Auth check
//----------------------------------
Route::get('/auth/check', [AuthController::class, 'check']);
// Search users
//----------------------------------