@userfrosting/sprinkle-core 6.0.0-beta.6 → 6.0.0-beta.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/Page401Unauthorized-Dkz0W0kI.js +11 -0
- package/dist/Page403Forbidden-CZrvZLe3.js +11 -0
- package/dist/Page404NotFound-C7Y20KCv.js +11 -0
- package/dist/PageError-wq0-2ksY.js +11 -0
- package/dist/_plugin-vue_export-helper-CHgC5LLL.js +9 -0
- package/dist/composables/index.d.ts +4 -0
- package/dist/composables/useAxiosInterceptor.d.ts +10 -0
- package/dist/composables/useCsrf.d.ts +18 -0
- package/dist/composables/useRuleSchemaAdapter.d.ts +7 -0
- package/dist/composables/useSprunjer.d.ts +2 -0
- package/dist/composables.js +151 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +15 -0
- package/{app/assets/interfaces/ApiResponse.ts → dist/interfaces/ApiResponse.d.ts} +5 -6
- package/{app/assets/interfaces/DictionaryApi.ts → dist/interfaces/DictionaryApi.d.ts} +9 -11
- package/dist/interfaces/alerts.d.ts +8 -0
- package/{app/assets/interfaces/common.ts → dist/interfaces/common.d.ts} +1 -1
- package/dist/interfaces/index.d.ts +13 -0
- package/{app/assets/interfaces/severity.ts → dist/interfaces/severity.d.ts} +9 -9
- package/dist/interfaces/sprunjer.d.ts +51 -0
- package/{app/assets/interfaces/sprunjerApi.ts → dist/interfaces/sprunjerApi.d.ts} +12 -15
- package/dist/interfaces.js +5 -0
- package/dist/routes/index.d.ts +16 -0
- package/dist/routes.js +41 -0
- package/dist/severity-DwLpzIij.js +4 -0
- package/{app/assets/stores/Helpers/PluralRules.ts → dist/stores/Helpers/PluralRules.d.ts} +17 -114
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/useAlertsStore.d.ts +29 -0
- package/dist/stores/useConfigStore.d.ts +11 -0
- package/dist/stores/usePageMeta.d.ts +51 -0
- package/dist/stores/useTranslator.d.ts +66 -0
- package/dist/stores.js +7 -0
- package/dist/useAlertsStore-BnSfoOG2.js +179 -0
- package/dist/useAxiosInterceptor-DTHSvv-f.js +68 -0
- package/dist/views/Page401Unauthorized.vue.d.ts +2 -0
- package/dist/views/Page403Forbidden.vue.d.ts +2 -0
- package/dist/views/Page404NotFound.vue.d.ts +2 -0
- package/dist/views/PageError.vue.d.ts +2 -0
- package/package.json +36 -8
- package/app/assets/composables/index.ts +0 -4
- package/app/assets/composables/useAxiosInterceptor.ts +0 -30
- package/app/assets/composables/useCsrf.ts +0 -129
- package/app/assets/composables/useRuleSchemaAdapter.ts +0 -205
- package/app/assets/composables/useSprunjer.ts +0 -188
- package/app/assets/index.d.ts +0 -8
- package/app/assets/index.ts +0 -40
- package/app/assets/interfaces/alerts.ts +0 -16
- package/app/assets/interfaces/index.ts +0 -30
- package/app/assets/interfaces/sprunjer.ts +0 -60
- package/app/assets/routes/index.ts +0 -44
- package/app/assets/stores/index.ts +0 -4
- package/app/assets/stores/useAlertsStore.ts +0 -30
- package/app/assets/stores/useConfigStore.ts +0 -30
- package/app/assets/stores/usePageMeta.ts +0 -114
- package/app/assets/stores/useTranslator.ts +0 -293
- package/app/assets/tests/composables/useCsrf.test.ts +0 -212
- package/app/assets/tests/composables/useRuleSchemaAdapter.test.ts +0 -657
- package/app/assets/tests/interfaces/alerts.test.ts +0 -43
- package/app/assets/tests/plugin.test.ts +0 -29
- package/app/assets/tests/stores/Helpers/PluralRules.test.ts +0 -440
- package/app/assets/tests/stores/config.test.ts +0 -42
- package/app/assets/tests/stores/useTranslator.test.ts +0 -373
- package/app/assets/views/Page401Unauthorized.vue +0 -3
- package/app/assets/views/Page403Forbidden.vue +0 -3
- package/app/assets/views/Page404NotFound.vue +0 -3
- package/app/assets/views/PageError.vue +0 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@userfrosting/sprinkle-core",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.8",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Core Sprinkle for UserFrosting",
|
|
6
6
|
"funding": "https://opencollective.com/userfrosting",
|
|
@@ -24,15 +24,39 @@
|
|
|
24
24
|
"url": "https://github.com/userfrosting/UserFrosting/issues"
|
|
25
25
|
},
|
|
26
26
|
"exports": {
|
|
27
|
-
".":
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
".": {
|
|
28
|
+
"userfrosting:monorepo": "./app/assets/index.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"types": "./dist/index.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./interfaces": {
|
|
33
|
+
"userfrosting:monorepo": "./app/assets/interfaces/index.ts",
|
|
34
|
+
"import": "./dist/interfaces.js",
|
|
35
|
+
"types": "./dist/interfaces/index.d.ts"
|
|
36
|
+
},
|
|
37
|
+
"./stores": {
|
|
38
|
+
"userfrosting:monorepo": "./app/assets/stores/index.ts",
|
|
39
|
+
"import": "./dist/stores.js",
|
|
40
|
+
"types": "./dist/stores/index.d.ts"
|
|
41
|
+
},
|
|
42
|
+
"./composables": {
|
|
43
|
+
"userfrosting:monorepo": "./app/assets/composables/index.ts",
|
|
44
|
+
"import": "./dist/composables.js",
|
|
45
|
+
"types": "./dist/composables/index.d.ts"
|
|
46
|
+
},
|
|
47
|
+
"./routes": {
|
|
48
|
+
"userfrosting:monorepo": "./app/assets/routes/index.ts",
|
|
49
|
+
"import": "./dist/routes.js",
|
|
50
|
+
"types": "./dist/routes/index.d.ts"
|
|
51
|
+
}
|
|
32
52
|
},
|
|
53
|
+
"types": "./dist/index.d.ts",
|
|
33
54
|
"files": [
|
|
34
|
-
"
|
|
55
|
+
"dist/"
|
|
35
56
|
],
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">= 18"
|
|
59
|
+
},
|
|
36
60
|
"dependencies": {
|
|
37
61
|
"@regle/core": "^1.6.0",
|
|
38
62
|
"@regle/rules": "^1.6.0",
|
|
@@ -43,6 +67,10 @@
|
|
|
43
67
|
"axios": "^1.12.0",
|
|
44
68
|
"pinia": "^2.1.6",
|
|
45
69
|
"pinia-plugin-persistedstate": "^3.2.0",
|
|
46
|
-
"vue": "^3.4.21"
|
|
70
|
+
"vue": "^3.4.21",
|
|
71
|
+
"vue-router": "^4.2.4"
|
|
72
|
+
},
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "vite build"
|
|
47
75
|
}
|
|
48
76
|
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import axios from 'axios'
|
|
2
|
-
import { useAlertsStore } from '../stores/useAlertsStore'
|
|
3
|
-
import { Severity } from '../interfaces'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Axios Error Handler
|
|
7
|
-
*
|
|
8
|
-
* This composable sets up an Axios interceptor to handle errors globally using
|
|
9
|
-
* the Alerts store. It sends the error to the Alerts store, unless it's a 401
|
|
10
|
-
* error.
|
|
11
|
-
*
|
|
12
|
-
* @see https://axios-http.com/docs/interceptors
|
|
13
|
-
*/
|
|
14
|
-
export const useAxiosInterceptor = () => {
|
|
15
|
-
axios.interceptors.response.use(
|
|
16
|
-
(response) => response,
|
|
17
|
-
(error) => {
|
|
18
|
-
if (error.response.status !== 401) {
|
|
19
|
-
const alertStore = useAlertsStore()
|
|
20
|
-
alertStore.push({
|
|
21
|
-
title: error.response.data.title ?? error.response?.statusText,
|
|
22
|
-
description: error.response?.data?.description ?? error.message,
|
|
23
|
-
style: Severity.Danger
|
|
24
|
-
})
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return Promise.reject(error)
|
|
28
|
-
}
|
|
29
|
-
)
|
|
30
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { ref, watchEffect } from 'vue'
|
|
2
|
-
import { useConfigStore } from '../stores'
|
|
3
|
-
import axios from 'axios'
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* CSRF Protection Composable
|
|
7
|
-
*
|
|
8
|
-
* Automatically sets the CSRF token in the axios headers for all requests.
|
|
9
|
-
* The CSRF token is read from the meta tags in the HTML document.
|
|
10
|
-
* The CSRF token can updated when the server responds with a new token in the headers.
|
|
11
|
-
*
|
|
12
|
-
* @see https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html#axios
|
|
13
|
-
*/
|
|
14
|
-
export const useCsrf = () => {
|
|
15
|
-
/**
|
|
16
|
-
* Public constant for the CSRF token name and value, plus respective keys.
|
|
17
|
-
*/
|
|
18
|
-
const key_name = ref(getNameKey())
|
|
19
|
-
const key_value = ref(getValueKey())
|
|
20
|
-
const name = ref(readMetaTag(key_name.value))
|
|
21
|
-
const token = ref(readMetaTag(key_value.value))
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Set the axios headers for CSRF protection
|
|
25
|
-
*/
|
|
26
|
-
function setAxiosHeader() {
|
|
27
|
-
axios.defaults.headers.post[key_name.value] = name.value
|
|
28
|
-
axios.defaults.headers.post[key_value.value] = token.value
|
|
29
|
-
axios.defaults.headers.put[key_name.value] = name.value
|
|
30
|
-
axios.defaults.headers.put[key_value.value] = token.value
|
|
31
|
-
axios.defaults.headers.delete[key_name.value] = name.value
|
|
32
|
-
axios.defaults.headers.delete[key_value.value] = token.value
|
|
33
|
-
axios.defaults.headers.patch[key_name.value] = name.value
|
|
34
|
-
axios.defaults.headers.patch[key_value.value] = token.value
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Fetch the CSRF token from the server
|
|
39
|
-
*/
|
|
40
|
-
async function fetchCsrfToken() {
|
|
41
|
-
const response = await axios.get('/api/csrf')
|
|
42
|
-
updateFromHeaders(response.headers)
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Get the CSRF token name and value keys from config.
|
|
47
|
-
*/
|
|
48
|
-
function getNameKey(): string {
|
|
49
|
-
const config = useConfigStore()
|
|
50
|
-
return config.get('csrf.name', 'csrf') + '_name'
|
|
51
|
-
}
|
|
52
|
-
function getValueKey(): string {
|
|
53
|
-
const config = useConfigStore()
|
|
54
|
-
return config.get('csrf.name', 'csrf') + '_value'
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Meta tag reader and writer
|
|
59
|
-
*/
|
|
60
|
-
function readMetaTag(name: string): string {
|
|
61
|
-
return document.querySelector("meta[name='" + name + "']")?.getAttribute('content') ?? ''
|
|
62
|
-
}
|
|
63
|
-
function writeMetaTag(name: string, value: string) {
|
|
64
|
-
const metaTag = document.querySelector("meta[name='" + name + "']")
|
|
65
|
-
if (metaTag) {
|
|
66
|
-
metaTag.setAttribute('content', value)
|
|
67
|
-
} else {
|
|
68
|
-
const newMetaTag = document.createElement('meta')
|
|
69
|
-
newMetaTag.setAttribute('name', name)
|
|
70
|
-
newMetaTag.setAttribute('content', value)
|
|
71
|
-
document.head.appendChild(newMetaTag)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Update the CSRF token with the values from the request headers.
|
|
77
|
-
*
|
|
78
|
-
* N.B.: CSRF keys are hardcoded with '{name}_name' and '{name}_value' in
|
|
79
|
-
* PHP. However, the headers doesn't allows underscores that are replaced
|
|
80
|
-
* with dashes automatically.
|
|
81
|
-
*/
|
|
82
|
-
function updateFromHeaders(headers: any) {
|
|
83
|
-
const config = useConfigStore()
|
|
84
|
-
const nameKey = config.get('csrf.name', 'csrf') + '-name'
|
|
85
|
-
const valueKey = config.get('csrf.name', 'csrf') + '-value'
|
|
86
|
-
|
|
87
|
-
// Update both value only if the headers are present
|
|
88
|
-
// This is to avoid overwriting the CSRF token with empty values
|
|
89
|
-
if (nameKey in headers) {
|
|
90
|
-
name.value = headers[nameKey]
|
|
91
|
-
}
|
|
92
|
-
if (valueKey in headers) {
|
|
93
|
-
token.value = headers[valueKey]
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Return if CSRF is enabled
|
|
99
|
-
*/
|
|
100
|
-
function isEnabled(): boolean {
|
|
101
|
-
const config = useConfigStore()
|
|
102
|
-
return config.get('csrf.enabled', true)
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Watchers - Watch for changes in the CSRF token and update the axios
|
|
107
|
-
* headers + meta tags
|
|
108
|
-
*/
|
|
109
|
-
watchEffect(() => {
|
|
110
|
-
if (isEnabled() && name.value !== '' && token.value !== '') {
|
|
111
|
-
writeMetaTag(key_name.value, name.value)
|
|
112
|
-
writeMetaTag(key_value.value, token.value)
|
|
113
|
-
setAxiosHeader()
|
|
114
|
-
}
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
/**
|
|
118
|
-
* Export functions and managed states
|
|
119
|
-
*/
|
|
120
|
-
return {
|
|
121
|
-
key_name,
|
|
122
|
-
key_value,
|
|
123
|
-
name,
|
|
124
|
-
token,
|
|
125
|
-
isEnabled,
|
|
126
|
-
updateFromHeaders,
|
|
127
|
-
fetchCsrfToken
|
|
128
|
-
}
|
|
129
|
-
}
|
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
withMessage,
|
|
3
|
-
required,
|
|
4
|
-
maxLength,
|
|
5
|
-
minLength,
|
|
6
|
-
email,
|
|
7
|
-
integer,
|
|
8
|
-
numeric,
|
|
9
|
-
url,
|
|
10
|
-
oneOf,
|
|
11
|
-
between,
|
|
12
|
-
regex,
|
|
13
|
-
not
|
|
14
|
-
} from '@regle/rules'
|
|
15
|
-
import { useTranslator } from '../stores'
|
|
16
|
-
|
|
17
|
-
export function useRuleSchemaAdapter() {
|
|
18
|
-
/**
|
|
19
|
-
* Parse the YAML schema string into a JavaScript object.
|
|
20
|
-
*
|
|
21
|
-
* @param rawSchema The YAML schema string to parse.
|
|
22
|
-
* @returns RuleSchema The Regle schema object.
|
|
23
|
-
*/
|
|
24
|
-
function adapt(sourceSchema: Record<string, any>): Record<string, any> {
|
|
25
|
-
// The Regle schema object to be returned
|
|
26
|
-
const regleSchema: any = {}
|
|
27
|
-
|
|
28
|
-
// Iterate over each field in the schema
|
|
29
|
-
for (const field in sourceSchema) {
|
|
30
|
-
// Check if the field is a direct property of the sourceSchema object
|
|
31
|
-
if (Object.prototype.hasOwnProperty.call(sourceSchema, field)) {
|
|
32
|
-
// Get the field rules from the source schema
|
|
33
|
-
const schemaFieldRules = sourceSchema[field]?.validators || {}
|
|
34
|
-
|
|
35
|
-
// The returned regle rules for the field
|
|
36
|
-
const regleRules: Record<string, any> = {}
|
|
37
|
-
|
|
38
|
-
// Iterate over each rule in the source schema field rules
|
|
39
|
-
for (const key of Object.keys(schemaFieldRules)) {
|
|
40
|
-
adaptRule(key, schemaFieldRules, regleRules)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
regleSchema[field] = regleRules
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return regleSchema
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function translateMessage(fieldRulesMeta: { message?: string; [key: string]: any }): string {
|
|
51
|
-
const { translate } = useTranslator()
|
|
52
|
-
|
|
53
|
-
// If there's no message, return an empty string
|
|
54
|
-
if (!fieldRulesMeta.message) {
|
|
55
|
-
return ''
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Copy the field rules meta to avoid mutation and remove the message key
|
|
59
|
-
const fieldRulesMetaCopy = { ...fieldRulesMeta }
|
|
60
|
-
delete fieldRulesMetaCopy.message
|
|
61
|
-
|
|
62
|
-
return translate(fieldRulesMeta.message, fieldRulesMetaCopy)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function adaptRule(key: string, schemaFieldRules: any, regleRules: Record<string, any>) {
|
|
66
|
-
// Required
|
|
67
|
-
if (key === 'required' && schemaFieldRules.required) {
|
|
68
|
-
const message: string = translateMessage(schemaFieldRules.required)
|
|
69
|
-
regleRules['required'] = message === '' ? required : withMessage(required, message)
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Email
|
|
73
|
-
if (key === 'email' && schemaFieldRules.email) {
|
|
74
|
-
const message: string = translateMessage(schemaFieldRules.email)
|
|
75
|
-
regleRules['email'] = withMessage(email, message)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Length
|
|
79
|
-
if (key === 'length' && schemaFieldRules.length) {
|
|
80
|
-
if (schemaFieldRules.length.min !== undefined) {
|
|
81
|
-
const message: string = translateMessage(schemaFieldRules.length)
|
|
82
|
-
regleRules['minLength'] =
|
|
83
|
-
message === ''
|
|
84
|
-
? minLength(schemaFieldRules.length.min)
|
|
85
|
-
: withMessage(minLength(schemaFieldRules.length.min), message)
|
|
86
|
-
}
|
|
87
|
-
if (schemaFieldRules.length.max !== undefined) {
|
|
88
|
-
const message: string = translateMessage(schemaFieldRules.length)
|
|
89
|
-
regleRules['maxLength'] =
|
|
90
|
-
message === ''
|
|
91
|
-
? maxLength(schemaFieldRules.length.max)
|
|
92
|
-
: withMessage(maxLength(schemaFieldRules.length.max), message)
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// TODO : matches
|
|
97
|
-
if (key === 'matches' && schemaFieldRules.matches) {
|
|
98
|
-
console.warn('Validation rule "matches" not implemented yet')
|
|
99
|
-
// console.debug('Matched rule: matches', schemaFieldRules.matches)
|
|
100
|
-
// sameAs: sameAs(() => form.value.password),
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// TODO : equals
|
|
104
|
-
if (key === 'equals' && schemaFieldRules.equals) {
|
|
105
|
-
console.warn('Validation rule "equals" not implemented yet')
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Integer
|
|
109
|
-
if (key === 'integer' && schemaFieldRules.integer) {
|
|
110
|
-
const message: string = translateMessage(schemaFieldRules.integer)
|
|
111
|
-
regleRules['integer'] = message === '' ? integer : withMessage(integer, message)
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// member_of
|
|
115
|
-
if (key === 'member_of' && schemaFieldRules.member_of) {
|
|
116
|
-
const message: string = translateMessage(schemaFieldRules.member_of)
|
|
117
|
-
regleRules['member_of'] =
|
|
118
|
-
message === ''
|
|
119
|
-
? oneOf(schemaFieldRules.member_of.values)
|
|
120
|
-
: withMessage(oneOf(schemaFieldRules.member_of.values), message)
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// no_leading_whitespace
|
|
124
|
-
if (key === 'no_leading_whitespace' && schemaFieldRules.no_leading_whitespace) {
|
|
125
|
-
const message: string = translateMessage(schemaFieldRules.no_leading_whitespace)
|
|
126
|
-
regleRules['no_leading_whitespace'] =
|
|
127
|
-
message === '' ? regex(/^\S.*$/) : withMessage(regex(/^\S.*$/), message)
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// no_trailing_whitespace
|
|
131
|
-
if (key === 'no_trailing_whitespace' && schemaFieldRules.no_trailing_whitespace) {
|
|
132
|
-
const message: string = translateMessage(schemaFieldRules.no_trailing_whitespace)
|
|
133
|
-
regleRules['no_trailing_whitespace'] =
|
|
134
|
-
message === '' ? regex(/^.*\S$/) : withMessage(regex(/^.*\S$/), message)
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// TODO : not_equals
|
|
138
|
-
if (key === 'not_equals' && schemaFieldRules.not_equals) {
|
|
139
|
-
console.warn('Validation rule "not_equals" not implemented yet')
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// TODO : not_matches
|
|
143
|
-
if (key === 'not_matches' && schemaFieldRules.not_matches) {
|
|
144
|
-
console.warn('Validation rule "not_matches" not implemented yet')
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// not_member_of
|
|
148
|
-
if (key === 'not_member_of' && schemaFieldRules.not_member_of) {
|
|
149
|
-
const message: string = translateMessage(schemaFieldRules.not_member_of)
|
|
150
|
-
regleRules['not_member_of'] =
|
|
151
|
-
message === ''
|
|
152
|
-
? not(oneOf(schemaFieldRules.not_member_of.values))
|
|
153
|
-
: withMessage(not(oneOf(schemaFieldRules.not_member_of.values)), message)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
// Numeric
|
|
157
|
-
if (key === 'numeric' && schemaFieldRules.numeric) {
|
|
158
|
-
const message: string = translateMessage(schemaFieldRules.numeric)
|
|
159
|
-
regleRules['numeric'] = message === '' ? numeric : withMessage(numeric, message)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Range
|
|
163
|
-
if (key === 'range' && schemaFieldRules.range) {
|
|
164
|
-
const message: string = translateMessage(schemaFieldRules.range)
|
|
165
|
-
regleRules['range'] =
|
|
166
|
-
message === ''
|
|
167
|
-
? between(schemaFieldRules.range.min, schemaFieldRules.range.max)
|
|
168
|
-
: withMessage(
|
|
169
|
-
between(schemaFieldRules.range.min, schemaFieldRules.range.max),
|
|
170
|
-
message
|
|
171
|
-
)
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Regex
|
|
175
|
-
if (key === 'regex' && schemaFieldRules.regex) {
|
|
176
|
-
const message: string = translateMessage(schemaFieldRules.regex)
|
|
177
|
-
regleRules['regex'] =
|
|
178
|
-
message === ''
|
|
179
|
-
? regex(new RegExp(schemaFieldRules.regex.regex))
|
|
180
|
-
: withMessage(regex(new RegExp(schemaFieldRules.regex.regex)), message)
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// TODO : telephone
|
|
184
|
-
if (key === 'telephone' && schemaFieldRules.telephone) {
|
|
185
|
-
console.warn('Validation rule "telephone" not implemented yet')
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// uri
|
|
189
|
-
if (key === 'uri' && schemaFieldRules.uri) {
|
|
190
|
-
const message: string = translateMessage(schemaFieldRules.uri)
|
|
191
|
-
regleRules['uri'] = message === '' ? url : withMessage(url, message)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Username
|
|
195
|
-
if (key === 'username' && schemaFieldRules.username) {
|
|
196
|
-
const message: string = translateMessage(schemaFieldRules.username)
|
|
197
|
-
regleRules['username'] =
|
|
198
|
-
message === ''
|
|
199
|
-
? regex(/^([a-z0-9.\-_])+$/i)
|
|
200
|
-
: withMessage(regex(/^([a-z0-9.\-_])+$/i), message)
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
return { adapt, translateMessage }
|
|
205
|
-
}
|
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Sprunjer Composable
|
|
3
|
-
*
|
|
4
|
-
* A composable function that fetches data from a Sprunjer API and provides
|
|
5
|
-
* all necessary function like pagination, sorting and filtering.
|
|
6
|
-
*
|
|
7
|
-
* Pass the URL of the Sprunjer API to the function, and it will fetch the data.
|
|
8
|
-
* A watcher will refetch the data whenever any parameters change.
|
|
9
|
-
*
|
|
10
|
-
* Params:
|
|
11
|
-
* @param {String} dataUrl - The URL of the Sprunjer API
|
|
12
|
-
* @param {Object} defaultSorts - An object of default sorts
|
|
13
|
-
* @param {Object} defaultFilters - An object of default filters
|
|
14
|
-
* @param {Number} defaultSize - The default number of items per page
|
|
15
|
-
* @param {Number} defaultPage - The default page number
|
|
16
|
-
*
|
|
17
|
-
* Exports:
|
|
18
|
-
* - size: The number of items per page
|
|
19
|
-
* - page: The current page number
|
|
20
|
-
* - sorts: An object of sorts
|
|
21
|
-
* - filters: An object of filters
|
|
22
|
-
* - data: The raw data from the API
|
|
23
|
-
* - fetch: A function to fetch the data
|
|
24
|
-
* - loading: A boolean indicating if the data is loading
|
|
25
|
-
* - totalPages: The total number of pages
|
|
26
|
-
* - downloadCsv: A function to download the data as a CSV file
|
|
27
|
-
* - countFiltered: The total number of items after filtering
|
|
28
|
-
* - count: The total number of items
|
|
29
|
-
* - rows: The rows of data
|
|
30
|
-
* - first: The index of the first item on the current page
|
|
31
|
-
* - last: The index of the last item on the current page
|
|
32
|
-
* - toggleSort: A function to toggle the sort order of a column
|
|
33
|
-
*/
|
|
34
|
-
import { ref, toValue, watchEffect, computed } from 'vue'
|
|
35
|
-
import axios from 'axios'
|
|
36
|
-
import type {
|
|
37
|
-
ApiErrorResponse,
|
|
38
|
-
AssociativeArray,
|
|
39
|
-
Sprunjer,
|
|
40
|
-
SprunjerData,
|
|
41
|
-
SprunjerResponse
|
|
42
|
-
} from '../interfaces'
|
|
43
|
-
|
|
44
|
-
export const useSprunjer = (
|
|
45
|
-
dataUrl: string | (() => string),
|
|
46
|
-
defaultSorts: AssociativeArray = {},
|
|
47
|
-
defaultFilters: AssociativeArray = {},
|
|
48
|
-
defaultSize: number = 10,
|
|
49
|
-
defaultPage: number = 0
|
|
50
|
-
): Sprunjer => {
|
|
51
|
-
// Sprunje parameters
|
|
52
|
-
const size = ref<number>(defaultSize)
|
|
53
|
-
const page = ref<number>(defaultPage)
|
|
54
|
-
const sorts = ref<AssociativeArray>(defaultSorts)
|
|
55
|
-
const filters = ref<AssociativeArray>(defaultFilters)
|
|
56
|
-
|
|
57
|
-
// Raw data - Init with default data
|
|
58
|
-
const data = ref<SprunjerData>({
|
|
59
|
-
count: 0,
|
|
60
|
-
count_filtered: 0,
|
|
61
|
-
rows: [],
|
|
62
|
-
listable: {},
|
|
63
|
-
sortable: [],
|
|
64
|
-
filterable: []
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
// State
|
|
68
|
-
const loading = ref<boolean>(false)
|
|
69
|
-
const error = ref<ApiErrorResponse | null>(null)
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Api fetch function
|
|
73
|
-
*/
|
|
74
|
-
async function fetch() {
|
|
75
|
-
loading.value = true
|
|
76
|
-
axios
|
|
77
|
-
.get<SprunjerResponse>(toValue(dataUrl), {
|
|
78
|
-
params: {
|
|
79
|
-
size: size.value,
|
|
80
|
-
page: page.value,
|
|
81
|
-
sorts: sorts.value,
|
|
82
|
-
filters: filters.value
|
|
83
|
-
}
|
|
84
|
-
})
|
|
85
|
-
.then((response) => {
|
|
86
|
-
// Assign the response data to the Sprunje Data.
|
|
87
|
-
// Note both object can't be assigned directly, as the response
|
|
88
|
-
// object is not a SprunjeData object.
|
|
89
|
-
data.value.count = response.data.count
|
|
90
|
-
data.value.count_filtered = response.data.count_filtered
|
|
91
|
-
data.value.rows = response.data.rows
|
|
92
|
-
data.value.listable = response.data.listable ?? {}
|
|
93
|
-
data.value.sortable = response.data.sortable ?? []
|
|
94
|
-
data.value.filterable = response.data.filterable ?? []
|
|
95
|
-
})
|
|
96
|
-
.catch((err) => {
|
|
97
|
-
error.value = err.response.data as ApiErrorResponse
|
|
98
|
-
})
|
|
99
|
-
.finally(() => {
|
|
100
|
-
loading.value = false
|
|
101
|
-
})
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Computed properties
|
|
106
|
-
*/
|
|
107
|
-
const totalPages = computed(() => {
|
|
108
|
-
// N.B.: Sprunjer page starts at 0, not 1
|
|
109
|
-
// Make sure page is never negative
|
|
110
|
-
return Math.max(Math.ceil((data.value.count_filtered ?? 0) / size.value) - 1, 0)
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
const count = computed(() => {
|
|
114
|
-
return data.value.count ?? 0
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const first = computed(() => {
|
|
118
|
-
return Math.min(page.value * size.value + 1, data.value.count ?? 0)
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
const last = computed(() => {
|
|
122
|
-
return Math.min((page.value + 1) * size.value, data.value.count_filtered ?? 0)
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
const countFiltered = computed(() => {
|
|
126
|
-
return data.value.count_filtered ?? 0
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
const rows = computed(() => {
|
|
130
|
-
return data.value.rows ?? []
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Download the data as a CSV file
|
|
135
|
-
*/
|
|
136
|
-
function downloadCsv() {
|
|
137
|
-
console.log('Not yet implemented')
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Apply sorting to a column, cycling from the previous sort order.
|
|
142
|
-
* Order goes : asc -> desc -> null -> asc
|
|
143
|
-
* Used to toggle the sort order of a column when the column header is clicked
|
|
144
|
-
* @param column The column to sort
|
|
145
|
-
*/
|
|
146
|
-
function toggleSort(column: string) {
|
|
147
|
-
let newOrder: string | null
|
|
148
|
-
if (sorts.value[column] === 'asc') {
|
|
149
|
-
newOrder = 'desc'
|
|
150
|
-
} else if (sorts.value[column] === 'desc') {
|
|
151
|
-
newOrder = null
|
|
152
|
-
} else {
|
|
153
|
-
newOrder = 'asc'
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
sorts.value[column] = newOrder
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Automatically fetch the data when any parameters change
|
|
161
|
-
*/
|
|
162
|
-
watchEffect(() => {
|
|
163
|
-
fetch()
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Export the functions and data
|
|
168
|
-
*/
|
|
169
|
-
return {
|
|
170
|
-
dataUrl,
|
|
171
|
-
size,
|
|
172
|
-
page,
|
|
173
|
-
sorts,
|
|
174
|
-
filters,
|
|
175
|
-
data,
|
|
176
|
-
fetch,
|
|
177
|
-
loading,
|
|
178
|
-
error,
|
|
179
|
-
downloadCsv,
|
|
180
|
-
totalPages,
|
|
181
|
-
countFiltered,
|
|
182
|
-
count,
|
|
183
|
-
rows,
|
|
184
|
-
first,
|
|
185
|
-
last,
|
|
186
|
-
toggleSort
|
|
187
|
-
}
|
|
188
|
-
}
|
package/app/assets/index.d.ts
DELETED
package/app/assets/index.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { App } from 'vue'
|
|
2
|
-
import { useConfigStore, useTranslator } from './stores'
|
|
3
|
-
import { useCsrf } from './composables/useCsrf'
|
|
4
|
-
import { useAxiosInterceptor } from './composables'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Core Sprinkle initialization recipe.
|
|
8
|
-
*
|
|
9
|
-
* This recipe is responsible for loading the configuration from the api,
|
|
10
|
-
* loading the translations, register the translator as $t and $tdate global
|
|
11
|
-
* properties and setting up the axios CSRF headers.
|
|
12
|
-
*/
|
|
13
|
-
export default {
|
|
14
|
-
install: (app: App) => {
|
|
15
|
-
/**
|
|
16
|
-
* Add Axios error handler.
|
|
17
|
-
* Load first to ensure that all axios requests are intercepted.
|
|
18
|
-
* This is important for the config loading and translator loading.
|
|
19
|
-
*/
|
|
20
|
-
useAxiosInterceptor()
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Load configuration
|
|
24
|
-
*/
|
|
25
|
-
useConfigStore().load()
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Load translations & add $t+$tdate to global properties
|
|
29
|
-
*/
|
|
30
|
-
const translator = useTranslator()
|
|
31
|
-
translator.load()
|
|
32
|
-
app.config.globalProperties.$t = translator.translate
|
|
33
|
-
app.config.globalProperties.$tdate = translator.translateDate
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Setup CSRF Protection.
|
|
37
|
-
*/
|
|
38
|
-
useCsrf()
|
|
39
|
-
}
|
|
40
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Alert Interface
|
|
3
|
-
*
|
|
4
|
-
* Represents a common interface for alert components. This interface is used by
|
|
5
|
-
* API when an error occurs or a successful event occurs, and consumed by the
|
|
6
|
-
* interface.
|
|
7
|
-
*/
|
|
8
|
-
import { Severity } from './severity'
|
|
9
|
-
|
|
10
|
-
export interface AlertInterface {
|
|
11
|
-
title?: string
|
|
12
|
-
description?: string
|
|
13
|
-
style?: Severity | keyof typeof Severity
|
|
14
|
-
closeBtn?: boolean
|
|
15
|
-
hideIcon?: boolean
|
|
16
|
-
}
|