@hywax/cms 0.0.7 → 0.0.9

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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "configKey": "cms",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.1",
package/dist/module.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { createResolver, addComponentsDir, addImportsDir, addPlugin, addImports, addServerImports, addServerImportsDir, hasNuxtModule, installModule, addServerHandler, addTypeTemplate, addTemplate, addServerTemplate, defineNuxtModule } from '@nuxt/kit';
1
+ import { createResolver, addComponentsDir, addPlugin, addImports, addImportsDir, addServerImports, addServerImportsDir, hasNuxtModule, installModule, addServerHandler, addTypeTemplate, addTemplate, addServerTemplate, defineNuxtModule } from '@nuxt/kit';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { dirname, join } from 'pathe';
4
4
  import { defu } from 'defu';
@@ -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.7";
9
+ const version = "0.0.9";
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: {
@@ -61,19 +62,25 @@ function prepareAutoImports({ resolve, options, nuxt }) {
61
62
  pathPrefix: false,
62
63
  global: true
63
64
  });
64
- addImportsDir(resolve("./runtime/composables"));
65
- addPlugin(resolve("./runtime/plugins/api.ts"));
65
+ addPlugin(resolve("./runtime/plugins/api"));
66
66
  addImports([
67
67
  ...httpCodesImports,
68
68
  { name: "docToMarkdown", from: resolve("./runtime/editor/markdown") },
69
69
  { name: "markdownToDoc", from: resolve("./runtime/editor/markdown") }
70
70
  ]);
71
+ addImportsDir([
72
+ resolve("./runtime/composables"),
73
+ resolve("./runtime/utils")
74
+ ]);
71
75
  addServerImports([
72
76
  ...httpCodesImports,
73
77
  { name: "docToMarkdown", from: resolve("./runtime/editor/markdown") },
74
78
  { name: "markdownToDoc", from: resolve("./runtime/editor/markdown") }
75
79
  ]);
76
- addServerImportsDir(resolve("./runtime/server/utils"));
80
+ addServerImportsDir([
81
+ resolve("./runtime/utils"),
82
+ resolve("./runtime/server/utils")
83
+ ]);
77
84
  nuxt.options.nitro.alias ||= {};
78
85
  nuxt.options.nitro.alias["#cms/http-codes"] = httpCodesPath;
79
86
  nuxt.options.alias["#cms"] = resolve("./runtime");
@@ -170,6 +177,7 @@ function prepareMergeConfigs({ nuxt, options }) {
170
177
  name: `${options.name}-session`
171
178
  });
172
179
  nuxt.options.runtimeConfig.uplora = defu(nuxt.options.runtimeConfig.uplora || {}, {
180
+ url: options.uploraUrl,
173
181
  apiKey: ""
174
182
  });
175
183
  }
@@ -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
- :model-value="modelValue.alt"
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 { open, execute: uploadExecute, status: uploadStatus, reset: resetUpload, onUploaded } = useUploraUpload();
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 updateAlt(alt) {
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>, "onSubmit" | "onReset"> & TablePanelFiltersProps<T> & Partial<{}>> & import("vue").PublicProps;
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,4 +1,4 @@
1
- import type { Ref } from 'vue';
1
+ import type { Ref } from '#imports';
2
2
  export interface UseAdminReturn {
3
3
  isSidebarOpen: Ref<boolean>;
4
4
  isSidebarCollapsed: Ref<boolean>;
@@ -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
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<never>>;
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 default defineHttpHandler(() => {
3
- throw new Error("Not implemented");
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
- declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
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
- export default defineHttpHandler(async () => {
3
- await new Promise((resolve) => setTimeout(resolve, 300));
4
- return {
5
- id: "f50hmx9sbnr6azkvc9ir46dg",
6
- lqip: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAclBMVEUiKCLCu7LSo4HftJOwr6q5ta9LOi0pKiQyLiY+MynmvZvZrIlYQTLVx7qlpqLLwbfq07zrxqbuz7JkSTfKmXe3hmbfz75xUTyfc1WrfF3BkG6TaU58WEKcnJeHYEffwafQu6fhybLFq5Wwo5WSko2FiYXlL2BpAAAACXBIWXMAAAsTAAALEwEAmpwYAAADAklEQVR4nJ2VbW+bQAzH/z4oJC2iaUqkbVGaStu+/1dqlFfV+gRqk0B4mGwfcNAs0uqrjjP1z76zzYV+g6UBkANIRdthKJe7S35cq+afIajhoS8YSpXyHUAIa0868WBsJwyA9BpGCVW7nZAQraLqjp2lSOELkLsh1LzsEN4IseMdOFJqGBBigQUT4rEse4KVEk4k+sX2AYA/orb+Y2TRuw7EmXNowM8VwGa9YcDaI4t4ESF6j4zoJeCTUL7YS6ItEIsWmQiWqnnzMTKU8PlQxto3d9tVucQaa1brGnV/mLquayCO+VhLEP3ULW5XavMKazzdTxXYT/fyNMDNZr1Zb+inLUtt2PT59jl5Sp46+1YsdSNz8yCAOm/mlDwlmF6NCEynCRLMtLTmnh+Pi+1KcyiH+wCKoAgKBAUQFJyiK/H8Mid4NHuYPT6X2WorqTI1l2ZfVuDBf7yoqviVAzc8efR2/wh/mWbI8itORTpJJ/ms6DpP5WAm0tvgjfFOgQOAEFggi7PZm7ZZn2cDXKfXb1o0Sl6UaSGE0h8z29a1kUpK7WwDUwJm2kAC6eT2f9teufwj4dmN5FLcRNJL3ScC5PQDKM5QveTtRAtc8Jqxdoculoe9f3lMGAF66hTWWx840aDvPJefOWmZw0RIkUnba/Rt8LGfCAe3oecvc9BSWgLnQCtzmQPQnQJ2phOYFe1OHC/8WnsGXk96TDYMkpIqdJTH0T9ah4bJCp5gDUB8pZRQMxbfXsagW/ilrNWfRKxGOx12DUU29y5ohpgVr/IsorotswNqPHtEl6HATUcLDuL1UmuUqetJeRd0U3a8kGRQV+AxZ9PkSOlLxfTHghOjZ0Ml9eObRUQDWvkIuT9Jug2jhGrE7rp2pAhAofP1DcnujHyjya2mIki77slPmJNmavPzGRtdGO7rMB9hrnHvov1FhwdUYVXBz6vKz52cDs930l+Y8xhfLAwO2cYiYms5XfwjAsup6G240QFbMSfe5XwFhWBQx/9JeHaPZzEdX+O/JH8BtUM+KttnGNQAAAAASUVORK5CYII="
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 {};
@@ -1,6 +1,7 @@
1
1
  export class InternalHttpError extends Error {
2
- constructor(httpCodeString) {
2
+ constructor(httpCodeString, options) {
3
3
  super("Internal Error");
4
4
  this.httpCodeString = httpCodeString;
5
+ this.cause = options?.cause;
5
6
  }
6
7
  }
@@ -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 { PaginationQueryRaw, PaginationQueryValidated, SortQuery, SortQueryRaw } from '../../types';
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): PaginationQueryValidated;
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
+ }
@@ -17,6 +17,3 @@ export interface PaginationQuery {
17
17
  page: number;
18
18
  perPage: number;
19
19
  }
20
- export type PaginationQueryValidated = PaginationQuery & {
21
- pageOffset: number;
22
- };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hywax/cms",
3
3
  "type": "module",
4
- "version": "0.0.7",
4
+ "version": "0.0.9",
5
5
  "description": "Hywax CMS. ⚠️ This package is intended for internal use only.",
6
6
  "repository": {
7
7
  "type": "git",