@hywax/cms 0.0.7 → 0.0.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/module.d.mts +6 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +3 -1
- package/dist/runtime/components/AutocompleteSelect.vue +1 -2
- package/dist/runtime/components/InputUploraImage.vue +11 -6
- package/dist/runtime/components/TablePanelFilters.vue.d.ts +1 -1
- package/dist/runtime/composables/useAdmin.d.ts +1 -1
- package/dist/runtime/composables/useAdmin.js +1 -2
- package/dist/runtime/composables/useLogout.d.ts +8 -0
- package/dist/runtime/composables/useLogout.js +21 -0
- package/dist/runtime/server/api/uplora/[id].delete.d.ts +9 -1
- package/dist/runtime/server/api/uplora/[id].delete.js +13 -2
- package/dist/runtime/server/api/uplora/index.post.d.ts +5 -2
- package/dist/runtime/server/api/uplora/index.post.js +31 -6
- package/dist/runtime/server/errors/InternalHttpError.d.ts +5 -1
- package/dist/runtime/server/errors/InternalHttpError.js +2 -1
- package/dist/runtime/server/utils/errors.js +1 -1
- package/dist/runtime/server/utils/validation.d.ts +6 -2
- package/dist/runtime/server/utils/validation.js +28 -0
- package/dist/runtime/types/query.d.ts +0 -3
- package/package.json +1 -1
package/dist/module.d.mts
CHANGED
|
@@ -17,6 +17,11 @@ interface CMSOptions {
|
|
|
17
17
|
* @defaultValue 'https://fluxor.uplora.ru'
|
|
18
18
|
*/
|
|
19
19
|
fluxorUrl?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Uplora URL
|
|
22
|
+
* @defaultValue 'http://uplora.ru'
|
|
23
|
+
*/
|
|
24
|
+
uploraUrl?: string;
|
|
20
25
|
/**
|
|
21
26
|
* Prefix for the environment variables
|
|
22
27
|
* @defaultValue 'APP_'
|
|
@@ -35,6 +40,7 @@ declare module '@nuxt/schema' {
|
|
|
35
40
|
fluxorUrl: string;
|
|
36
41
|
version: string;
|
|
37
42
|
};
|
|
43
|
+
uploraUrl: string;
|
|
38
44
|
uploraApiKey: string;
|
|
39
45
|
}
|
|
40
46
|
}
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -6,7 +6,7 @@ import { snakeCase, kebabCase } from 'scule';
|
|
|
6
6
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
7
7
|
|
|
8
8
|
const name = "@hywax/cms";
|
|
9
|
-
const version = "0.0.
|
|
9
|
+
const version = "0.0.8";
|
|
10
10
|
|
|
11
11
|
function createContext(options, nuxt) {
|
|
12
12
|
const { resolve } = createResolver(import.meta.url);
|
|
@@ -25,6 +25,7 @@ const defaultCMSConfig = {};
|
|
|
25
25
|
const defaultModuleOptions = {
|
|
26
26
|
name: "cms",
|
|
27
27
|
prefix: "C",
|
|
28
|
+
uploraUrl: "http://uplora.ru",
|
|
28
29
|
fluxorUrl: "https://fluxor.uplora.ru",
|
|
29
30
|
envPrefix: "APP_",
|
|
30
31
|
httpCodes: {
|
|
@@ -170,6 +171,7 @@ function prepareMergeConfigs({ nuxt, options }) {
|
|
|
170
171
|
name: `${options.name}-session`
|
|
171
172
|
});
|
|
172
173
|
nuxt.options.runtimeConfig.uplora = defu(nuxt.options.runtimeConfig.uplora || {}, {
|
|
174
|
+
url: options.uploraUrl,
|
|
173
175
|
apiKey: ""
|
|
174
176
|
});
|
|
175
177
|
}
|
|
@@ -31,8 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
<script>
|
|
33
33
|
import theme from "#build/cms/autocomplete-select";
|
|
34
|
-
import { computed, ref, refDebounced, shallowRef, toRaw, triggerRef, useAppConfig, useAsyncData, useId, useNuxtData, watch } from "#imports";
|
|
35
|
-
import { useInfiniteScroll, useOffsetPagination } from "@vueuse/core";
|
|
34
|
+
import { computed, ref, refDebounced, shallowRef, toRaw, triggerRef, useAppConfig, useAsyncData, useId, useInfiniteScroll, useNuxtData, useOffsetPagination, watch } from "#imports";
|
|
36
35
|
import { tv } from "../utils/tv";
|
|
37
36
|
</script>
|
|
38
37
|
|
|
@@ -19,16 +19,16 @@
|
|
|
19
19
|
<UPopover
|
|
20
20
|
:ui="{ content: 'p-2' }"
|
|
21
21
|
:content="{ align: 'end', side: 'bottom', sideOffset: 8 }"
|
|
22
|
+
@update:open="syncState"
|
|
22
23
|
>
|
|
23
24
|
<UButton :icon="appConfig.ui.icons.ellipsisVertical" color="neutral" size="sm" />
|
|
24
25
|
|
|
25
26
|
<template #content>
|
|
26
27
|
<UFormField label="Описание изображения">
|
|
27
28
|
<UTextarea
|
|
28
|
-
|
|
29
|
+
v-model="state.alt"
|
|
29
30
|
placeholder="Введите описание..."
|
|
30
31
|
autoresize
|
|
31
|
-
@update:model-value="updateAlt"
|
|
32
32
|
/>
|
|
33
33
|
</UFormField>
|
|
34
34
|
</template>
|
|
@@ -79,7 +79,7 @@
|
|
|
79
79
|
|
|
80
80
|
<script>
|
|
81
81
|
import theme from "#build/cms/input-uplora-image";
|
|
82
|
-
import { computed, useAppConfig } from "#imports";
|
|
82
|
+
import { computed, reactive, useAppConfig } from "#imports";
|
|
83
83
|
import { imagesExtensions } from "@uplora/formats";
|
|
84
84
|
import { Primitive } from "reka-ui";
|
|
85
85
|
import { useUploraDelete, useUploraUpload } from "../composables/useUplora";
|
|
@@ -103,7 +103,12 @@ const modelValue = defineModel({ type: Object, ...{
|
|
|
103
103
|
})
|
|
104
104
|
} });
|
|
105
105
|
const appConfig = useAppConfig();
|
|
106
|
-
const
|
|
106
|
+
const state = reactive({
|
|
107
|
+
alt: modelValue.value.alt
|
|
108
|
+
});
|
|
109
|
+
const { open, execute: uploadExecute, status: uploadStatus, reset: resetUpload, onUploaded } = useUploraUpload({
|
|
110
|
+
accept: "image/*"
|
|
111
|
+
});
|
|
107
112
|
const { execute: deleteExecute, onDeleted } = useUploraDelete();
|
|
108
113
|
onUploaded((file) => {
|
|
109
114
|
modelValue.value = {
|
|
@@ -121,10 +126,10 @@ onDeleted(() => {
|
|
|
121
126
|
};
|
|
122
127
|
emit("delete");
|
|
123
128
|
});
|
|
124
|
-
function
|
|
129
|
+
function syncState() {
|
|
125
130
|
modelValue.value = {
|
|
126
131
|
...modelValue.value,
|
|
127
|
-
alt
|
|
132
|
+
alt: state.alt
|
|
128
133
|
};
|
|
129
134
|
}
|
|
130
135
|
const ui = computed(() => tv({ extend: tv(theme), ...appConfig.cms?.inputUploraImage || {} })());
|
|
@@ -18,7 +18,7 @@ declare const _default: <T extends object>(__VLS_props: NonNullable<Awaited<type
|
|
|
18
18
|
props: __VLS_PrettifyLocal<Pick<Partial<{}> & Omit<{
|
|
19
19
|
readonly onReset?: (() => any) | undefined;
|
|
20
20
|
readonly onSubmit?: (() => any) | undefined;
|
|
21
|
-
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "
|
|
21
|
+
} & import("vue").VNodeProps & import("vue").AllowedComponentProps & import("vue").ComponentCustomProps, never>, "onReset" | "onSubmit"> & TablePanelFiltersProps<T> & Partial<{}>> & import("vue").PublicProps;
|
|
22
22
|
expose(exposed: import("vue").ShallowUnwrapRef<{}>): void;
|
|
23
23
|
attrs: any;
|
|
24
24
|
slots: {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { ref, useRoute, watch } from "#imports";
|
|
2
|
-
import { createSharedComposable } from "@vueuse/shared";
|
|
1
|
+
import { createSharedComposable, ref, useRoute, watch } from "#imports";
|
|
3
2
|
export const useAdmin = createSharedComposable(() => {
|
|
4
3
|
const route = useRoute();
|
|
5
4
|
const isSidebarOpen = ref(false);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Ref } from '#imports';
|
|
2
|
+
import type { EventHookOn } from '@vueuse/shared';
|
|
3
|
+
export interface UseLogoutResult {
|
|
4
|
+
isLoading: Ref<boolean>;
|
|
5
|
+
logout: () => Promise<void>;
|
|
6
|
+
onLoggedOut: EventHookOn<void>;
|
|
7
|
+
}
|
|
8
|
+
export declare const useLogout: () => UseLogoutResult;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { clearNuxtData, createEventHook, createSharedComposable, ref, useUserSession } from "#imports";
|
|
2
|
+
export const useLogout = createSharedComposable(() => {
|
|
3
|
+
const isLoading = ref(false);
|
|
4
|
+
const { clear } = useUserSession();
|
|
5
|
+
const { on: onLoggedOut, trigger: logoutTrigger } = createEventHook();
|
|
6
|
+
async function logout() {
|
|
7
|
+
isLoading.value = true;
|
|
8
|
+
try {
|
|
9
|
+
clearNuxtData();
|
|
10
|
+
await clear();
|
|
11
|
+
logoutTrigger();
|
|
12
|
+
} finally {
|
|
13
|
+
isLoading.value = false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
isLoading,
|
|
18
|
+
logout,
|
|
19
|
+
onLoggedOut
|
|
20
|
+
};
|
|
21
|
+
});
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const filesDeleteRouteParamsSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
}, "strip", z.ZodTypeAny, {
|
|
5
|
+
id: string;
|
|
6
|
+
}, {
|
|
7
|
+
id: string;
|
|
8
|
+
}>;
|
|
9
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
2
10
|
export default _default;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
+
import { useRuntimeConfig } from "#imports";
|
|
2
|
+
import { getValidatedRouterParams } from "h3";
|
|
3
|
+
import { z } from "zod";
|
|
1
4
|
import { defineHttpHandler } from "../../utils/httpHandler.js";
|
|
2
|
-
export
|
|
3
|
-
|
|
5
|
+
export const filesDeleteRouteParamsSchema = z.object({
|
|
6
|
+
id: z.string().min(24).max(24)
|
|
7
|
+
});
|
|
8
|
+
export default defineHttpHandler(async (event) => {
|
|
9
|
+
const { id } = await getValidatedRouterParams(event, filesDeleteRouteParamsSchema.parse);
|
|
10
|
+
const { uplora } = useRuntimeConfig();
|
|
11
|
+
await $fetch(`${uplora.url}/api/files/${id}`, {
|
|
12
|
+
method: "DELETE",
|
|
13
|
+
headers: { "x-api-key": uplora.apiKey }
|
|
14
|
+
});
|
|
4
15
|
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
interface UploraFile {
|
|
2
2
|
id: string;
|
|
3
|
+
}
|
|
4
|
+
type UploraFileImage = UploraFile & {
|
|
3
5
|
lqip: string;
|
|
4
|
-
}
|
|
6
|
+
};
|
|
7
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<UploraFile | UploraFileImage>>;
|
|
5
8
|
export default _default;
|
|
@@ -1,8 +1,33 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
import { HTTP_CODE_BAD_REQUEST } from "#cms/http-codes";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
import { isImageMimeType } from "@uplora/formats";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { InternalHttpError } from "../../errors/InternalHttpError.js";
|
|
1
7
|
import { defineHttpHandler } from "../../utils/httpHandler.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
import { readValidatedMultipartFormData } from "../../utils/validation.js";
|
|
9
|
+
const filesCreatePayloadSchema = z.object({
|
|
10
|
+
file: z.object({
|
|
11
|
+
name: z.literal("file"),
|
|
12
|
+
filename: z.string(),
|
|
13
|
+
type: z.string(),
|
|
14
|
+
data: z.instanceof(Buffer)
|
|
15
|
+
})
|
|
16
|
+
});
|
|
17
|
+
export default defineHttpHandler(async (event) => {
|
|
18
|
+
const { file } = await readValidatedMultipartFormData(event, filesCreatePayloadSchema.parse);
|
|
19
|
+
const { uplora } = useRuntimeConfig();
|
|
20
|
+
if (!file) {
|
|
21
|
+
throw new InternalHttpError(HTTP_CODE_BAD_REQUEST);
|
|
22
|
+
}
|
|
23
|
+
const formData = new FormData();
|
|
24
|
+
formData.append("file", new Blob([file.data], { type: file.type }), file.filename);
|
|
25
|
+
if (isImageMimeType(file.type)) {
|
|
26
|
+
formData.append("lqip", "true");
|
|
27
|
+
}
|
|
28
|
+
return await $fetch(`${uplora.url}/api/files`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "x-api-key": uplora.apiKey },
|
|
31
|
+
body: formData
|
|
32
|
+
});
|
|
8
33
|
});
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
+
interface InternalHttpErrorOptions {
|
|
2
|
+
cause?: unknown;
|
|
3
|
+
}
|
|
1
4
|
export declare class InternalHttpError extends Error {
|
|
2
5
|
httpCodeString: string;
|
|
3
|
-
constructor(httpCodeString: string);
|
|
6
|
+
constructor(httpCodeString: string, options?: InternalHttpErrorOptions);
|
|
4
7
|
}
|
|
8
|
+
export {};
|
|
@@ -45,7 +45,7 @@ export function parseErrorString(errorString) {
|
|
|
45
45
|
export function errorServerResolver(error, errorMap) {
|
|
46
46
|
const httpCodeString = extractHttpCodeString(error, errorMap);
|
|
47
47
|
const parsedHttpCode = parseErrorString(httpCodeString);
|
|
48
|
-
consola.error({
|
|
48
|
+
consola.error.raw({
|
|
49
49
|
code: parsedHttpCode.statusCode,
|
|
50
50
|
message: parsedHttpCode.message,
|
|
51
51
|
error
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { H3Event, InferEventInput, ValidateFunction } from 'h3';
|
|
2
|
+
import type { PaginationQuery, PaginationQueryRaw, SortQuery, SortQueryRaw } from '../../types';
|
|
2
3
|
export declare function getValidatedSort<T extends string[]>(query: SortQueryRaw, availableColumns: readonly [...T]): SortQuery<T[number]>;
|
|
3
|
-
export declare function getValidatedPagination(query: PaginationQueryRaw):
|
|
4
|
+
export declare function getValidatedPagination(query: PaginationQueryRaw): PaginationQuery & {
|
|
5
|
+
pageOffset: number;
|
|
6
|
+
};
|
|
7
|
+
export declare function readValidatedMultipartFormData<T, Event extends H3Event = H3Event, _T = InferEventInput<'body', Event, T>>(event: Event, validate: ValidateFunction<_T>): Promise<_T>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { HTTP_CODE_BAD_REQUEST } from "#cms/http-codes";
|
|
2
|
+
import { readMultipartFormData } from "h3";
|
|
2
3
|
import { InternalHttpError } from "../errors/InternalHttpError.js";
|
|
3
4
|
export function getValidatedSort(query, availableColumns) {
|
|
4
5
|
if (!query.sortColumn || !availableColumns.includes(query.sortColumn)) {
|
|
@@ -24,3 +25,30 @@ export function getValidatedPagination(query) {
|
|
|
24
25
|
pageOffset
|
|
25
26
|
};
|
|
26
27
|
}
|
|
28
|
+
export async function readValidatedMultipartFormData(event, validate) {
|
|
29
|
+
const formData = await readMultipartFormData(event);
|
|
30
|
+
if (!formData) {
|
|
31
|
+
throw new InternalHttpError(HTTP_CODE_BAD_REQUEST);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
const transformedFormData = {};
|
|
35
|
+
for (const [key, value] of Object.entries(formData)) {
|
|
36
|
+
const dataKey = value.name ?? key;
|
|
37
|
+
if ("name" in value && "filename" in value && "type" in value && "data" in value) {
|
|
38
|
+
transformedFormData[dataKey] = value;
|
|
39
|
+
} else {
|
|
40
|
+
transformedFormData[dataKey] = value.data.toString();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
const res = await validate(transformedFormData);
|
|
44
|
+
if (res === false) {
|
|
45
|
+
throw new InternalHttpError(HTTP_CODE_BAD_REQUEST);
|
|
46
|
+
}
|
|
47
|
+
if (res === true) {
|
|
48
|
+
return formData;
|
|
49
|
+
}
|
|
50
|
+
return res ?? formData;
|
|
51
|
+
} catch (error) {
|
|
52
|
+
throw new InternalHttpError(HTTP_CODE_BAD_REQUEST, { cause: error });
|
|
53
|
+
}
|
|
54
|
+
}
|