mktcms 0.1.6 → 0.1.7
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/README.md +69 -17
- package/dist/module.json +1 -1
- package/dist/module.mjs +28 -5
- package/dist/runtime/app/components/admin.vue +1 -1
- package/dist/runtime/app/components/content/editor.d.vue.ts +3 -0
- package/dist/runtime/app/components/content/editor.vue +42 -0
- package/dist/runtime/app/components/content/editor.vue.d.ts +3 -0
- package/dist/runtime/app/components/content/index.vue +5 -5
- package/dist/runtime/app/components/header.d.vue.ts +3 -0
- package/dist/runtime/app/components/header.vue +25 -0
- package/dist/runtime/app/components/header.vue.d.ts +3 -0
- package/dist/runtime/app/pages/admin/edit/[path].d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin/edit/[path].vue +12 -0
- package/dist/runtime/app/pages/admin/edit/[path].vue.d.ts +3 -0
- package/dist/runtime/app/pages/admin/index.vue +2 -12
- package/dist/runtime/app/pages/admin/new.d.vue.ts +3 -0
- package/dist/runtime/app/pages/admin/new.vue +12 -0
- package/dist/runtime/app/pages/admin/new.vue.d.ts +3 -0
- package/dist/runtime/server/api/admin/content/[path].post.d.ts +2 -0
- package/dist/runtime/server/api/admin/content/[path].post.js +18 -0
- package/dist/runtime/server/api/admin/content/upload.js +2 -2
- package/dist/runtime/server/api/content/[path].js +4 -2
- package/dist/runtime/server/api/content/list.js +3 -3
- package/dist/runtime/server/utils/sendMail.d.ts +4 -0
- package/dist/runtime/server/utils/sendMail.js +22 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
Get your module up and running quickly.
|
|
1
|
+
# Simple CMS module for Nuxt (pre-alpha)
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
- Name: My Module
|
|
6
|
-
- Package name: mktcms
|
|
7
|
-
- Description: My new Nuxt module
|
|
8
|
-
-->
|
|
9
|
-
|
|
10
|
-
# Simple CMS module for Nuxt
|
|
3
|
+
This module is my personal, minimalist, opinionated, independent alternative to @nuxt/content and to a large portion of the WordPress projects I’ve worked on.
|
|
11
4
|
|
|
12
5
|
[![npm version][npm-version-src]][npm-version-href]
|
|
13
6
|
[![npm downloads][npm-downloads-src]][npm-downloads-href]
|
|
@@ -20,21 +13,80 @@ Find and replace all on all files (CMD+SHIFT+F):
|
|
|
20
13
|
|
|
21
14
|
## Features
|
|
22
15
|
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
## Quick Setup
|
|
16
|
+
- S3 bucket explorer/editor at `/admin`
|
|
17
|
+
- API routes at `/api/admin`
|
|
18
|
+
- `ADMIN_AUTH_KEY` env var to set a password
|
|
19
|
+
- `useContent` composable
|
|
29
20
|
|
|
30
|
-
|
|
21
|
+
## Setup
|
|
31
22
|
|
|
32
23
|
```bash
|
|
33
24
|
npx nuxi module add mktcms
|
|
34
25
|
```
|
|
35
26
|
|
|
36
|
-
|
|
27
|
+
```bash
|
|
28
|
+
MKTCMS_ADMIN_AUTH_KEY="your-admin-auth-key"
|
|
29
|
+
MKTCMS_S3_ACCESS_KEY_ID=your-s3-access-key-id
|
|
30
|
+
MKTCMS_S3_SECRET_ACCESS_KEY=your-s3-secret-access-key
|
|
31
|
+
MKTCMS_S3_BUCKET=your-s3-bucket-name
|
|
32
|
+
MKTCMS_S3_REGION=your-s3-bucket-region
|
|
33
|
+
MKTCMS_S3_PREFIX="your-project"
|
|
34
|
+
MKTCMS_SMTP_HOST="your-smtp-host"
|
|
35
|
+
MKTCMS_SMTP_PORT=465
|
|
36
|
+
MKTCMS_SMTP_SECURE=true
|
|
37
|
+
MKTCMS_SMTP_USER="your-smtp-user"
|
|
38
|
+
MKTCMS_SMTP_PASS="your-smtp-pass"
|
|
39
|
+
MKTCMS_MAILER_FROM="your-mailer-from-address"
|
|
40
|
+
MKTCMS_MAILER_TO="your-mailer-to-address"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
Assuming json files in S3 like `your-project:articles:article-1.json`:
|
|
46
|
+
|
|
47
|
+
```vue
|
|
48
|
+
<script setup lang="ts">
|
|
49
|
+
import { useContent } from 'mktcms'
|
|
50
|
+
|
|
51
|
+
type Article = {
|
|
52
|
+
id: string
|
|
53
|
+
title: string
|
|
54
|
+
content: string
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const { data: articles } = await useContent<Article[]>('articles')
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<article v-for="article in articles" :key="article.id">
|
|
62
|
+
<h2>{{ article.title }}</h2>
|
|
63
|
+
<p>{{ article.content }}</p>
|
|
64
|
+
</article>
|
|
65
|
+
</template>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For a specific article:
|
|
37
69
|
|
|
70
|
+
```vue
|
|
71
|
+
<script setup lang="ts">
|
|
72
|
+
import { useContent } from 'mktcms'
|
|
73
|
+
|
|
74
|
+
type Article = {
|
|
75
|
+
id: string
|
|
76
|
+
title: string
|
|
77
|
+
content: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const { data: article } = await useContent<Article>('articles/article-1.json')
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<template>
|
|
84
|
+
<article>
|
|
85
|
+
<h2>{{ article.title }}</h2>
|
|
86
|
+
<p>{{ article.content }}</p>
|
|
87
|
+
</article>
|
|
88
|
+
</template>
|
|
89
|
+
```
|
|
38
90
|
|
|
39
91
|
## Contribution
|
|
40
92
|
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver, addServerPlugin, addServerHandler, extendPages } from '@nuxt/kit';
|
|
1
|
+
import { defineNuxtModule, createResolver, addServerImportsDir, addServerPlugin, addServerHandler, extendPages } from '@nuxt/kit';
|
|
2
2
|
import defu from 'defu';
|
|
3
3
|
|
|
4
4
|
const module$1 = defineNuxtModule({
|
|
@@ -10,13 +10,21 @@ const module$1 = defineNuxtModule({
|
|
|
10
10
|
const resolver = createResolver(import.meta.url);
|
|
11
11
|
_nuxt.options.runtimeConfig.mktcms = defu((_nuxt.options.runtimeConfig.mktcms, {
|
|
12
12
|
adminAuthKey: "",
|
|
13
|
-
filesPathPrefix: "",
|
|
14
13
|
s3AccessKey: "",
|
|
15
14
|
s3SecretKey: "",
|
|
16
15
|
s3Endpoint: "",
|
|
17
16
|
s3Bucket: "",
|
|
18
|
-
s3Region: ""
|
|
17
|
+
s3Region: "",
|
|
18
|
+
s3Prefix: "",
|
|
19
|
+
smtpHost: "",
|
|
20
|
+
smtpPort: 465,
|
|
21
|
+
smtpSecure: true,
|
|
22
|
+
smtpUser: "",
|
|
23
|
+
smtpPass: "",
|
|
24
|
+
mailerFrom: "",
|
|
25
|
+
mailerTo: ""
|
|
19
26
|
}));
|
|
27
|
+
addServerImportsDir(resolver.resolve("runtime/server/utils"));
|
|
20
28
|
addServerPlugin(resolver.resolve("./runtime/server/plugins/storage"));
|
|
21
29
|
addServerHandler({
|
|
22
30
|
middleware: true,
|
|
@@ -30,6 +38,11 @@ const module$1 = defineNuxtModule({
|
|
|
30
38
|
route: "/api/admin/logout",
|
|
31
39
|
handler: resolver.resolve("./runtime/server/api/admin/logout")
|
|
32
40
|
});
|
|
41
|
+
addServerHandler({
|
|
42
|
+
route: "/api/admin/content/:path",
|
|
43
|
+
method: "post",
|
|
44
|
+
handler: resolver.resolve("./runtime/server/api/admin/content/[path].post")
|
|
45
|
+
});
|
|
33
46
|
addServerHandler({
|
|
34
47
|
route: "/api/admin/content/upload",
|
|
35
48
|
handler: resolver.resolve("./runtime/server/api/admin/content/upload")
|
|
@@ -39,15 +52,25 @@ const module$1 = defineNuxtModule({
|
|
|
39
52
|
handler: resolver.resolve("./runtime/server/api/content/list")
|
|
40
53
|
});
|
|
41
54
|
addServerHandler({
|
|
42
|
-
route: "/api/content
|
|
55
|
+
route: "/api/content/:path",
|
|
43
56
|
handler: resolver.resolve("./runtime/server/api/content/[path]")
|
|
44
57
|
});
|
|
45
58
|
extendPages((pages) => {
|
|
46
59
|
pages.push({
|
|
47
60
|
name: "Admin Dashboard",
|
|
48
|
-
path: "/admin/:path
|
|
61
|
+
path: "/admin/:path?",
|
|
49
62
|
file: resolver.resolve("./runtime/app/pages/admin/index.vue")
|
|
50
63
|
});
|
|
64
|
+
pages.push({
|
|
65
|
+
name: "Admin Editor",
|
|
66
|
+
path: "/admin/edit/:path",
|
|
67
|
+
file: resolver.resolve("./runtime/app/pages/admin/edit/[path].vue")
|
|
68
|
+
});
|
|
69
|
+
pages.push({
|
|
70
|
+
name: "Admin New Content",
|
|
71
|
+
path: "/admin/new",
|
|
72
|
+
file: resolver.resolve("./runtime/app/pages/admin/new.vue")
|
|
73
|
+
});
|
|
51
74
|
pages.push({
|
|
52
75
|
name: "Admin Login",
|
|
53
76
|
path: "/admin/login",
|
|
@@ -5,5 +5,5 @@
|
|
|
5
5
|
</template>
|
|
6
6
|
|
|
7
7
|
<style>
|
|
8
|
-
body{font-family:Arial,sans-serif;margin:0}#mktcms-admin{background-color:#f9f9f9;margin:0 auto;max-width:800px;padding:20px}#mktcms-admin h1{color:#333}#mktcms-admin button{background-color:#3cb371;border:none;border-radius:5px;color:#fff;cursor:pointer;font-size:16px;padding:10px 20px;transition:background-color .3s}#mktcms-admin button:hover{background-color:#45a049}#mktcms-admin input[type=email],#mktcms-admin input[type=password],#mktcms-admin input[type=text],#mktcms-admin textarea{border:1px solid #ccc;border-radius:5px;font-size:16px;padding:10px}#mktcms-admin .breadcrumbs{color:#888;font-size:1.5rem;margin:20px 0}#mktcms-admin .breadcrumbs a{color:#888;text-decoration:none}#mktcms-admin .breadcrumbs a:hover{text-decoration:underline}#mktcms-admin .dirs,#mktcms-admin .files{display:flex;flex-direction:column;gap:8px}#mktcms-admin .dirs a,#mktcms-admin .files a{border-radius:4px;display:block;padding:8px 12px;text-decoration:none}#mktcms-admin .dirs a:hover,#mktcms-admin .files a:hover{text-decoration:underline}#mktcms-admin .files a{background-color:#fff;color:#555}#mktcms-admin .dirs a{background-color:#555;color:#fff;display:flex;justify-content:space-between}
|
|
8
|
+
*{box-sizing:border-box}body{font-family:Arial,sans-serif;margin:0}#mktcms-admin{background-color:#f9f9f9;margin:0 auto;max-width:800px;padding:20px}#mktcms-admin h1{color:#333}#mktcms-admin button{background-color:#3cb371;border:none;border-radius:5px;color:#fff;cursor:pointer;font-size:16px;padding:10px 20px;transition:background-color .3s}#mktcms-admin button:hover{background-color:#45a049}#mktcms-admin input[type=email],#mktcms-admin input[type=password],#mktcms-admin input[type=text],#mktcms-admin textarea{border:1px solid #ccc;border-radius:5px;font-size:16px;padding:10px}#mktcms-admin .breadcrumbs{color:#888;font-size:1.5rem;margin:20px 0}#mktcms-admin .breadcrumbs a{color:#888;text-decoration:none}#mktcms-admin .breadcrumbs a:hover{text-decoration:underline}#mktcms-admin .dirs,#mktcms-admin .files{display:flex;flex-direction:column;gap:8px}#mktcms-admin .dirs a,#mktcms-admin .files a{border-radius:4px;display:block;padding:8px 12px;text-decoration:none}#mktcms-admin .dirs a:hover,#mktcms-admin .files a:hover{text-decoration:underline}#mktcms-admin .files a{background-color:#fff;color:#555}#mktcms-admin .dirs a{background-color:#555;color:#fff;display:flex;justify-content:space-between}
|
|
9
9
|
</style>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { useFetch, useRoute } from "#app";
|
|
3
|
+
const path = useRoute().params.path || "";
|
|
4
|
+
const pathParts = path.split(":");
|
|
5
|
+
const { data: content } = await useFetch(`/api/content/${path}`);
|
|
6
|
+
async function saveContent() {
|
|
7
|
+
await $fetch(`/api/admin/content/${path}`, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
body: { content: content.value }
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div>
|
|
16
|
+
<div class="breadcrumbs">
|
|
17
|
+
<a href="/admin">Hauptverzeichnis</a>
|
|
18
|
+
<span
|
|
19
|
+
v-for="(part, index) in pathParts"
|
|
20
|
+
:key="index"
|
|
21
|
+
>
|
|
22
|
+
/
|
|
23
|
+
<a :href="`/admin/${pathParts.slice(0, index + 1).join(':')}`">
|
|
24
|
+
{{ part }}
|
|
25
|
+
</a>
|
|
26
|
+
</span>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div v-if="content !== void 0">
|
|
30
|
+
<textarea
|
|
31
|
+
v-model="content"
|
|
32
|
+
style="width: 100%; resize: vertical;"
|
|
33
|
+
/>
|
|
34
|
+
<button
|
|
35
|
+
style="margin-top: 10px;"
|
|
36
|
+
@click="saveContent"
|
|
37
|
+
>
|
|
38
|
+
Speichern
|
|
39
|
+
</button>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { useFetch, useRoute } from "#app";
|
|
3
3
|
import { computed } from "vue";
|
|
4
4
|
const path = useRoute().params.path || "";
|
|
5
|
-
const pathParts = path.split("
|
|
5
|
+
const pathParts = path.split(":");
|
|
6
6
|
const { data: keys } = await useFetch("/api/content/list", {
|
|
7
7
|
query: { path }
|
|
8
8
|
});
|
|
@@ -37,7 +37,7 @@ const dirs = computed(() => {
|
|
|
37
37
|
:key="index"
|
|
38
38
|
>
|
|
39
39
|
/
|
|
40
|
-
<a :href="`/admin/${pathParts.slice(0, index + 1).join('
|
|
40
|
+
<a :href="`/admin/${pathParts.slice(0, index + 1).join(':')}`">
|
|
41
41
|
{{ part }}
|
|
42
42
|
</a>
|
|
43
43
|
</span>
|
|
@@ -50,7 +50,7 @@ const dirs = computed(() => {
|
|
|
50
50
|
<a
|
|
51
51
|
v-for="file in files"
|
|
52
52
|
:key="file"
|
|
53
|
-
:href="`/admin/${path ? path + '
|
|
53
|
+
:href="`/admin/edit/${path ? path + ':' : ''}${file}`"
|
|
54
54
|
>{{ file }}</a>
|
|
55
55
|
</div>
|
|
56
56
|
|
|
@@ -62,10 +62,10 @@ const dirs = computed(() => {
|
|
|
62
62
|
<a
|
|
63
63
|
v-for="dir in dirs"
|
|
64
64
|
:key="dir"
|
|
65
|
-
:href="`/admin/${path ? path + '
|
|
65
|
+
:href="`/admin/${path ? path + ':' : ''}${dir}`"
|
|
66
66
|
>
|
|
67
67
|
<span>
|
|
68
|
-
{{ dir
|
|
68
|
+
{{ dir }}
|
|
69
69
|
</span>
|
|
70
70
|
<span>
|
|
71
71
|
<svg
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div style="display: flex; justify-content: space-between; align-items: center;">
|
|
3
|
+
<h1>
|
|
4
|
+
<NuxtLink
|
|
5
|
+
to="/admin"
|
|
6
|
+
style="text-decoration: none; color: inherit;"
|
|
7
|
+
>
|
|
8
|
+
Website Verwaltung
|
|
9
|
+
</NuxtLink>
|
|
10
|
+
</h1>
|
|
11
|
+
<NuxtLink
|
|
12
|
+
to="/admin/new"
|
|
13
|
+
style="background-color: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; margin-left: auto; margin-right: 10px;"
|
|
14
|
+
>
|
|
15
|
+
Neuer Inhalt
|
|
16
|
+
</NuxtLink>
|
|
17
|
+
<NuxtLink
|
|
18
|
+
external
|
|
19
|
+
to="/api/admin/logout"
|
|
20
|
+
style="background-color: #ccc; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;"
|
|
21
|
+
>
|
|
22
|
+
Abmelden
|
|
23
|
+
</NuxtLink>
|
|
24
|
+
</div>
|
|
25
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import Admin from "../../../components/admin.vue";
|
|
3
|
+
import Header from "../../../components/header.vue";
|
|
4
|
+
import Editor from "../../../components/content/editor.vue";
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<Admin>
|
|
9
|
+
<Header />
|
|
10
|
+
<Editor />
|
|
11
|
+
</Admin>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
<script setup>
|
|
2
2
|
import Admin from "../../components/admin.vue";
|
|
3
|
+
import Header from "../../components/header.vue";
|
|
3
4
|
import Content from "../../components/content/index.vue";
|
|
4
|
-
import ContentUpload from "../../components/content/upload.vue";
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<template>
|
|
8
8
|
<Admin>
|
|
9
|
-
<
|
|
10
|
-
<h1>Website Verwaltung</h1>
|
|
11
|
-
<a
|
|
12
|
-
href="/api/admin/logout"
|
|
13
|
-
style="background-color: #ccc; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px;"
|
|
14
|
-
>
|
|
15
|
-
Abmelden
|
|
16
|
-
</a>
|
|
17
|
-
</div>
|
|
18
|
-
|
|
19
|
-
<ContentUpload />
|
|
9
|
+
<Header />
|
|
20
10
|
<Content />
|
|
21
11
|
</Admin>
|
|
22
12
|
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import Admin from "../../components/admin.vue";
|
|
3
|
+
import Header from "../../components/header.vue";
|
|
4
|
+
import ContentUpload from "../../components/content/upload.vue";
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<template>
|
|
8
|
+
<Admin>
|
|
9
|
+
<Header />
|
|
10
|
+
<ContentUpload />
|
|
11
|
+
</Admin>
|
|
12
|
+
</template>
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { defineEventHandler, getValidatedRouterParams, readValidatedBody } from "h3";
|
|
3
|
+
import { useRuntimeConfig, useStorage } from "nitropack/runtime";
|
|
4
|
+
const paramsSchema = z.object({
|
|
5
|
+
path: z.string().min(1)
|
|
6
|
+
});
|
|
7
|
+
const bodySchema = z.object({
|
|
8
|
+
content: z.string()
|
|
9
|
+
});
|
|
10
|
+
export default defineEventHandler(async (event) => {
|
|
11
|
+
const { path } = await getValidatedRouterParams(event, (params) => paramsSchema.parse(params));
|
|
12
|
+
const { content } = await readValidatedBody(event, (body) => bodySchema.parse(body));
|
|
13
|
+
const { mktcms: { s3Prefix } } = useRuntimeConfig();
|
|
14
|
+
const fullPath = s3Prefix + ":" + path;
|
|
15
|
+
const storage = useStorage("content");
|
|
16
|
+
const file = await storage.setItem(fullPath, content);
|
|
17
|
+
return file;
|
|
18
|
+
});
|
|
@@ -9,7 +9,7 @@ const querySchema = z.object({
|
|
|
9
9
|
});
|
|
10
10
|
export default defineEventHandler(async (event) => {
|
|
11
11
|
const form = await readMultipartFormData(event);
|
|
12
|
-
const { mktcms: {
|
|
12
|
+
const { mktcms: { s3Prefix } } = useRuntimeConfig();
|
|
13
13
|
const { path } = await getValidatedQuery(event, (query) => querySchema.parse(query));
|
|
14
14
|
const sanePath = path ? path.replace(/^\//, "").replace(/\/$/, "") : void 0;
|
|
15
15
|
if (!form) {
|
|
@@ -39,7 +39,7 @@ export default defineEventHandler(async (event) => {
|
|
|
39
39
|
statusMessage: "Invalid file type. Only PDF, JPG, JPEG, and PNG files are allowed."
|
|
40
40
|
});
|
|
41
41
|
}
|
|
42
|
-
const filePath = [
|
|
42
|
+
const filePath = [s3Prefix, sanePath, sanitizeFilename(file.filename)].filter(Boolean).join("/");
|
|
43
43
|
await useStorage("content").setItemRaw(filePath, Buffer.from(file.data));
|
|
44
44
|
const returnFileName = filePath;
|
|
45
45
|
return { success: true, path: returnFileName };
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { createError, defineEventHandler, getValidatedRouterParams } from "h3";
|
|
3
|
-
import { useStorage } from "nitropack/runtime";
|
|
3
|
+
import { useRuntimeConfig, useStorage } from "nitropack/runtime";
|
|
4
4
|
const paramsSchema = z.object({
|
|
5
5
|
path: z.string().min(1)
|
|
6
6
|
});
|
|
7
7
|
export default defineEventHandler(async (event) => {
|
|
8
8
|
const { path } = await getValidatedRouterParams(event, (params) => paramsSchema.parse(params));
|
|
9
|
+
const { mktcms: { s3Prefix } } = useRuntimeConfig();
|
|
10
|
+
const fullPath = s3Prefix + ":" + path;
|
|
9
11
|
const storage = useStorage("content");
|
|
10
|
-
const file = await storage.getItem(
|
|
12
|
+
const file = await storage.getItem(fullPath);
|
|
11
13
|
if (!file) {
|
|
12
14
|
throw createError({
|
|
13
15
|
statusCode: 404,
|
|
@@ -6,8 +6,8 @@ const querySchema = z.object({
|
|
|
6
6
|
});
|
|
7
7
|
export default defineEventHandler(async (event) => {
|
|
8
8
|
const { path } = await getValidatedQuery(event, (query) => querySchema.parse(query));
|
|
9
|
-
const { mktcms: {
|
|
9
|
+
const { mktcms: { s3Prefix } } = useRuntimeConfig();
|
|
10
10
|
const storage = useStorage("content");
|
|
11
|
-
const keys = await storage.getKeys(
|
|
12
|
-
return keys.map((key) => key.replace(
|
|
11
|
+
const keys = await storage.getKeys(s3Prefix + (path ? ":" + path : ""));
|
|
12
|
+
return keys.map((key) => key.replace(s3Prefix + ":", ""));
|
|
13
13
|
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import nodemailer from "nodemailer";
|
|
2
|
+
import { useRuntimeConfig } from "#imports";
|
|
3
|
+
export default async function sendMail({ subject, fields }) {
|
|
4
|
+
const { mktcms: { smtpHost, smtpPort, smtpUser, smtpPass, smtpSecure, mailerFrom, mailerTo } } = useRuntimeConfig();
|
|
5
|
+
const transporter = nodemailer.createTransport({
|
|
6
|
+
host: smtpHost,
|
|
7
|
+
port: smtpPort,
|
|
8
|
+
secure: smtpSecure,
|
|
9
|
+
auth: {
|
|
10
|
+
user: smtpUser,
|
|
11
|
+
pass: smtpPass
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
const mailOptions = {
|
|
15
|
+
from: mailerFrom,
|
|
16
|
+
to: mailerTo,
|
|
17
|
+
subject,
|
|
18
|
+
html: Object.entries(fields).map(([key, value]) => `<p><strong>${key}:</strong> ${value}</p>`).join(""),
|
|
19
|
+
text: Object.entries(fields).map(([key, value]) => `${key}: ${value}`).join("\n")
|
|
20
|
+
};
|
|
21
|
+
return await transporter.sendMail(mailOptions);
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mktcms",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Simple CMS module for Nuxt",
|
|
5
5
|
"repository": "mktcode/mktcms",
|
|
6
6
|
"license": "MIT",
|
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
"@nuxt/kit": "^4.2.2",
|
|
39
39
|
"aws4fetch": "^1.0.20",
|
|
40
40
|
"defu": "^6.1.4",
|
|
41
|
+
"nodemailer": "^7.0.12",
|
|
41
42
|
"zod": "^4.3.5"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"@nuxt/schema": "^4.2.2",
|
|
48
49
|
"@nuxt/test-utils": "^3.22.0",
|
|
49
50
|
"@types/node": "latest",
|
|
51
|
+
"@types/nodemailer": "^7.0.5",
|
|
50
52
|
"changelogen": "^0.6.2",
|
|
51
53
|
"eslint": "^9.39.2",
|
|
52
54
|
"nuxt": "^4.2.2",
|